mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-20 01:37:32 +00:00
Compare commits
173 Commits
v18.0
...
manager-v7
| 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)
|
[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
|
## 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).
|
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.
|
**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
|
## 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
|
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
|
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`
|
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 class Const {
|
||||||
|
|
||||||
public static final String DEBUG_TAG = "MagiskManager";
|
public static final String DEBUG_TAG = "MagiskManager";
|
||||||
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
|
||||||
|
|
||||||
// APK content
|
// APK content
|
||||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
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 */
|
/* A list of apps that should not be shown as hide-able */
|
||||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||||
Data.MM().getPackageName(),
|
App.self.getPackageName(),
|
||||||
"com.google.android.gms"
|
"com.google.android.gms"
|
||||||
);
|
);
|
||||||
|
|
||||||
public static final int USER_ID = Process.myUid() / 100000;
|
public static final int USER_ID = Process.myUid() / 100000;
|
||||||
|
|
||||||
public static final class MAGISK_VER {
|
public static final class MAGISK_VER {
|
||||||
public static final int SEPOL_REFACTOR = 1640;
|
public static final int MIN_SUPPORT = 18000;
|
||||||
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 class ID {
|
public static class ID {
|
||||||
@@ -67,12 +62,16 @@ public class Const {
|
|||||||
public static final int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
public static final int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
||||||
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
||||||
public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress";
|
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 class Url {
|
||||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
|
private static String getRaw(String where, String name) {
|
||||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
|
return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name);
|
||||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed";
|
}
|
||||||
|
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 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 ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||||
public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu";
|
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 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 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 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 {
|
public static class Key {
|
||||||
// su
|
// others
|
||||||
public static final String ROOT_ACCESS = "root_access";
|
public static final String LINK_KEY = "Link";
|
||||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||||
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";
|
|
||||||
|
|
||||||
// intents
|
// intents
|
||||||
|
public static final String FROM_SPLASH = "splash";
|
||||||
public static final String OPEN_SECTION = "section";
|
public static final String OPEN_SECTION = "section";
|
||||||
public static final String INTENT_SET_NAME = "filename";
|
public static final String INTENT_SET_NAME = "filename";
|
||||||
public static final String INTENT_SET_LINK = "link";
|
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 FLASH_SET_BOOT = "boot";
|
||||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
||||||
public static final String BROADCAST_REBOOT = "reboot";
|
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 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 FLASH_ZIP = "flash";
|
||||||
public static final String PATCH_BOOT = "patch";
|
public static final String PATCH_BOOT = "patch";
|
||||||
public static final String FLASH_MAGISK = "magisk";
|
public static final String FLASH_MAGISK = "magisk";
|
||||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||||
public static final String UNINSTALL = "uninstall";
|
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.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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 String mId, mName, mVersion, mAuthor, mDescription;
|
||||||
private int mVersionCode = -1, minMagiskVersion = -1;
|
private int mVersionCode = -1, minMagiskVersion = -1;
|
||||||
@@ -26,6 +28,37 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
|
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) {
|
private String nonNull(String s) {
|
||||||
return s == null ? "" : s;
|
return s == null ? "" : s;
|
||||||
}
|
}
|
||||||
@@ -119,9 +152,4 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
public int getMinMagiskVersion() {
|
public int getMinMagiskVersion() {
|
||||||
return minMagiskVersion;
|
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;
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
|
||||||
@@ -32,6 +35,19 @@ public class Module extends BaseModule {
|
|||||||
mUpdated = mUpdateFile.exists();
|
mUpdated = mUpdateFile.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<Module> CREATOR = new Creator<Module>() {
|
||||||
|
/* It won't be used at any place */
|
||||||
|
@Override
|
||||||
|
public Module createFromParcel(Parcel source) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Module[] newArray(int size) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public void createDisableFile() {
|
public void createDisableFile() {
|
||||||
mEnable = !mDisableFile.createNewFile();
|
mEnable = !mDisableFile.createNewFile();
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,10 @@ package com.topjohnwu.magisk.container;
|
|||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.utils.Download;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
import com.topjohnwu.magisk.utils.Logger;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
@@ -24,6 +25,30 @@ public class Repo extends BaseModule {
|
|||||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
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 {
|
public void update() throws IllegalRepoException {
|
||||||
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
||||||
try {
|
try {
|
||||||
@@ -73,7 +98,7 @@ public class Repo extends BaseModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDownloadFilename() {
|
public String getDownloadFilename() {
|
||||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IllegalRepoException extends Exception {
|
public class IllegalRepoException extends Exception {
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.database;
|
package com.topjohnwu.magisk.database;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.container.Policy;
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||||
@@ -19,12 +20,21 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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;
|
private PackageManager pm;
|
||||||
|
|
||||||
public MagiskDBCmdline() {
|
public MagiskDB(Context context) {
|
||||||
pm = Data.MM().getPackageManager();
|
pm = context.getPackageManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deletePolicy(Policy policy) {
|
||||||
|
deletePolicy(policy.uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> rawSQL(String fmt, Object... args) {
|
private List<String> rawSQL(String fmt, Object... args) {
|
||||||
@@ -70,27 +80,23 @@ public class MagiskDBCmdline extends MagiskDB {
|
|||||||
return keys.toString();
|
return keys.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearOutdated() {
|
public void clearOutdated() {
|
||||||
rawSQL(
|
rawSQL(
|
||||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
"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,
|
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) {
|
public void deletePolicy(String pkg) {
|
||||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deletePolicy(int uid) {
|
public void deletePolicy(int uid) {
|
||||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Policy getPolicy(int uid) {
|
public Policy getPolicy(int uid) {
|
||||||
List<ContentValues> res =
|
List<ContentValues> res =
|
||||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||||
@@ -104,12 +110,10 @@ public class MagiskDBCmdline extends MagiskDB {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updatePolicy(Policy policy) {
|
public void updatePolicy(Policy policy) {
|
||||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Policy> getPolicyList() {
|
public List<Policy> getPolicyList() {
|
||||||
List<Policy> list = new ArrayList<>();
|
List<Policy> list = new ArrayList<>();
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<List<SuLogEntry>> getLogs() {
|
public List<List<SuLogEntry>> getLogs() {
|
||||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||||
List<SuLogEntry> list = null;
|
List<SuLogEntry> list = null;
|
||||||
@@ -141,17 +144,14 @@ public class MagiskDBCmdline extends MagiskDB {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addLog(SuLogEntry log) {
|
public void addLog(SuLogEntry log) {
|
||||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearLogs() {
|
public void clearLogs() {
|
||||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
rawSQL("DELETE FROM %s", LOG_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSettings(String key, int value) {
|
public void setSettings(String key, int value) {
|
||||||
ContentValues data = new ContentValues();
|
ContentValues data = new ContentValues();
|
||||||
data.put("key", key);
|
data.put("key", key);
|
||||||
@@ -159,7 +159,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
|||||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSettings(String key, int defaultValue) {
|
public int getSettings(String key, int defaultValue) {
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||||
if (res.isEmpty())
|
if (res.isEmpty())
|
||||||
@@ -167,7 +166,6 @@ public class MagiskDBCmdline extends MagiskDB {
|
|||||||
return res.get(0).getAsInteger("value");
|
return res.get(0).getAsInteger("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStrings(String key, String value) {
|
public void setStrings(String key, String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
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));
|
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getStrings(String key, String defaultValue) {
|
public String getStrings(String key, String defaultValue) {
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||||
if (res.isEmpty())
|
if (res.isEmpty())
|
||||||
@@ -5,10 +5,8 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
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 com.topjohnwu.magisk.container.Repo;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -20,12 +18,9 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
private static final String TABLE_NAME = "repos";
|
private static final String TABLE_NAME = "repos";
|
||||||
|
|
||||||
private SQLiteDatabase mDb;
|
private SQLiteDatabase mDb;
|
||||||
private MagiskManager mm;
|
|
||||||
private ReposAdapter adapter;
|
|
||||||
|
|
||||||
public RepoDatabaseHelper(Context context) {
|
public RepoDatabaseHelper(Context context) {
|
||||||
super(context, "repo.db", null, DATABASE_VER);
|
super(context, "repo.db", null, DATABASE_VER);
|
||||||
mm = Data.MM();
|
|
||||||
mDb = getWritableDatabase();
|
mDb = getWritableDatabase();
|
||||||
|
|
||||||
// Remove outdated repos
|
// Remove outdated repos
|
||||||
@@ -46,7 +41,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
"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() {
|
public void clearRepo() {
|
||||||
mDb.delete(TABLE_NAME, null, null);
|
mDb.delete(TABLE_NAME, null, null);
|
||||||
notifyAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void removeRepo(String id) {
|
public void removeRepo(String id) {
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||||
notifyAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRepo(Repo repo) {
|
public void removeRepo(Repo repo) {
|
||||||
@@ -75,12 +68,10 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||||
}
|
}
|
||||||
notifyAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRepo(Repo repo) {
|
public void addRepo(Repo repo) {
|
||||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||||
notifyAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repo getRepo(String id) {
|
public Repo getRepo(String id) {
|
||||||
@@ -98,15 +89,15 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
public Cursor getRepoCursor() {
|
public Cursor getRepoCursor() {
|
||||||
String orderBy = null;
|
String orderBy = null;
|
||||||
switch (Data.repoOrder) {
|
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||||
case Const.Value.ORDER_NAME:
|
case Config.Value.ORDER_NAME:
|
||||||
orderBy = "name COLLATE NOCASE";
|
orderBy = "name COLLATE NOCASE";
|
||||||
break;
|
break;
|
||||||
case Const.Value.ORDER_DATE:
|
case Config.Value.ORDER_DATE:
|
||||||
orderBy = "last_update DESC";
|
orderBy = "last_update DESC";
|
||||||
}
|
}
|
||||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
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);
|
null, null, orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,18 +110,4 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
return set;
|
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;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.KeyguardManager;
|
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.hardware.fingerprint.FingerprintManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
import android.security.keystore.KeyProperties;
|
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.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;
|
import java.security.KeyStore;
|
||||||
|
|
||||||
@@ -35,11 +26,10 @@ public abstract class FingerprintHelper {
|
|||||||
private Cipher cipher;
|
private Cipher cipher;
|
||||||
private CancellationSignal cancel;
|
private CancellationSignal cancel;
|
||||||
|
|
||||||
public static boolean useFingerPrint() {
|
public static boolean useFingerprint() {
|
||||||
MagiskManager mm = Data.MM();
|
boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
|
||||||
boolean fp = mm.mDB.getSettings(Const.Key.SU_FINGERPRINT, 0) != 0;
|
|
||||||
if (fp && !canUseFingerprint()) {
|
if (fp && !canUseFingerprint()) {
|
||||||
mm.mDB.setSettings(Const.Key.SU_FINGERPRINT, 0);
|
Config.set(Config.Key.SU_FINGERPRINT, false);
|
||||||
fp = false;
|
fp = false;
|
||||||
}
|
}
|
||||||
return fp;
|
return fp;
|
||||||
@@ -48,65 +38,14 @@ public abstract class FingerprintHelper {
|
|||||||
public static boolean canUseFingerprint() {
|
public static boolean canUseFingerprint() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||||
return false;
|
return false;
|
||||||
MagiskManager mm = Data.MM();
|
KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
|
||||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
|
||||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
|
||||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
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 {
|
protected FingerprintHelper() throws Exception {
|
||||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
manager = App.self.getSystemService(FingerprintManager.class);
|
||||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
+ 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 android.util.Log;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.BuildConfig;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
|
import com.topjohnwu.magisk.core.BuildConfig;
|
||||||
|
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
@@ -2,29 +2,19 @@ package com.topjohnwu.magisk.utils;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
import com.topjohnwu.magisk.core.R;
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.superuser.BusyBox;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
import com.topjohnwu.superuser.ShellUtils;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class RootUtils extends Shell.Initializer {
|
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) {
|
public static void rmAndLaunch(String rm, String launch) {
|
||||||
Shell.su(Utils.fmt("(rm_launch %d %s %s)&", Const.USER_ID, rm, launch)).exec();
|
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) {
|
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||||
Shell.Job job = shell.newJob();
|
Shell.Job job = shell.newJob();
|
||||||
if (shell.isRoot()) {
|
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))
|
job.add(context.getResources().openRawResource(R.raw.util_functions))
|
||||||
.add(context.getResources().openRawResource(R.raw.utils));
|
.add(context.getResources().openRawResource(R.raw.utils));
|
||||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||||
Data.loadMagiskInfo();
|
Config.loadMagiskInfo();
|
||||||
} else {
|
} else {
|
||||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||||
job.add(nonroot);
|
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();
|
job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec();
|
||||||
|
|
||||||
Data.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
Config.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||||
Data.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
Config.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||||
|
Config.recovery = Boolean.parseBoolean(ShellUtils.fastCmd("echo $RECOVERYMODE"));
|
||||||
return true;
|
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;
|
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.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@@ -15,16 +15,15 @@ public class Topic {
|
|||||||
public static final int MODULE_LOAD_DONE = 1;
|
public static final int MODULE_LOAD_DONE = 1;
|
||||||
public static final int REPO_LOAD_DONE = 2;
|
public static final int REPO_LOAD_DONE = 2;
|
||||||
public static final int UPDATE_CHECK_DONE = 3;
|
public static final int UPDATE_CHECK_DONE = 3;
|
||||||
public static final int SNET_CHECK_DONE = 4;
|
public static final int LOCALE_FETCH_DONE = 4;
|
||||||
public static final int LOCALE_FETCH_DONE = 5;
|
|
||||||
|
|
||||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
@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)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface TopicID {}
|
public @interface TopicID {}
|
||||||
|
|
||||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
// 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) {
|
public static void subscribe(Subscriber sub, @TopicID int... topics) {
|
||||||
for (int topic : topics) {
|
for (int topic : topics) {
|
||||||
@@ -38,8 +37,7 @@ public class Topic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void subscribe(AutoSubscriber sub) {
|
public static void subscribe(AutoSubscriber sub) {
|
||||||
if (sub instanceof Subscriber)
|
subscribe(sub, sub.getSubscribedTopics());
|
||||||
subscribe((Subscriber) sub, sub.getSubscribedTopics());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
||||||
@@ -51,8 +49,7 @@ public class Topic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void unsubscribe(AutoSubscriber sub) {
|
public static void unsubscribe(AutoSubscriber sub) {
|
||||||
if (sub instanceof Subscriber)
|
unsubscribe(sub, sub.getSubscribedTopics());
|
||||||
unsubscribe((Subscriber) sub, sub.getSubscribedTopics());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void publish(@TopicID int topic, Object... results) {
|
public static void publish(@TopicID int topic, Object... results) {
|
||||||
@@ -67,7 +64,7 @@ public class Topic {
|
|||||||
topicList[topic].published = true;
|
topicList[topic].published = true;
|
||||||
}
|
}
|
||||||
for (Subscriber sub : topicList[topic].subscribers) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPublished(AutoSubscriber sub) {
|
||||||
|
return isPublished(sub.getSubscribedTopics());
|
||||||
|
}
|
||||||
|
|
||||||
private static class Store {
|
private static class Store {
|
||||||
boolean published = false;
|
boolean published = false;
|
||||||
Set<Subscriber> subscribers = new HashSet<>();
|
Set<Subscriber> subscribers = new HashSet<>();
|
||||||
@@ -100,7 +101,7 @@ public class Topic {
|
|||||||
void onPublish(int topic, Object[] result);
|
void onPublish(int topic, Object[] result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AutoSubscriber {
|
public interface AutoSubscriber extends Subscriber {
|
||||||
@TopicID
|
@TopicID
|
||||||
int[] getSubscribedTopics();
|
int[] getSubscribedTopics();
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
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.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@@ -12,19 +8,18 @@ import android.content.res.Configuration;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.Build;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.widget.Toast;
|
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.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.Module;
|
||||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
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.Shell;
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -32,6 +27,19 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class Utils {
|
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) {
|
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||||
}
|
}
|
||||||
@@ -59,7 +67,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int dpInPx(int dp) {
|
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);
|
return (int) (dp * scale + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,56 +75,28 @@ public class Utils {
|
|||||||
return String.format(Locale.US, fmt, args);
|
return String.format(Locale.US, fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String dos2unix(String s) {
|
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||||
String newString = s.replace("\r\n", "\n");
|
try {
|
||||||
if(!newString.endsWith("\n")) {
|
if (info.labelRes > 0 && Build.VERSION.SDK_INT >= 17) {
|
||||||
return newString + "\n";
|
Resources res = pm.getResourcesForApplication(info);
|
||||||
} else {
|
Configuration config = new Configuration();
|
||||||
return newString;
|
config.setLocale(LocaleManager.locale);
|
||||||
}
|
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||||
}
|
return res.getString(info.labelRes);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch (Exception ignored) {}
|
||||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
return info.loadLabel(pm).toString();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openLink(Context context, Uri link) {
|
public static String getLegalFilename(CharSequence filename) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, link);
|
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||||
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 void loadModules() {
|
public static void loadModules() {
|
||||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
App.THREAD_POOL.execute(() -> {
|
||||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||||
SuFile[] modules = path.listFiles(
|
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() {
|
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 ||
|
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) {
|
public static Context getDEContext() {
|
||||||
String s = (String) AndroidNetworking.get(url).build().executeForString().getResult();
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||||
return s == null ? "" : s;
|
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;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
|
import com.topjohnwu.signing.JarMap;
|
||||||
|
import com.topjohnwu.signing.SignAPK;
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
import com.topjohnwu.superuser.ShellUtils;
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||||
import com.topjohnwu.utils.JarMap;
|
|
||||||
import com.topjohnwu.utils.SignAPK;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
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() {
|
env_check() {
|
||||||
for file in busybox magisk magiskboot magiskinit util_functions.sh boot_patch.sh; do
|
for file in busybox magisk magiskboot magiskinit util_functions.sh boot_patch.sh; do
|
||||||
[ -f /data/adb/magisk/$file ] || return 1
|
[ -f /data/adb/magisk/$file ] || return 1
|
||||||
@@ -46,11 +7,14 @@ env_check() {
|
|||||||
|
|
||||||
fix_env() {
|
fix_env() {
|
||||||
cd /data/adb/magisk
|
cd /data/adb/magisk
|
||||||
|
local OLDPATH="$PATH"
|
||||||
|
PATH=/sbin:/system/bin:/vendor/bin
|
||||||
sh update-binary extract
|
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
|
rm -f update-binary magisk.apk
|
||||||
cd /
|
cd /
|
||||||
rm -rf /sbin/.magisk/busybox/*
|
|
||||||
/sbin/.magisk/mirror/bin/busybox --install -s /sbin/.magisk/busybox
|
|
||||||
}
|
}
|
||||||
|
|
||||||
direct_install() {
|
direct_install() {
|
||||||
@@ -105,7 +69,7 @@ post_ota() {
|
|||||||
./bootctl hal-info || return
|
./bootctl hal-info || return
|
||||||
[ `./bootctl get-current-slot` -eq 0 ] && SLOT_NUM=1 || SLOT_NUM=0
|
[ `./bootctl get-current-slot` -eq 0 ] && SLOT_NUM=1 || SLOT_NUM=0
|
||||||
./bootctl set-active-boot-slot $SLOT_NUM
|
./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
|
chmod 755 post-fs-data.d/post_ota.sh
|
||||||
cd /
|
cd /
|
||||||
}
|
}
|
||||||
@@ -139,6 +103,6 @@ EOF
|
|||||||
rm_launch() {
|
rm_launch() {
|
||||||
db_clean $1
|
db_clean $1
|
||||||
pm uninstall $2
|
pm uninstall $2
|
||||||
monkey -p $3 1
|
am start -n ${3}/a.c
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -6,7 +6,6 @@
|
|||||||
app/release
|
app/release
|
||||||
*.hprof
|
*.hprof
|
||||||
.externalNativeBuild/
|
.externalNativeBuild/
|
||||||
src/full/res/raw/util_functions.sh
|
|
||||||
public.certificate.x509.pem
|
public.certificate.x509.pem
|
||||||
private.key.pk8
|
private.key.pk8
|
||||||
*.apk
|
*.apk
|
||||||
|
|||||||
@@ -5,13 +5,9 @@ def configPath = project.hasProperty('configPath') ? project.configPath : rootPr
|
|||||||
configProps.load(new FileInputStream(configPath))
|
configProps.load(new FileInputStream(configPath))
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
|
||||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.topjohnwu.magisk"
|
applicationId 'com.topjohnwu.magisk'
|
||||||
minSdkVersion 21
|
vectorDrawables.useSupportLibrary = true
|
||||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -37,7 +33,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "mode"
|
flavorDimensions 'mode'
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
full {
|
full {
|
||||||
@@ -51,18 +47,10 @@ android {
|
|||||||
}
|
}
|
||||||
stub {
|
stub {
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "stub"
|
versionName 'stub'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
dexOptions {
|
|
||||||
preDexLibraries true
|
|
||||||
javaMaxHeapSize "2g"
|
|
||||||
}
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
@@ -70,23 +58,24 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation 'androidx.core:core:1.0.1'
|
implementation project(':net')
|
||||||
fullImplementation project(':utils')
|
fullImplementation project(':app-core')
|
||||||
fullImplementation 'com.amitshekhar.android:android-networking:1.0.2'
|
fullImplementation 'ru.noties:markwon:2.0.1'
|
||||||
fullImplementation 'androidx.appcompat:appcompat:1.0.2'
|
fullImplementation 'com.caverock:androidsvg-aar:1.3'
|
||||||
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'
|
|
||||||
|
|
||||||
def butterKnifeVersion = '9.0.0-rc2'
|
def androidXVersion = "1.0.0"
|
||||||
if (properties.containsKey('android.injected.invoked.from.ide')) {
|
implementation 'androidx.core:core:1.0.1'
|
||||||
fullImplementation "com.jakewharton:butterknife-reflect:${butterKnifeVersion}"
|
fullImplementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
} else {
|
fullImplementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
|
fullImplementation "androidx.preference:preference:${androidXVersion}"
|
||||||
fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
|
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** { *; }
|
-keep,allowoptimization class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||||
-dontwarn javax.naming.**
|
-dontwarn javax.naming.**
|
||||||
|
|
||||||
# Snet extention
|
# Snet
|
||||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
-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
|
# BootSigner
|
||||||
-dontwarn okhttp3.**
|
-keepclassmembers class com.topjohnwu.signer.BootSigner { *; }
|
||||||
|
|
||||||
|
# SVG
|
||||||
|
-dontwarn com.caverock.androidsvg.SVGAndroidRenderer
|
||||||
|
|
||||||
# Strip logging
|
# Strip logging
|
||||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<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
|
<application
|
||||||
android:name="a.q"
|
android:name="a.e"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
|
|
||||||
@@ -29,67 +28,42 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name="a.d"
|
|
||||||
android:theme="@style/AppTheme.StatusBar" />
|
|
||||||
<activity
|
|
||||||
android:name="a.e"
|
|
||||||
android:theme="@style/AppTheme.StatusBar"/>
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.f"
|
android:name="a.f"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
android:screenOrientation="nosensor"
|
android:screenOrientation="nosensor"
|
||||||
android:theme="@style/AppTheme.StatusBar" />
|
android:theme="@style/AppTheme.NoDrawer" />
|
||||||
<activity
|
|
||||||
android:name="a.g"
|
|
||||||
android:theme="@style/AppTheme.Translucent" />
|
|
||||||
|
|
||||||
<!-- Superuser -->
|
<!-- Superuser -->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="a.m"
|
android:name="a.m"
|
||||||
|
android:directBootAware="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity="internal.superuser"
|
android:taskAffinity="a.m"
|
||||||
android:theme="@style/SuRequest" />
|
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 -->
|
||||||
|
|
||||||
<receiver android:name="a.h">
|
<receiver
|
||||||
|
android:name="a.h"
|
||||||
|
android:directBootAware="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||||
|
|
||||||
<data android:scheme="package" />
|
<data android:scheme="package" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name="a.i">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<!-- Service -->
|
<!-- Service -->
|
||||||
|
|
||||||
<service
|
<service android:name="a.j" />
|
||||||
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" />
|
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
<!-- Hardcode GMS version -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.BootSigner;
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
|
import com.topjohnwu.signing.BootSigner;
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public class a extends BootSigner {
|
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;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.DonationActivity;
|
import com.topjohnwu.magisk.App;
|
||||||
|
|
||||||
public class e extends DonationActivity {
|
public class e extends App {
|
||||||
/* stub */
|
/* stub */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
package a;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.NoUIActivity;
|
import android.content.Context;
|
||||||
|
|
||||||
public class g extends NoUIActivity {
|
import com.topjohnwu.magisk.components.UpdateCheckService;
|
||||||
/* stub */
|
|
||||||
|
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;
|
package a;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.receivers.GeneralReceiver;
|
import com.topjohnwu.magisk.components.GeneralReceiver;
|
||||||
|
|
||||||
public class h extends GeneralReceiver {
|
public class h extends GeneralReceiver {
|
||||||
/* stub */
|
/* 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;
|
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 */
|
/* 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;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
|
||||||
import com.topjohnwu.magisk.components.BaseActivity;
|
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.magisk.utils.Utils;
|
||||||
import com.topjohnwu.superuser.CallbackList;
|
import com.topjohnwu.superuser.CallbackList;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindColor;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
|
||||||
public class FlashActivity extends BaseActivity {
|
public class FlashActivity extends BaseActivity {
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
@BindView(R.id.txtLog) TextView flashLogs;
|
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
||||||
@BindView(R.id.button_panel) public LinearLayout buttonPanel;
|
@BindView(R.id.reboot) Button reboot;
|
||||||
@BindView(R.id.reboot) public Button reboot;
|
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||||
@BindView(R.id.scrollView) ScrollView sv;
|
@BindColor(android.R.color.white) int white;
|
||||||
|
|
||||||
private List<String> logs;
|
private List<String> console, logs;
|
||||||
|
|
||||||
@OnClick(R.id.reboot)
|
@OnClick(R.id.reboot)
|
||||||
void reboot() {
|
void reboot() {
|
||||||
Shell.su("/system/bin/reboot").submit();
|
Utils.reboot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.save_logs)
|
@OnClick(R.id.save_logs)
|
||||||
void saveLogs() {
|
void saveLogs() {
|
||||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
runWithExternalRW(() -> {
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
String filename = String.format(Locale.US,
|
String filename = String.format(Locale.US,
|
||||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
"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
|
@Override
|
||||||
public int getDarkTheme() {
|
public int getDarkTheme() {
|
||||||
return R.style.AppTheme_StatusBar_Dark;
|
return R.style.AppTheme_NoDrawer_Dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,77 +104,167 @@ public class FlashActivity extends BaseActivity {
|
|||||||
if (!Shell.rootAccess())
|
if (!Shell.rootAccess())
|
||||||
reboot.setVisibility(View.GONE);
|
reboot.setVisibility(View.GONE);
|
||||||
|
|
||||||
logs = new ArrayList<>();
|
logs = Collections.synchronizedList(new ArrayList<>());
|
||||||
CallbackList<String> console = new CallbackList<String>(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();
|
Intent intent = getIntent();
|
||||||
Uri uri = intent.getData();
|
Uri uri = intent.getData();
|
||||||
|
|
||||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
||||||
case Const.Value.FLASH_ZIP:
|
case Const.Value.FLASH_ZIP:
|
||||||
new FlashZip(this, uri, console, logs).exec();
|
new FlashModule(uri).exec();
|
||||||
break;
|
break;
|
||||||
case Const.Value.UNINSTALL:
|
case Const.Value.UNINSTALL:
|
||||||
new UninstallMagisk(this, uri, console, logs).exec();
|
new Uninstall(uri).exec();
|
||||||
break;
|
break;
|
||||||
case Const.Value.FLASH_MAGISK:
|
case Const.Value.FLASH_MAGISK:
|
||||||
new InstallMagisk(this, console, logs, InstallMagisk.DIRECT_MODE).exec();
|
new DirectInstall().exec();
|
||||||
break;
|
break;
|
||||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||||
new InstallMagisk(this, console, logs, InstallMagisk.SECOND_SLOT_MODE).exec();
|
new SecondSlot().exec();
|
||||||
break;
|
break;
|
||||||
case Const.Value.PATCH_BOOT:
|
case Const.Value.PATCH_BOOT:
|
||||||
new InstallMagisk(this, console, logs,
|
new PatchBoot(uri).exec();
|
||||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.close)
|
private class ConsoleAdapter extends StringListAdapter<ConsoleAdapter.ViewHolder> {
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
super.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
ConsoleAdapter() {
|
||||||
public void onBackPressed() {
|
super(console, true);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Integer result) {
|
protected int itemLayoutRes() {
|
||||||
if (result == 1) {
|
return R.layout.list_item_console;
|
||||||
Data.mainHandler.postDelayed(() ->
|
}
|
||||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
|
||||||
} else {
|
@NonNull
|
||||||
super.onPostExecute(result);
|
@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;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.Menu;
|
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.ReposFragment;
|
||||||
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
||||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||||
import com.topjohnwu.magisk.utils.Download;
|
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -49,8 +50,8 @@ public class MainActivity extends BaseActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
if (!mm.hasInit) {
|
if (!getIntent().getBooleanExtra(Const.Key.FROM_SPLASH, false)) {
|
||||||
startActivity(new Intent(this, Data.classMap.get(SplashActivity.class)));
|
startActivity(new Intent(this, ClassMap.get(SplashActivity.class)));
|
||||||
finish();
|
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);
|
drawer.addDrawerListener(toggle);
|
||||||
toggle.syncState();
|
toggle.syncState();
|
||||||
@@ -120,10 +123,10 @@ public class MainActivity extends BaseActivity
|
|||||||
public void checkHideSection() {
|
public void checkHideSection() {
|
||||||
Menu menu = navigationView.getMenu();
|
Menu menu = navigationView.getMenu();
|
||||||
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
|
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
|
||||||
mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
(boolean) Config.get(Config.Key.MAGISKHIDE));
|
||||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||||
menu.findItem(R.id.downloads).setVisible(Download.checkNetworkStatus(this)
|
menu.findItem(R.id.downloads).setVisible(Networking.checkNetworkStatus(this)
|
||||||
&& Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
&& Shell.rootAccess() && Config.magiskVersionCode >= 0);
|
||||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||||
menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser());
|
menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser());
|
||||||
}
|
}
|
||||||
@@ -150,19 +153,12 @@ public class MainActivity extends BaseActivity
|
|||||||
case "settings":
|
case "settings":
|
||||||
itemId = R.id.settings;
|
itemId = R.id.settings;
|
||||||
break;
|
break;
|
||||||
case "about":
|
|
||||||
itemId = R.id.app_about;
|
|
||||||
break;
|
|
||||||
case "donation":
|
|
||||||
itemId = R.id.donation;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
navigate(itemId);
|
navigate(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void navigate(int itemId) {
|
public void navigate(int itemId) {
|
||||||
int bak = mDrawerItem;
|
|
||||||
mDrawerItem = itemId;
|
mDrawerItem = itemId;
|
||||||
navigationView.setCheckedItem(itemId);
|
navigationView.setCheckedItem(itemId);
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
@@ -188,14 +184,6 @@ public class MainActivity extends BaseActivity
|
|||||||
case R.id.settings:
|
case R.id.settings:
|
||||||
displayFragment(new SettingsFragment(), true);
|
displayFragment(new SettingsFragment(), true);
|
||||||
break;
|
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)
|
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
.replace(R.id.content_frame, navFragment)
|
.replace(R.id.content_frame, navFragment)
|
||||||
.commitNow();
|
.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.os.Bundle;
|
||||||
import android.text.TextUtils;
|
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.BaseActivity;
|
||||||
import com.topjohnwu.magisk.components.Notifications;
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||||
import com.topjohnwu.magisk.utils.Download;
|
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.LocaleManager;
|
||||||
import com.topjohnwu.magisk.utils.RootUtils;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
public class SplashActivity extends BaseActivity {
|
public class SplashActivity extends BaseActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(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)) {
|
if (pkg != null && getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, null);
|
Config.remove(Config.Key.SU_MANAGER);
|
||||||
Shell.su("pm uninstall " + pkg).exec();
|
Shell.su("pm uninstall " + pkg).submit();
|
||||||
}
|
}
|
||||||
if (TextUtils.equals(pkg, getPackageName())) {
|
if (TextUtils.equals(pkg, getPackageName())) {
|
||||||
try {
|
try {
|
||||||
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
// We are the manager, remove com.topjohnwu.magisk as it could be malware
|
||||||
getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
|
getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
|
||||||
RootUtils.uninstallPkg(BuildConfig.APPLICATION_ID);
|
Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit();
|
||||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
} 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
|
// Dynamic detect all locales
|
||||||
LocaleManager.loadAvailableLocales();
|
LocaleManager.loadAvailableLocales(R.string.app_changelog);
|
||||||
|
|
||||||
|
// Set default configs
|
||||||
|
Config.initialize();
|
||||||
|
|
||||||
// Create notification channel on Android O
|
// Create notification channel on Android O
|
||||||
Notifications.setup(this);
|
Notifications.setup(this);
|
||||||
|
|
||||||
// Setup shortcuts
|
// Schedule periodic update checks
|
||||||
sendBroadcast(new Intent(this, Data.classMap.get(ShortcutReceiver.class)));
|
AppUtils.scheduleUpdateCheck();
|
||||||
|
|
||||||
if (Download.checkNetworkStatus(this)) {
|
// Setup shortcuts
|
||||||
// Fire update check
|
Shortcuts.setup(this);
|
||||||
CheckUpdates.check();
|
|
||||||
// Repo update check
|
// Create repo database
|
||||||
new UpdateRepos().exec();
|
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
|
Intent intent = new Intent(this, ClassMap.get(MainActivity.class));
|
||||||
Data.writeConfig();
|
|
||||||
|
|
||||||
mm.hasInit = true;
|
|
||||||
|
|
||||||
Intent intent = new Intent(this, Data.classMap.get(MainActivity.class));
|
|
||||||
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
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));
|
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
import android.net.LocalSocketAddress;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.CountDownTimer;
|
import android.os.CountDownTimer;
|
||||||
import android.os.FileObserver;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
@@ -21,10 +21,12 @@ import com.topjohnwu.magisk.components.BaseActivity;
|
|||||||
import com.topjohnwu.magisk.container.Policy;
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||||
import com.topjohnwu.magisk.utils.SuConnector;
|
import com.topjohnwu.magisk.utils.SuConnector;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
|
||||||
public class SuRequestActivity extends BaseActivity {
|
public class SuRequestActivity extends BaseActivity {
|
||||||
@@ -42,48 +44,7 @@ public class SuRequestActivity extends BaseActivity {
|
|||||||
private Policy policy;
|
private Policy policy;
|
||||||
private CountDownTimer timer;
|
private CountDownTimer timer;
|
||||||
private FingerprintHelper fingerprintHelper;
|
private FingerprintHelper fingerprintHelper;
|
||||||
|
private SharedPreferences timeoutPrefs;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDarkTheme() {
|
public int getDarkTheme() {
|
||||||
@@ -114,17 +75,22 @@ public class SuRequestActivity extends BaseActivity {
|
|||||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
PackageManager pm = getPackageManager();
|
PackageManager pm = getPackageManager();
|
||||||
mm.mDB.clearOutdated();
|
app.mDB.clearOutdated();
|
||||||
|
timeoutPrefs = Utils.getDEContext().getSharedPreferences("su_timeout", 0);
|
||||||
|
|
||||||
// Get policy
|
// Get policy
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
try {
|
try {
|
||||||
String socketName = intent.getStringExtra("socket");
|
String socketName = intent.getStringExtra("socket");
|
||||||
connector = intent.getIntExtra("version", 1) == 1 ?
|
connector = new SuConnector(socketName) {
|
||||||
new SuConnectorV1(socketName) : new SuConnectorV2(socketName);
|
@Override
|
||||||
|
protected void onResponse() throws IOException {
|
||||||
|
out.writeInt(policy.policy);
|
||||||
|
}
|
||||||
|
};
|
||||||
Bundle bundle = connector.readSocketInput();
|
Bundle bundle = connector.readSocketInput();
|
||||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||||
policy = mm.mDB.getPolicy(uid);
|
policy = app.mDB.getPolicy(uid);
|
||||||
if (policy == null) {
|
if (policy == null) {
|
||||||
policy = new Policy(uid, pm);
|
policy = new Policy(uid, pm);
|
||||||
}
|
}
|
||||||
@@ -140,14 +106,14 @@ public class SuRequestActivity extends BaseActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (Data.suResponseType) {
|
switch ((int) Config.get(Config.Key.SU_AUTO_RESPONSE)) {
|
||||||
case Const.Value.SU_AUTO_DENY:
|
case Config.Value.SU_AUTO_DENY:
|
||||||
handleAction(Policy.DENY, 0);
|
handleAction(Policy.DENY, 0);
|
||||||
return;
|
return;
|
||||||
case Const.Value.SU_AUTO_ALLOW:
|
case Config.Value.SU_AUTO_ALLOW:
|
||||||
handleAction(Policy.ALLOW, 0);
|
handleAction(Policy.ALLOW, 0);
|
||||||
return;
|
return;
|
||||||
case Const.Value.SU_PROMPT:
|
case Config.Value.SU_PROMPT:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +129,21 @@ public class SuRequestActivity extends BaseActivity {
|
|||||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||||
appNameView.setText(policy.appName);
|
appNameView.setText(policy.appName);
|
||||||
packageNameView.setText(policy.packageName);
|
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,
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
timeout.setAdapter(adapter);
|
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
|
@Override
|
||||||
public void onTick(long millisUntilFinished) {
|
public void onTick(long millisUntilFinished) {
|
||||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
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) {
|
if (useFP) {
|
||||||
try {
|
try {
|
||||||
@@ -245,14 +219,16 @@ public class SuRequestActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleAction(int action) {
|
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) {
|
private void handleAction(int action, int time) {
|
||||||
policy.policy = action;
|
policy.policy = action;
|
||||||
if (time >= 0) {
|
if (time >= 0) {
|
||||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||||
mm.mDB.updatePolicy(policy);
|
app.mDB.updatePolicy(policy);
|
||||||
}
|
}
|
||||||
handleAction();
|
handleAction();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ package com.topjohnwu.magisk.adapters;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.Filter;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -18,6 +17,7 @@ import com.topjohnwu.magisk.R;
|
|||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -25,22 +25,30 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
|
||||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
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 List<String> hideList;
|
||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
private ApplicationFilter filter;
|
private boolean showSystem;
|
||||||
|
|
||||||
public ApplicationAdapter(Context context) {
|
public ApplicationAdapter(Context context) {
|
||||||
fullList = showList = Collections.emptyList();
|
fullList = showList = Collections.emptyList();
|
||||||
hideList = Collections.emptyList();
|
hideList = Collections.emptyList();
|
||||||
filter = new ApplicationFilter();
|
|
||||||
pm = context.getPackageManager();
|
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
|
@NonNull
|
||||||
@@ -50,12 +58,17 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
return new ViewHolder(v);
|
return new ViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void loadApps() {
|
private void loadApps() {
|
||||||
fullList = pm.getInstalledApplications(0);
|
fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||||
ApplicationInfo info = i.next();
|
PackageInfo info = i.next();
|
||||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled || info.uid == 1000) {
|
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();
|
i.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +76,8 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
boolean ah = hideList.contains(a.packageName);
|
boolean ah = hideList.contains(a.packageName);
|
||||||
boolean bh = hideList.contains(b.packageName);
|
boolean bh = hideList.contains(b.packageName);
|
||||||
if (ah == bh) {
|
if (ah == bh) {
|
||||||
return Utils.getAppLabel(a, pm).toLowerCase()
|
return Utils.getAppLabel(a.applicationInfo, pm)
|
||||||
.compareTo(Utils.getAppLabel(b, pm).toLowerCase());
|
.compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm));
|
||||||
} else if (ah) {
|
} else if (ah) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
@@ -74,9 +87,13 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setShowSystem(boolean b) {
|
||||||
|
showSystem = b;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
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.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||||
@@ -100,12 +117,41 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
return showList.size();
|
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) {
|
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() {
|
public void refresh() {
|
||||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
@@ -120,33 +166,4 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
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.google.android.material.snackbar.Snackbar;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
|
||||||
import com.topjohnwu.magisk.container.Module;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.uicomponents.SnackbarMaker;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Switch;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.topjohnwu.magisk.R;
|
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.container.Policy;
|
||||||
import com.topjohnwu.magisk.database.MagiskDB;
|
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 com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
|
||||||
@@ -31,10 +31,11 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
private List<Policy> policyList;
|
private List<Policy> policyList;
|
||||||
private MagiskDB dbHelper;
|
private MagiskDB dbHelper;
|
||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
private Set<Policy> expandList = new HashSet<>();
|
private boolean[] expandList;
|
||||||
|
|
||||||
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
|
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
|
||||||
policyList = list;
|
policyList = list;
|
||||||
|
expandList = new boolean[policyList.size()];
|
||||||
dbHelper = db;
|
dbHelper = db;
|
||||||
this.pm = pm;
|
this.pm = pm;
|
||||||
}
|
}
|
||||||
@@ -50,15 +51,14 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
Policy policy = policyList.get(position);
|
Policy policy = policyList.get(position);
|
||||||
|
|
||||||
holder.setExpanded(expandList.contains(policy));
|
holder.settings.setExpanded(expandList[position]);
|
||||||
|
holder.trigger.setOnClickListener(view -> {
|
||||||
holder.itemView.setOnClickListener(view -> {
|
if (holder.settings.isExpanded()) {
|
||||||
if (holder.isExpanded()) {
|
holder.settings.collapse();
|
||||||
holder.collapse();
|
expandList[position] = false;
|
||||||
expandList.remove(policy);
|
|
||||||
} else {
|
} else {
|
||||||
holder.expand();
|
holder.settings.expand();
|
||||||
expandList.add(policy);
|
expandList[position] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,12 +85,12 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
dbHelper.updatePolicy(policy);
|
dbHelper.updatePolicy(policy);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (FingerprintHelper.useFingerPrint()) {
|
if (FingerprintHelper.useFingerprint()) {
|
||||||
holder.masterSwitch.setChecked(!isChecked);
|
holder.masterSwitch.setChecked(!isChecked);
|
||||||
FingerprintHelper.showAuthDialog((Activity) v.getContext(), () -> {
|
new FingerprintAuthDialog((Activity) v.getContext(), () -> {
|
||||||
holder.masterSwitch.setChecked(isChecked);
|
holder.masterSwitch.setChecked(isChecked);
|
||||||
r.run();
|
r.run();
|
||||||
});
|
}).show();
|
||||||
} else {
|
} else {
|
||||||
r.run();
|
r.run();
|
||||||
}
|
}
|
||||||
@@ -129,9 +129,6 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
.setNegativeButton(R.string.no_thanks, null)
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.show());
|
.show());
|
||||||
|
|
||||||
// Hide for now
|
|
||||||
holder.moreInfo.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -139,31 +136,26 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
return policyList.size();
|
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.app_name) TextView appName;
|
||||||
@BindView(R.id.package_name) TextView packageName;
|
@BindView(R.id.package_name) TextView packageName;
|
||||||
@BindView(R.id.app_icon) ImageView appIcon;
|
@BindView(R.id.app_icon) ImageView appIcon;
|
||||||
@BindView(R.id.master_switch) Switch masterSwitch;
|
@BindView(R.id.master_switch) SwitchCompat masterSwitch;
|
||||||
@BindView(R.id.notification_switch) Switch notificationSwitch;
|
@BindView(R.id.notification_switch) SwitchCompat notificationSwitch;
|
||||||
@BindView(R.id.logging_switch) Switch loggingSwitch;
|
@BindView(R.id.logging_switch) SwitchCompat loggingSwitch;
|
||||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
@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.delete) ImageView delete;
|
||||||
@BindView(R.id.more_info) ImageView moreInfo;
|
@BindView(R.id.more_info) ImageView moreInfo;
|
||||||
|
|
||||||
private Container container = new Container();
|
ExpandableViewHolder settings;
|
||||||
|
|
||||||
public ViewHolder(View itemView) {
|
public ViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||||
container.expandLayout = expandLayout;
|
settings = new ArrowExpandedViewHolder(expandLayout, arrow);
|
||||||
setupExpandable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Container getContainer() {
|
|
||||||
return container;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.ClassMap;
|
||||||
import com.topjohnwu.magisk.R;
|
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.BaseActivity;
|
||||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
import com.topjohnwu.magisk.components.DownloadModuleService;
|
||||||
import com.topjohnwu.magisk.container.Module;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.container.Repo;
|
import com.topjohnwu.magisk.container.Repo;
|
||||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
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.ArrayList;
|
||||||
import java.util.List;
|
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.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||||
|
|
||||||
holder.infoLayout.setOnClickListener(v ->
|
holder.infoLayout.setOnClickListener(v ->
|
||||||
new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec());
|
MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl()));
|
||||||
|
|
||||||
holder.downloadImage.setOnClickListener(v -> {
|
holder.downloadImage.setOnClickListener(v -> {
|
||||||
new CustomAlertDialog((BaseActivity) context)
|
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()))
|
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.install, (d, i) ->
|
.setPositiveButton(R.string.install, (d, i) ->
|
||||||
DownloadModule.exec((BaseActivity) context, repo, true))
|
startDownload((BaseActivity) context, repo, true))
|
||||||
.setNeutralButton(R.string.download, (d, i) ->
|
.setNeutralButton(R.string.download, (d, i) ->
|
||||||
DownloadModule.exec((BaseActivity) context, repo, false))
|
startDownload((BaseActivity) context, repo, false))
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
.show();
|
.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() {
|
public void notifyDBChanged() {
|
||||||
if (repoCursor != null)
|
if (repoCursor != null)
|
||||||
repoCursor.close();
|
repoCursor.close();
|
||||||
@@ -178,7 +192,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
|||||||
@BindView(R.id.version_name) TextView versionName;
|
@BindView(R.id.version_name) TextView versionName;
|
||||||
@BindView(R.id.description) TextView description;
|
@BindView(R.id.description) TextView description;
|
||||||
@BindView(R.id.author) TextView author;
|
@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.download) ImageView downloadImage;
|
||||||
@BindView(R.id.update_time) TextView updateTime;
|
@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;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -9,9 +10,9 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.components.ExpandableView;
|
|
||||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
import com.topjohnwu.magisk.database.MagiskDB;
|
import com.topjohnwu.magisk.database.MagiskDB;
|
||||||
|
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
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) {
|
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||||
SuLogEntry entry = logEntries.get(section).get(position);
|
SuLogEntry entry = logEntries.get(section).get(position);
|
||||||
int realIdx = getItemPosition(section, position);
|
int realIdx = getItemPosition(section, position);
|
||||||
holder.setExpanded(itemExpanded.contains(realIdx));
|
holder.expandable.setExpanded(itemExpanded.contains(realIdx));
|
||||||
holder.itemView.setOnClickListener(view -> {
|
holder.itemView.setOnClickListener(view -> {
|
||||||
if (holder.isExpanded()) {
|
if (holder.expandable.isExpanded()) {
|
||||||
holder.collapse();
|
holder.expandable.collapse();
|
||||||
itemExpanded.remove(realIdx);
|
itemExpanded.remove(realIdx);
|
||||||
} else {
|
} else {
|
||||||
holder.expand();
|
holder.expandable.expand();
|
||||||
itemExpanded.add(realIdx);
|
itemExpanded.add(realIdx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Context context = holder.itemView.getContext();
|
||||||
holder.appName.setText(entry.appName);
|
holder.appName.setText(entry.appName);
|
||||||
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||||
holder.command.setText(entry.command);
|
holder.pid.setText(context.getString(R.string.pid, entry.fromPid));
|
||||||
holder.fromPid.setText(String.valueOf(entry.fromPid));
|
holder.uid.setText(context.getString(R.string.target_uid, entry.toUid));
|
||||||
holder.toUid.setText(String.valueOf(entry.toUid));
|
holder.command.setText(context.getString(R.string.command, entry.command));
|
||||||
holder.time.setText(entry.getTimeString());
|
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.app_name) TextView appName;
|
||||||
@BindView(R.id.action) TextView action;
|
@BindView(R.id.action) TextView action;
|
||||||
@BindView(R.id.time) TextView time;
|
@BindView(R.id.time) TextView time;
|
||||||
@BindView(R.id.fromPid) TextView fromPid;
|
@BindView(R.id.pid) TextView pid;
|
||||||
@BindView(R.id.toUid) TextView toUid;
|
@BindView(R.id.uid) TextView uid;
|
||||||
@BindView(R.id.command) TextView command;
|
@BindView(R.id.cmd) TextView command;
|
||||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||||
|
|
||||||
private Container container = new Container();
|
ExpandableViewHolder expandable;
|
||||||
|
|
||||||
LogViewHolder(View itemView) {
|
LogViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
||||||
container.expandLayout = expandLayout;
|
expandable = new ExpandableViewHolder(expandLayout);
|
||||||
setupExpandable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Container getContainer() {
|
|
||||||
return container;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
package com.topjohnwu.magisk.components;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.App;
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
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.R;
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
@@ -20,24 +21,32 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StyleRes;
|
import androidx.annotation.StyleRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
public abstract class BaseActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
public abstract class BaseActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||||
|
|
||||||
public static final String INTENT_PERM = "perm_dialog";
|
public static final String INTENT_PERM = "perm_dialog";
|
||||||
|
private static Runnable grantCallback;
|
||||||
|
|
||||||
protected static Runnable permissionGrantCallback;
|
|
||||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||||
|
|
||||||
private ActivityResultListener activityResultListener;
|
private ActivityResultListener activityResultListener;
|
||||||
public MagiskManager mm;
|
public App app = App.self;
|
||||||
|
|
||||||
|
static {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getSubscribedTopics() {
|
public int[] getSubscribedTopics() {
|
||||||
return EMPTY_INT_ARRAY;
|
return EMPTY_INT_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPublish(int topic, Object[] result) {}
|
||||||
|
|
||||||
@StyleRes
|
@StyleRes
|
||||||
public int getDarkTheme() {
|
public int getDarkTheme() {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -45,17 +54,13 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(base);
|
super.attachBaseContext(LocaleManager.getLocaleContext(base, LocaleManager.locale));
|
||||||
Configuration config = base.getResources().getConfiguration();
|
|
||||||
config.setLocale(LocaleManager.locale);
|
|
||||||
applyOverrideConfiguration(config);
|
|
||||||
mm = Data.MM();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
Topic.subscribe(this);
|
Topic.subscribe(this);
|
||||||
if (Data.isDarkTheme && getDarkTheme() != -1) {
|
if (getDarkTheme() != -1 && (boolean) Config.get(Config.Key.DARK_THEME)) {
|
||||||
setTheme(getDarkTheme());
|
setTheme(getDarkTheme());
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
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) {
|
public static void runWithPermission(Context context, String[] permissions, Runnable callback) {
|
||||||
boolean granted = true;
|
boolean granted = true;
|
||||||
for (String perm : permissions) {
|
for (String perm : permissions) {
|
||||||
@@ -95,23 +108,13 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
|||||||
callback.run();
|
callback.run();
|
||||||
} else {
|
} else {
|
||||||
// Passed in context should be an activity if not granted, need to show dialog!
|
// Passed in context should be an activity if not granted, need to show dialog!
|
||||||
permissionGrantCallback = callback;
|
if (context instanceof BaseActivity) {
|
||||||
if (!(context instanceof BaseActivity)) {
|
grantCallback = callback;
|
||||||
// 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 {
|
|
||||||
ActivityCompat.requestPermissions((BaseActivity) context, permissions, 0);
|
ActivityCompat.requestPermissions((BaseActivity) context, permissions, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
|
||||||
runWithPermission(this, permissions, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (activityResultListener != null)
|
if (activityResultListener != null)
|
||||||
@@ -132,16 +135,23 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Au
|
|||||||
grant = false;
|
grant = false;
|
||||||
}
|
}
|
||||||
if (grant) {
|
if (grant) {
|
||||||
if (permissionGrantCallback != null) {
|
if (grantCallback != null) {
|
||||||
permissionGrantCallback.run();
|
grantCallback.run();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.no_rw_storage, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.no_rw_storage, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
permissionGrantCallback = null;
|
grantCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ActivityResultListener {
|
public interface ActivityResultListener {
|
||||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
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 android.content.Intent;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Data;
|
import com.topjohnwu.magisk.App;
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import butterknife.Unbinder;
|
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;
|
protected Unbinder unbinder = null;
|
||||||
|
|
||||||
public BaseFragment() {
|
|
||||||
mm = Data.MM();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -54,4 +49,7 @@ public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
|||||||
public int[] getSubscribedTopics() {
|
public int[] getSubscribedTopics() {
|
||||||
return BaseActivity.EMPTY_INT_ARRAY;
|
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.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -22,9 +22,9 @@ public class CustomAlertDialog extends AlertDialog.Builder {
|
|||||||
private DialogInterface.OnClickListener positiveListener;
|
private DialogInterface.OnClickListener positiveListener;
|
||||||
private DialogInterface.OnClickListener negativeListener;
|
private DialogInterface.OnClickListener negativeListener;
|
||||||
private DialogInterface.OnClickListener neutralListener;
|
private DialogInterface.OnClickListener neutralListener;
|
||||||
private AlertDialog dialog;
|
|
||||||
|
|
||||||
private ViewHolder vh;
|
protected AlertDialog dialog;
|
||||||
|
protected ViewHolder vh;
|
||||||
|
|
||||||
public class ViewHolder {
|
public class ViewHolder {
|
||||||
@BindView(R.id.dialog_layout) public LinearLayout dialogLayout;
|
@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.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.widget.Toast;
|
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.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.topjohnwu.magisk.ClassMap;
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.FlashActivity;
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
import com.topjohnwu.magisk.R;
|
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.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@@ -34,7 +36,7 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
|||||||
downloadOnly(activity);
|
downloadOnly(activity);
|
||||||
break;
|
break;
|
||||||
case 2:
|
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);
|
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
break;
|
break;
|
||||||
@@ -49,13 +51,13 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
|||||||
private void patchBoot(BaseActivity a) {
|
private void patchBoot(BaseActivity a) {
|
||||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
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,
|
a.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||||
(requestCode, resultCode, data) -> {
|
(requestCode, resultCode, data) -> {
|
||||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||||
resultCode == Activity.RESULT_OK && data != null) {
|
resultCode == Activity.RESULT_OK && data != null) {
|
||||||
Intent i = new Intent(a, Data.classMap.get(FlashActivity.class))
|
Intent i = new Intent(a, ClassMap.get(FlashActivity.class))
|
||||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
.setData(data.getData())
|
||||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||||
a.startActivity(i);
|
a.startActivity(i);
|
||||||
}
|
}
|
||||||
@@ -64,27 +66,19 @@ class InstallMethodDialog extends AlertDialog.Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void downloadOnly(BaseActivity a) {
|
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",
|
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);
|
ProgressNotification progress = new ProgressNotification(filename);
|
||||||
AndroidNetworking
|
Networking.get(Config.magiskLink)
|
||||||
.download(Data.magiskLink, Const.EXTERNAL_PATH.getPath(), filename)
|
|
||||||
.build()
|
|
||||||
.setDownloadProgressListener(progress)
|
.setDownloadProgressListener(progress)
|
||||||
.startDownload(new DownloadListener() {
|
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||||
@Override
|
.getAsFile(zip, f -> {
|
||||||
public void onDownloadComplete() {
|
progress.dlDone();
|
||||||
progress.dlDone();
|
SnackbarMaker.make(a,
|
||||||
SnackbarMaker.make(a,
|
|
||||||
a.getString(R.string.internal_storage, "/Download/" + filename),
|
a.getString(R.string.internal_storage, "/Download/" + filename),
|
||||||
Snackbar.LENGTH_LONG).show();
|
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)
|
.setMessage(R.string.install_inactive_slot_msg)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
.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);
|
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||||
activity.startActivity(intent);
|
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.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
@@ -7,14 +7,14 @@ import android.net.Uri;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.androidnetworking.AndroidNetworking;
|
import com.topjohnwu.magisk.ClassMap;
|
||||||
import com.androidnetworking.error.ANError;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.androidnetworking.interfaces.DownloadListener;
|
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.FlashActivity;
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.uicomponents.ProgressNotification;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
import java.io.File;
|
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) -> {
|
setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||||
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
|
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
|
||||||
ProgressNotification progress = new ProgressNotification(zip.getName());
|
ProgressNotification progress = new ProgressNotification(zip.getName());
|
||||||
AndroidNetworking.download(Data.uninstallerLink, zip.getParent(), zip.getName())
|
Networking.get(Config.uninstallerLink)
|
||||||
.build()
|
|
||||||
.setDownloadProgressListener(progress)
|
.setDownloadProgressListener(progress)
|
||||||
.startDownload(new DownloadListener() {
|
.setErrorHandler(((conn, e) -> progress.dlFail()))
|
||||||
@Override
|
.getAsFile(zip, f -> {
|
||||||
public void onDownloadComplete() {
|
progress.dismiss();
|
||||||
progress.dismiss();
|
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
|
||||||
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.setData(Uri.fromFile(f))
|
||||||
.setData(Uri.fromFile(zip))
|
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
activity.startActivity(intent);
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ANError anError) {
|
|
||||||
progress.dlFail();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.fragments;
|
package com.topjohnwu.magisk.fragments;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -27,7 +28,9 @@ public class LogFragment extends BaseFragment {
|
|||||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||||
unbinder = new LogFragment_ViewBinding(this, v);
|
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());
|
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.fragments;
|
package com.topjohnwu.magisk.fragments;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
import android.net.Uri;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -11,72 +8,58 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
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.BuildConfig;
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.MainActivity;
|
import com.topjohnwu.magisk.MainActivity;
|
||||||
import com.topjohnwu.magisk.R;
|
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.BaseActivity;
|
||||||
import com.topjohnwu.magisk.components.BaseFragment;
|
import com.topjohnwu.magisk.components.BaseFragment;
|
||||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
import com.topjohnwu.magisk.dialogs.EnvFixDialog;
|
||||||
import com.topjohnwu.magisk.components.EnvFixDialog;
|
import com.topjohnwu.magisk.dialogs.MagiskInstallDialog;
|
||||||
import com.topjohnwu.magisk.components.ExpandableView;
|
import com.topjohnwu.magisk.dialogs.ManagerInstallDialog;
|
||||||
import com.topjohnwu.magisk.components.MagiskInstallDialog;
|
import com.topjohnwu.magisk.dialogs.UninstallDialog;
|
||||||
import com.topjohnwu.magisk.components.ManagerInstallDialog;
|
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||||
import com.topjohnwu.magisk.components.UninstallDialog;
|
import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder;
|
||||||
import com.topjohnwu.magisk.utils.Download;
|
import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder;
|
||||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
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.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
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.BindColor;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
|
||||||
public class MagiskFragment extends BaseFragment
|
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;
|
private static boolean shownDialog = false;
|
||||||
|
|
||||||
@BindView(R.id.swipeRefreshLayout) public SwipeRefreshLayout mSwipeRefreshLayout;
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
@BindView(R.id.linearLayout) LinearLayout root;
|
||||||
@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.install_option_card) CardView installOptionCard;
|
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||||
@BindView(R.id.install_button) CardView installButton;
|
@BindView(R.id.install_option_expand) ViewGroup optionExpandLayout;
|
||||||
@BindView(R.id.install_text) TextView installText;
|
@BindView(R.id.arrow) ImageView arrow;
|
||||||
|
|
||||||
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
||||||
|
|
||||||
@BindColor(R.color.red500) int colorBad;
|
@BindColor(R.color.red500) int colorBad;
|
||||||
@@ -85,42 +68,52 @@ public class MagiskFragment extends BaseFragment
|
|||||||
@BindColor(R.color.green500) int colorNeutral;
|
@BindColor(R.color.green500) int colorNeutral;
|
||||||
@BindColor(R.color.blue500) int colorInfo;
|
@BindColor(R.color.blue500) int colorInfo;
|
||||||
|
|
||||||
@OnClick(R.id.safetyNet_title)
|
private UpdateCardHolder magisk;
|
||||||
void safetyNet() {
|
private UpdateCardHolder manager;
|
||||||
Runnable task = () -> {
|
private SafetyNet safetyNet;
|
||||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
private Transition transition;
|
||||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
private ExpandableViewHolder optionExpand;
|
||||||
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 void magiskInstall(View v) {
|
||||||
// Show Manager update first
|
// Show Manager update first
|
||||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
new ManagerInstallDialog((BaseActivity) requireActivity()).show();
|
new ManagerInstallDialog(requireActivity()).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
new MagiskInstallDialog((BaseActivity) requireActivity()).show();
|
||||||
|
}
|
||||||
|
|
||||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
private void managerInstall(View v) {
|
||||||
new MagiskInstallDialog((BaseActivity) getActivity()).show();
|
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)
|
@OnClick(R.id.uninstall_button)
|
||||||
@@ -128,6 +121,13 @@ public class MagiskFragment extends BaseFragment
|
|||||||
new UninstallDialog(requireActivity()).show();
|
new UninstallDialog(requireActivity()).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.arrow)
|
||||||
|
void expandOptions() {
|
||||||
|
if (optionExpand.isExpanded())
|
||||||
|
optionExpand.collapse();
|
||||||
|
else optionExpand.expand();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@@ -136,178 +136,196 @@ public class MagiskFragment extends BaseFragment
|
|||||||
unbinder = new MagiskFragment_ViewBinding(this, v);
|
unbinder = new MagiskFragment_ViewBinding(this, v);
|
||||||
requireActivity().setTitle(R.string.magisk);
|
requireActivity().setTitle(R.string.magisk);
|
||||||
|
|
||||||
expandableContainer.expandLayout = expandLayout;
|
optionExpand = new ArrowExpandedViewHolder(optionExpandLayout, arrow);
|
||||||
setupExpandable();
|
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.setChecked(Config.keepVerity);
|
||||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepVerity = checked);
|
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepVerity = checked);
|
||||||
keepEncChkbox.setChecked(Data.keepEnc);
|
keepEncChkbox.setChecked(Config.keepEnc);
|
||||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepEnc = checked);
|
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Config.keepEnc = checked);
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
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;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
safetyNet.unbinder.unbind();
|
||||||
|
magisk.unbinder.unbind();
|
||||||
|
manager.unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh() {
|
public void onRefresh() {
|
||||||
Data.loadMagiskInfo();
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
TransitionManager.beginDelayedTransition(root, transition);
|
||||||
|
safetyNet.reset();
|
||||||
|
magisk.reset();
|
||||||
|
manager.reset();
|
||||||
|
|
||||||
|
Config.loadMagiskInfo();
|
||||||
updateUI();
|
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());
|
Topic.reset(getSubscribedTopics());
|
||||||
Data.remoteMagiskVersionString = null;
|
Config.remoteMagiskVersionString = null;
|
||||||
Data.remoteMagiskVersionCode = -1;
|
Config.remoteMagiskVersionCode = -1;
|
||||||
collapse();
|
|
||||||
|
|
||||||
shownDialog = false;
|
shownDialog = false;
|
||||||
|
|
||||||
// Trigger state check
|
// Trigger state check
|
||||||
if (Download.checkNetworkStatus(mm)) {
|
if (Networking.checkNetworkStatus(app)) {
|
||||||
CheckUpdates.check();
|
CheckUpdates.check();
|
||||||
} else {
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getSubscribedTopics() {
|
public int[] getSubscribedTopics() {
|
||||||
return new int[] {Topic.SNET_CHECK_DONE, Topic.UPDATE_CHECK_DONE};
|
return new int[] {Topic.UPDATE_CHECK_DONE};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPublish(int topic, Object[] result) {
|
public void onPublish(int topic, Object[] result) {
|
||||||
switch (topic) {
|
updateCheckUI();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private void updateUI() {
|
||||||
((MainActivity) requireActivity()).checkHideSection();
|
((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;
|
int image, color;
|
||||||
|
String status;
|
||||||
if (Data.magiskVersionCode < 0) {
|
if (Config.magiskVersionCode < 0) {
|
||||||
color = colorBad;
|
color = colorBad;
|
||||||
image = R.drawable.ic_cancel;
|
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 {
|
} else {
|
||||||
color = colorOK;
|
color = colorOK;
|
||||||
image = R.drawable.ic_check_circle;
|
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);
|
manager.statusIcon.setColorFilter(colorOK);
|
||||||
magiskStatusIcon.setColorFilter(color);
|
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() {
|
private void updateCheckUI() {
|
||||||
int image, color;
|
int image, color;
|
||||||
|
String status, button = "";
|
||||||
|
|
||||||
safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE);
|
if (Config.remoteMagiskVersionCode < 0) {
|
||||||
|
|
||||||
if (Data.remoteMagiskVersionCode < 0) {
|
|
||||||
color = colorNeutral;
|
color = colorNeutral;
|
||||||
image = R.drawable.ic_help;
|
image = R.drawable.ic_help;
|
||||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
status = getString(R.string.invalid_update_channel);
|
||||||
installButton.setVisibility(View.GONE);
|
|
||||||
} else {
|
} else {
|
||||||
color = colorOK;
|
magisk.latestVersion.setText(getString(R.string.latest_version,
|
||||||
image = R.drawable.ic_check_circle;
|
String.format(Locale.US, "v%s (%d)",
|
||||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + Data.remoteMagiskVersionString));
|
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode)));
|
||||||
installButton.setVisibility(View.VISIBLE);
|
if (Config.remoteMagiskVersionCode > Config.magiskVersionCode) {
|
||||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
color = colorInfo;
|
||||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
image = R.drawable.ic_update;
|
||||||
} else if (Data.magiskVersionCode > 0 && Data.remoteMagiskVersionCode > Data.magiskVersionCode) {
|
status = getString(R.string.magisk_update_title);
|
||||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
button = getString(R.string.update);
|
||||||
} else {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Config.magiskVersionCode > 0) {
|
||||||
magiskUpdateIcon.setImageResource(image);
|
// Only override status if Magisk is installed
|
||||||
magiskUpdateIcon.setColorFilter(color);
|
magisk.statusIcon.setImageResource(image);
|
||||||
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
magisk.statusIcon.setColorFilter(color);
|
||||||
|
magisk.status.setText(status);
|
||||||
magiskUpdateProgress.setVisibility(View.GONE);
|
magisk.install.setText(button);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSafetyNetUI(int response) {
|
if (Config.remoteManagerVersionCode < 0) {
|
||||||
safetyNetProgress.setVisibility(View.GONE);
|
color = colorNeutral;
|
||||||
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
image = R.drawable.ic_help;
|
||||||
if ((response & 0x0F) == 0) {
|
status = getString(R.string.invalid_update_channel);
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
@StringRes int resid;
|
manager.latestVersion.setText(getString(R.string.latest_version,
|
||||||
switch (response) {
|
String.format(Locale.US, "v%s (%d)",
|
||||||
case ISafetyNetHelper.RESPONSE_ERR:
|
Config.remoteManagerVersionString, Config.remoteManagerVersionCode)));
|
||||||
resid = R.string.safetyNet_res_invalid;
|
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
break;
|
color = colorInfo;
|
||||||
case ISafetyNetHelper.CONNECTION_FAIL:
|
image = R.drawable.ic_update;
|
||||||
default:
|
status = getString(R.string.manager_update_title);
|
||||||
resid = R.string.safetyNet_api_error;
|
manager.install.setText(R.string.update);
|
||||||
break;
|
} 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.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||||
import com.topjohnwu.magisk.components.BaseFragment;
|
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.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
SearchView search;
|
|
||||||
|
|
||||||
private ApplicationAdapter appAdapter;
|
|
||||||
|
|
||||||
|
private SearchView search;
|
||||||
|
private ApplicationAdapter adapter;
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
private SearchView.OnQueryTextListener searchListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,22 +42,22 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||||
|
|
||||||
appAdapter = new ApplicationAdapter(requireActivity());
|
adapter = new ApplicationAdapter(requireActivity());
|
||||||
recyclerView.setAdapter(appAdapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
|
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
searchListener = new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
appAdapter.filter(query);
|
adapter.filter(query);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText) {
|
public boolean onQueryTextChange(String newText) {
|
||||||
appAdapter.filter(newText);
|
adapter.filter(newText);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -71,6 +72,21 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||||
search.setOnQueryTextListener(searchListener);
|
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
|
@Override
|
||||||
@@ -81,6 +97,6 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
@Override
|
@Override
|
||||||
public void onPublish(int topic, Object[] result) {
|
public void onPublish(int topic, Object[] result) {
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
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.Manifest;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.google.android.material.snackbar.Snackbar;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.adapters.StringListAdapter;
|
||||||
import com.topjohnwu.magisk.components.BaseFragment;
|
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.magisk.utils.Utils;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
import com.topjohnwu.superuser.internal.NOPList;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
|
||||||
public class MagiskLogFragment extends BaseFragment {
|
public class MagiskLogFragment extends BaseFragment {
|
||||||
|
|
||||||
@BindView(R.id.txtLog) TextView txtLog;
|
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||||
@BindView(R.id.svLog) ScrollView svLog;
|
|
||||||
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -42,7 +39,6 @@ public class MagiskLogFragment extends BaseFragment {
|
|||||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||||
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
txtLog.setTextIsSelectable(true);
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +70,7 @@ public class MagiskLogFragment extends BaseFragment {
|
|||||||
return true;
|
return true;
|
||||||
case R.id.menu_clear:
|
case R.id.menu_clear:
|
||||||
clearLogs();
|
clearLogs();
|
||||||
|
rv.setAdapter(new MagiskLogAdapter(NOPList.getInstance()));
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
@@ -81,14 +78,8 @@ public class MagiskLogFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readLogs() {
|
private void readLogs() {
|
||||||
Shell.su("cat " + Const.MAGISK_LOG + " | tail -n 5000").submit(result -> {
|
Shell.su("tail -n 5000 " + Const.MAGISK_LOG).submit(result -> {
|
||||||
progressBar.setVisibility(View.GONE);
|
rv.setAdapter(new MagiskLogAdapter(result.getOut()));
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +98,60 @@ public class MagiskLogFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||||
.submit(result ->
|
.submit(result ->
|
||||||
SnackbarMaker.make(txtLog, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
SnackbarMaker.make(rv, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearLogs() {
|
private void clearLogs() {
|
||||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||||
txtLog.setText(R.string.log_is_empty);
|
SnackbarMaker.make(rv, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||||
SnackbarMaker.make(txtLog, 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.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.ClassMap;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.FlashActivity;
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
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) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
// Get the URI of the selected file
|
// Get the URI of the selected file
|
||||||
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);
|
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.reboot:
|
case R.id.reboot:
|
||||||
Shell.su("/system/bin/reboot").submit();
|
Utils.reboot();
|
||||||
return true;
|
return true;
|
||||||
case R.id.reboot_recovery:
|
case R.id.reboot_recovery:
|
||||||
Shell.su("/system/bin/reboot recovery").submit();
|
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();
|
Shell.su("/system/bin/reboot bootloader").submit();
|
||||||
return true;
|
return true;
|
||||||
case R.id.reboot_download:
|
case R.id.reboot_download:
|
||||||
Shell.su("/system/bin/reboot upgrade").submit();
|
Shell.su("/system/bin/reboot download").submit();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
|
||||||
import com.topjohnwu.magisk.components.BaseFragment;
|
import com.topjohnwu.magisk.components.BaseFragment;
|
||||||
import com.topjohnwu.magisk.container.Module;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.tasks.UpdateRepos;
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -51,11 +50,7 @@ public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
|||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
recyclerView.setVisibility(View.GONE);
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
mSwipeRefreshLayout.setOnRefreshListener(() -> new UpdateRepos().exec(true));
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
emptyRv.setVisibility(View.GONE);
|
|
||||||
new UpdateRepos().exec(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
requireActivity().setTitle(R.string.downloads);
|
requireActivity().setTitle(R.string.downloads);
|
||||||
|
|
||||||
@@ -69,17 +64,21 @@ public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPublish(int topic, Object[] result) {
|
public void onPublish(int topic, Object[] result) {
|
||||||
if (topic == Topic.MODULE_LOAD_DONE) {
|
switch (topic) {
|
||||||
adapter = new ReposAdapter(mm.repoDB, (Map<String, Module>) result[0]);
|
case Topic.MODULE_LOAD_DONE:
|
||||||
mm.repoDB.registerAdapter(adapter);
|
adapter = new ReposAdapter(app.repoDB, (Map<String, Module>) result[0]);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
break;
|
||||||
emptyRv.setVisibility(View.GONE);
|
case Topic.REPO_LOAD_DONE:
|
||||||
|
if (adapter != null)
|
||||||
|
adapter.notifyDBChanged();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (Topic.isPublished(getSubscribedTopics())) {
|
if (Topic.isPublished(this)) {
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
boolean empty = adapter.getItemCount() == 0;
|
||||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
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) {
|
if (item.getItemId() == R.id.repo_sort) {
|
||||||
new AlertDialog.Builder(getActivity())
|
new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(R.string.sorting_order)
|
.setTitle(R.string.sorting_order)
|
||||||
.setSingleChoiceItems(R.array.sorting_orders, Data.repoOrder, (d, which) -> {
|
.setSingleChoiceItems(R.array.sorting_orders,
|
||||||
Data.repoOrder = which;
|
Config.get(Config.Key.REPO_ORDER), (d, which) -> {
|
||||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, Data.repoOrder).apply();
|
Config.set(Config.Key.REPO_ORDER, which);
|
||||||
adapter.notifyDBChanged();
|
adapter.notifyDBChanged();
|
||||||
d.dismiss();
|
d.dismiss();
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
mm.repoDB.unregisterAdapter();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
package com.topjohnwu.magisk.fragments;
|
package com.topjohnwu.magisk.fragments;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.BuildConfig;
|
import com.topjohnwu.magisk.BuildConfig;
|
||||||
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.Const;
|
import com.topjohnwu.magisk.Const;
|
||||||
import com.topjohnwu.magisk.Data;
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
import com.topjohnwu.magisk.components.BasePreferenceFragment;
|
||||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog;
|
||||||
import com.topjohnwu.magisk.utils.DlInstallManager;
|
import com.topjohnwu.magisk.tasks.CheckUpdates;
|
||||||
import com.topjohnwu.magisk.utils.Download;
|
import com.topjohnwu.magisk.utils.AppUtils;
|
||||||
|
import com.topjohnwu.magisk.utils.DownloadApp;
|
||||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||||
|
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.net.Networking;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -32,43 +32,23 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceCategory;
|
import androidx.preference.PreferenceCategory;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
|
||||||
import androidx.preference.PreferenceGroupAdapter;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
import androidx.preference.PreferenceViewHolder;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
import androidx.preference.SwitchPreference;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat
|
public class SettingsFragment extends BasePreferenceFragment implements Topic.Subscriber {
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
|
||||||
Topic.Subscriber, Topic.AutoSubscriber {
|
|
||||||
|
|
||||||
private MagiskManager mm;
|
|
||||||
|
|
||||||
private ListPreference updateChannel, autoRes, suNotification,
|
private ListPreference updateChannel, autoRes, suNotification,
|
||||||
requestTimeout, rootConfig, multiuserConfig, nsConfig;
|
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
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
mm = Data.MM();
|
|
||||||
prefsSync();
|
|
||||||
|
|
||||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
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();
|
PreferenceScreen prefScreen = getPreferenceScreen();
|
||||||
|
|
||||||
@@ -82,12 +62,12 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
});
|
});
|
||||||
Preference restoreManager = findPreference("restore");
|
Preference restoreManager = findPreference("restore");
|
||||||
restoreManager.setOnPreferenceClickListener(pref -> {
|
restoreManager.setOnPreferenceClickListener(pref -> {
|
||||||
DlInstallManager.restore();
|
DownloadApp.restore();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
findPreference("clear").setOnPreferenceClickListener(pref -> {
|
||||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
app.prefs.edit().remove(Config.Key.ETAG_KEY).apply();
|
||||||
mm.repoDB.clearRepo();
|
app.repoDB.clearRepo();
|
||||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -98,33 +78,32 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
updateChannel = (ListPreference) findPreference(Config.Key.UPDATE_CHANNEL);
|
||||||
rootConfig = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
rootConfig = (ListPreference) findPreference(Config.Key.ROOT_ACCESS);
|
||||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
autoRes = (ListPreference) findPreference(Config.Key.SU_AUTO_RESPONSE);
|
||||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
requestTimeout = (ListPreference) findPreference(Config.Key.SU_REQUEST_TIMEOUT);
|
||||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
suNotification = (ListPreference) findPreference(Config.Key.SU_NOTIFICATION);
|
||||||
multiuserConfig = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
multiuserConfig = (ListPreference) findPreference(Config.Key.SU_MULTIUSER_MODE);
|
||||||
nsConfig = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
nsConfig = (ListPreference) findPreference(Config.Key.SU_MNT_NS);
|
||||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
SwitchPreferenceCompat reauth = (SwitchPreferenceCompat) findPreference(Config.Key.SU_REAUTH);
|
||||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
SwitchPreferenceCompat fingerprint = (SwitchPreferenceCompat) findPreference(Config.Key.SU_FINGERPRINT);
|
||||||
|
|
||||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||||
String prev =String.valueOf(Data.updateChannel);
|
int prev = Config.get(Config.Key.UPDATE_CHANNEL);
|
||||||
int channel = Integer.parseInt((String) o);
|
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);
|
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||||
EditText url = v.findViewById(R.id.custom_url);
|
EditText url = v.findViewById(R.id.custom_url);
|
||||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
url.setText(app.prefs.getString(Config.Key.CUSTOM_CHANNEL, ""));
|
||||||
new AlertDialog.Builder(requireActivity())
|
new AlertDialog.Builder(requireActivity())
|
||||||
.setTitle(R.string.settings_update_custom)
|
.setTitle(R.string.settings_update_custom)
|
||||||
.setView(v)
|
.setView(v)
|
||||||
.setPositiveButton(R.string.ok, (d, i) ->
|
.setPositiveButton(R.string.ok, (d, i) ->
|
||||||
mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
Config.set(Config.Key.CUSTOM_CHANNEL, url.getText().toString()))
|
||||||
url.getText().toString()).apply())
|
|
||||||
.setNegativeButton(R.string.close, (d, i) ->
|
.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 ->
|
.setOnCancelListener(d ->
|
||||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
Config.set(Config.Key.UPDATE_CHANNEL, prev))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -132,6 +111,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
setSummary();
|
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
|
// Disable dangerous settings in secondary user
|
||||||
if (Const.USER_ID > 0) {
|
if (Const.USER_ID > 0) {
|
||||||
suCategory.removePreference(multiuserConfig);
|
suCategory.removePreference(multiuserConfig);
|
||||||
@@ -152,10 +135,10 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
if (Shell.rootAccess() && Const.USER_ID == 0) {
|
||||||
if (mm.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
if (app.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
|
||||||
generalCatagory.removePreference(restoreManager);
|
generalCatagory.removePreference(restoreManager);
|
||||||
} else {
|
} else {
|
||||||
if (!Download.checkNetworkStatus(mm))
|
if (!Networking.checkNetworkStatus(app))
|
||||||
generalCatagory.removePreference(restoreManager);
|
generalCatagory.removePreference(restoreManager);
|
||||||
generalCatagory.removePreference(hideManager);
|
generalCatagory.removePreference(hideManager);
|
||||||
}
|
}
|
||||||
@@ -182,51 +165,25 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
int i = 1;
|
int i = 1;
|
||||||
for (Locale locale : LocaleManager.locales) {
|
for (Locale locale : LocaleManager.locales) {
|
||||||
entries[i] = locale.getDisplayName(locale);
|
entries[i] = locale.getDisplayName(locale);
|
||||||
entryValues[i++] = locale.toLanguageTag();
|
entryValues[i++] = LocaleManager.toLanguageTag(locale);
|
||||||
}
|
}
|
||||||
lp.setEntries(entries);
|
lp.setEntries(entries);
|
||||||
lp.setEntryValues(entryValues);
|
lp.setEntryValues(entryValues);
|
||||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
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
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Const.Key.ROOT_ACCESS:
|
case Config.Key.ROOT_ACCESS:
|
||||||
case Const.Key.SU_MULTIUSER_MODE:
|
case Config.Key.SU_MULTIUSER_MODE:
|
||||||
case Const.Key.SU_MNT_NS:
|
case Config.Key.SU_MNT_NS:
|
||||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
app.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||||
break;
|
break;
|
||||||
}
|
case Config.Key.DARK_THEME:
|
||||||
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:
|
|
||||||
requireActivity().recreate();
|
requireActivity().recreate();
|
||||||
break;
|
break;
|
||||||
case Const.Key.COREONLY:
|
case Config.Key.COREONLY:
|
||||||
if (prefs.getBoolean(key, false)) {
|
if (prefs.getBoolean(key, false)) {
|
||||||
try {
|
try {
|
||||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||||
@@ -236,104 +193,101 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
}
|
}
|
||||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||||
break;
|
break;
|
||||||
case Const.Key.MAGISKHIDE:
|
case Config.Key.MAGISKHIDE:
|
||||||
if (prefs.getBoolean(key, false)) {
|
if (prefs.getBoolean(key, false)) {
|
||||||
Shell.su("magiskhide --enable").submit();
|
Shell.su("magiskhide --enable").submit();
|
||||||
} else {
|
} else {
|
||||||
Shell.su("magiskhide --disable").submit();
|
Shell.su("magiskhide --disable").submit();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Const.Key.LOCALE:
|
case Config.Key.LOCALE:
|
||||||
LocaleManager.setLocale(mm);
|
LocaleManager.setLocale(app);
|
||||||
requireActivity().recreate();
|
requireActivity().recreate();
|
||||||
break;
|
break;
|
||||||
case Const.Key.UPDATE_CHANNEL:
|
case Config.Key.UPDATE_CHANNEL:
|
||||||
case Const.Key.CUSTOM_CHANNEL:
|
case Config.Key.CUSTOM_CHANNEL:
|
||||||
CheckUpdates.check();
|
CheckUpdates.check();
|
||||||
break;
|
break;
|
||||||
case Const.Key.CHECK_UPDATES:
|
case Config.Key.CHECK_UPDATES:
|
||||||
Utils.setupUpdateCheck();
|
AppUtils.scheduleUpdateCheck();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Data.loadConfig();
|
setSummary(key);
|
||||||
setSummary();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceTreeClick(Preference preference) {
|
public boolean onPreferenceTreeClick(Preference preference) {
|
||||||
String key = preference.getKey();
|
String key = preference.getKey();
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case Const.Key.SU_FINGERPRINT:
|
case Config.Key.SU_FINGERPRINT:
|
||||||
boolean checked = ((SwitchPreference) preference).isChecked();
|
boolean checked = ((SwitchPreferenceCompat) preference).isChecked();
|
||||||
((SwitchPreference) preference).setChecked(!checked);
|
((SwitchPreferenceCompat) preference).setChecked(!checked);
|
||||||
FingerprintHelper.showAuthDialog(requireActivity(), () -> {
|
new FingerprintAuthDialog(requireActivity(), () -> {
|
||||||
((SwitchPreference) preference).setChecked(checked);
|
((SwitchPreferenceCompat) preference).setChecked(checked);
|
||||||
mm.mDB.setSettings(key, checked ? 1 : 0);
|
Config.set(key, checked);
|
||||||
});
|
}).show();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
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() {
|
private void setSummary() {
|
||||||
updateChannel.setSummary(getResources()
|
setSummary(Config.Key.UPDATE_CHANNEL);
|
||||||
.getStringArray(R.array.update_channel)[Data.updateChannel]);
|
setSummary(Config.Key.ROOT_ACCESS);
|
||||||
rootConfig.setSummary(getResources()
|
setSummary(Config.Key.SU_AUTO_RESPONSE);
|
||||||
.getStringArray(R.array.su_access)[rootState]);
|
setSummary(Config.Key.SU_NOTIFICATION);
|
||||||
autoRes.setSummary(getResources()
|
setSummary(Config.Key.SU_REQUEST_TIMEOUT);
|
||||||
.getStringArray(R.array.auto_response)[Data.suResponseType]);
|
setSummary(Config.Key.SU_MULTIUSER_MODE);
|
||||||
suNotification.setSummary(getResources()
|
setSummary(Config.Key.SU_MNT_NS);
|
||||||
.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]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPublish(int topic, Object[] result) {
|
public void onPublish(int topic, Object[] result) {
|
||||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
setLocalePreference((ListPreference) findPreference(Config.Key.LOCALE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getSubscribedTopics() {
|
public int[] getSubscribedTopics() {
|
||||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
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