mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 12:40:56 +00:00
Compare commits
240 Commits
manager-v5
...
manager-v6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c2b016370b | ||
![]() |
fc791b4371 | ||
![]() |
f76bb009f4 | ||
![]() |
8a1292b295 | ||
![]() |
d7d80d3fc1 | ||
![]() |
41b01003fd | ||
![]() |
6557070ae1 | ||
![]() |
e7e580e177 | ||
![]() |
dd9ddd2019 | ||
![]() |
74aae523ba | ||
![]() |
48c40f9516 | ||
![]() |
e0e7674715 | ||
![]() |
e1a65276b9 | ||
![]() |
469adc85ad | ||
![]() |
e1b181ca4e | ||
![]() |
a4f0fbf8b7 | ||
![]() |
190cdaddf8 | ||
![]() |
5c4ba13839 | ||
![]() |
e62630cf3e | ||
![]() |
36fe7846c0 | ||
![]() |
8d150dd67a | ||
![]() |
506df00d81 | ||
![]() |
a9121fa28f | ||
![]() |
d5a56d9e85 | ||
![]() |
acf7c0c665 | ||
![]() |
619d48c97a | ||
![]() |
2cb198c38c | ||
![]() |
e8e39e0f3c | ||
![]() |
37860181d4 | ||
![]() |
d119dd9a0c | ||
![]() |
09ef19f7ec | ||
![]() |
6a06c92fa6 | ||
![]() |
58ae596b0f | ||
![]() |
f1ca21678d | ||
![]() |
d7eeef2c8a | ||
![]() |
4f626897f2 | ||
![]() |
b127e01845 | ||
![]() |
2118beeb23 | ||
![]() |
5020cd1bbf | ||
![]() |
cce636224c | ||
![]() |
60b3b8ddce | ||
![]() |
41446ec9ba | ||
![]() |
df8b047bca | ||
![]() |
12ced52012 | ||
![]() |
1d53335ae5 | ||
![]() |
971a50d290 | ||
![]() |
36dd9106a8 | ||
![]() |
0a4ee3ffc7 | ||
![]() |
cfe32f1a70 | ||
![]() |
d877f5d5c6 | ||
![]() |
0ab6ffefb4 | ||
![]() |
a292a1d23a | ||
![]() |
3f87f6aee3 | ||
![]() |
04bcd145d3 | ||
![]() |
244e811291 | ||
![]() |
ac7467fb59 | ||
![]() |
2c0436216f | ||
![]() |
017fbf267b | ||
![]() |
e6afbf2ec0 | ||
![]() |
906b4aad9e | ||
![]() |
4cf8d41f6a | ||
![]() |
47c860142e | ||
![]() |
2fba3f213b | ||
![]() |
af7c6f9fce | ||
![]() |
78534deab6 | ||
![]() |
6710314832 | ||
![]() |
0cd4fa6fa0 | ||
![]() |
065949496e | ||
![]() |
39c82576ae | ||
![]() |
37221a508d | ||
![]() |
6b43a32a10 | ||
![]() |
d7cd1ff142 | ||
![]() |
659d947863 | ||
![]() |
39be7a6288 | ||
![]() |
8ac976c579 | ||
![]() |
70fd432c57 | ||
![]() |
00135f2f49 | ||
![]() |
9b944bc29c | ||
![]() |
d520b3d2a0 | ||
![]() |
6f41d9855b | ||
![]() |
2d7c1da741 | ||
![]() |
c0f45b6b1e | ||
![]() |
7a0025673c | ||
![]() |
ad7ec79903 | ||
![]() |
0543239cca | ||
![]() |
ff3dad2457 | ||
![]() |
298d5e197b | ||
![]() |
d73c0a998d | ||
![]() |
1b79a3ddbf | ||
![]() |
a8478ace18 | ||
![]() |
72cf5f3f9f | ||
![]() |
6f9d493a18 | ||
![]() |
08f7d5ebff | ||
![]() |
1fe3675403 | ||
![]() |
a0f956d2c1 | ||
![]() |
1560f91b4a | ||
![]() |
c20f362594 | ||
![]() |
7ae8c26e50 | ||
![]() |
adfffe6121 | ||
![]() |
64601baa76 | ||
![]() |
aa374b51f1 | ||
![]() |
5c483745ff | ||
![]() |
0c247110a0 | ||
![]() |
1643638a78 | ||
![]() |
4ace228fc2 | ||
![]() |
25aa86a0dc | ||
![]() |
70d3b24338 | ||
![]() |
8664e9d19b | ||
![]() |
50d9877446 | ||
![]() |
fe06352089 | ||
![]() |
7b599419b5 | ||
![]() |
491adf072e | ||
![]() |
f6aae2b048 | ||
![]() |
d2d5c94633 | ||
![]() |
10581f9ef2 | ||
![]() |
c7e0e1c038 | ||
![]() |
a914d701eb | ||
![]() |
0f9dee6e9c | ||
![]() |
aa383e2190 | ||
![]() |
9bbfcf326c | ||
![]() |
3948e67c8f | ||
![]() |
d56e1b2cc5 | ||
![]() |
bfac1f1bc2 | ||
![]() |
d4a956c355 | ||
![]() |
6c71fefa58 | ||
![]() |
ad3003c00a | ||
![]() |
0ad5dcb258 | ||
![]() |
d790309b02 | ||
![]() |
1072faf309 | ||
![]() |
d2c196896d | ||
![]() |
e42b608444 | ||
![]() |
89a501a3af | ||
![]() |
c19b78180c | ||
![]() |
c0b750a09a | ||
![]() |
c967e618a1 | ||
![]() |
59f78d7dfc | ||
![]() |
d8405f0d05 | ||
![]() |
0f34f0033c | ||
![]() |
190646d50c | ||
![]() |
a46c6252c6 | ||
![]() |
5c1886c8f5 | ||
![]() |
afcb3d8f34 | ||
![]() |
9fbffafdbf | ||
![]() |
075f0458f7 | ||
![]() |
d4568aa0a7 | ||
![]() |
97588408a2 | ||
![]() |
1def9b301b | ||
![]() |
5bac442b18 | ||
![]() |
6add682705 | ||
![]() |
8b50d84a05 | ||
![]() |
d3858b81e2 | ||
![]() |
bdff9769be | ||
![]() |
c61df75e5e | ||
![]() |
a74bf2cc27 | ||
![]() |
ada0f93686 | ||
![]() |
ff36f2ba17 | ||
![]() |
5164cfd399 | ||
![]() |
5fa021503e | ||
![]() |
7b5d79d313 | ||
![]() |
3e3f38500d | ||
![]() |
5109b9abfd | ||
![]() |
7fb4777c1c | ||
![]() |
c38533e0f8 | ||
![]() |
51ba99d09e | ||
![]() |
9159f86a9e | ||
![]() |
e139f4fc13 | ||
![]() |
2fbfeacb87 | ||
![]() |
ebb7a9fcda | ||
![]() |
9e72317302 | ||
![]() |
d764c20c08 | ||
![]() |
9c17b8a098 | ||
![]() |
3084873154 | ||
![]() |
32809e56d0 | ||
![]() |
9f05b182a2 | ||
![]() |
525484e834 | ||
![]() |
65a4e69cae | ||
![]() |
e973f8bab9 | ||
![]() |
92466671ff | ||
![]() |
6d61106070 | ||
![]() |
ac13749fb8 | ||
![]() |
7ec1a9a316 | ||
![]() |
cf17e21ad3 | ||
![]() |
0e0240c4ab | ||
![]() |
d1b290b91a | ||
![]() |
a63696836c | ||
![]() |
46aad00f16 | ||
![]() |
252afe8932 | ||
![]() |
9dd467a613 | ||
![]() |
4c14df67cc | ||
![]() |
20e0fe3ba1 | ||
![]() |
6a005135f2 | ||
![]() |
82e8375957 | ||
![]() |
bb25edc09e | ||
![]() |
169c0fe4af | ||
![]() |
cd6918e6eb | ||
![]() |
5be035fd44 | ||
![]() |
f1edc8443c | ||
![]() |
d9564bd04c | ||
![]() |
35f1c396f2 | ||
![]() |
6acb950990 | ||
![]() |
27e0d1641a | ||
![]() |
9ac71ff8af | ||
![]() |
075737a4ec | ||
![]() |
6d0e4a6a5e | ||
![]() |
a2544768a0 | ||
![]() |
8574a14ed2 | ||
![]() |
e90c555c18 | ||
![]() |
863b9a410f | ||
![]() |
23c7bbc7d5 | ||
![]() |
f900189f90 | ||
![]() |
7c74be2790 | ||
![]() |
70dd2d4829 | ||
![]() |
914b7ee056 | ||
![]() |
e39f83edbf | ||
![]() |
52fe0c6abb | ||
![]() |
5cb3e5937f | ||
![]() |
e0cd224831 | ||
![]() |
de225ac64a | ||
![]() |
5807808a10 | ||
![]() |
362877d18f | ||
![]() |
88b8dd0149 | ||
![]() |
1552f32e09 | ||
![]() |
50b73a6720 | ||
![]() |
53e51f1735 | ||
![]() |
40b63bfebe | ||
![]() |
89861eceef | ||
![]() |
b8eaff66fa | ||
![]() |
a747fdd27d | ||
![]() |
27851bdefa | ||
![]() |
3fdeb40ddf | ||
![]() |
546c7cebd3 | ||
![]() |
473902f5f4 | ||
![]() |
41c0721159 | ||
![]() |
413d4badfd | ||
![]() |
c5d67ebf72 | ||
![]() |
91818cfa1a | ||
![]() |
6263d684d9 | ||
![]() |
07140d33a7 | ||
![]() |
4ffc388491 | ||
![]() |
0ef026c610 |
65
README.MD
65
README.MD
@@ -1,7 +1,15 @@
|
||||
# Magisk
|
||||
[Downloads](https://github.com/topjohnwu/Magisk/releases) | [Documentation](https://topjohnwu.github.io/Magisk/) | [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
## Introduction
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 5.0 (API 21). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
|
||||
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).
|
||||
|
||||
## Bug Reports
|
||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
|
||||
|
||||
## Building Environment Requirements
|
||||
|
||||
1. Python 3.5+: run `build.py` script
|
||||
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
|
||||
@@ -9,18 +17,18 @@
|
||||
5. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes
|
||||
|
||||
## Building Notes and Instructions
|
||||
1. Building is tested on macOS, Ubuntu, and Windows 10 using the latest stable NDK and NDK r10e. Officially released binaries were built with NDK r10e.
|
||||
2. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
|
||||
4. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `--release` flag), you need a Java Keystore file `release-key.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
5. The SafetyNet extension pack requires the full Magisk Manager as a `compileOnly` dependency. Build the **release** APK, convert it back to Java `.class` files (I use [dex2jar](https://github.com/pxb1988/dex2jar)), and place the converted JAR under `snet/libs` before compiling.
|
||||
1. Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
2. Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling.
|
||||
3. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
4. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
|
||||
5. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Magisk, including all git submodules are free software:
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
@@ -31,44 +39,3 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
**MagiskManager** (`app`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* All contributors and translators on Github
|
||||
|
||||
**MagiskSU** (`native/jni/su`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
|
||||
* Copyright 2013, Koushik Dutta (@koush)
|
||||
* Copyright 2010, Adam Shanks (@ChainsDD)
|
||||
* Copyright 2008, Zinx Verituse (@zinxv)
|
||||
|
||||
**MagiskPolicy** (`native/jni/magiskpolicy`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2015, Pierre-Hugues Husson (phh@phh.me)
|
||||
* Copyright 2015, Joshua Brindle (@joshua_brindle)
|
||||
|
||||
**MagiskHide** (`native/jni/magiskhide`)
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
* Copyright 2016, Pierre-Hugues Husson (phh@phh.me)
|
||||
|
||||
**resetprop** (`native/jni/resetprop`)
|
||||
|
||||
* Copyright 2016-2018 John Wu (@topjohnwu)
|
||||
* Copyright 2016 nkk71 (nkk71x@gmail.com)
|
||||
|
||||
**External Dependencies** (`native/jni/external`)
|
||||
|
||||
* Makefile for busybox, generated by [ndk-busybox-kitchen](https://github.com/topjohnwu/ndk-busybox-kitchen)
|
||||
* Each dependencies has its own license/copyright information in each subdirectory.
|
||||
All of them are either GPL or GPL compatible.
|
||||
|
||||
**Others Not Mentioned**
|
||||
|
||||
* Copyright 2016-2018, John Wu (@topjohnwu)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Magisk Manager
|
||||
This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
This repo is no longer an independent component. It is merged into the [Magisk Project](https://github.com/topjohnwu/Magisk).
|
||||
|
||||
# Translations
|
||||
The default (English) string resources are scattered in these files: `src/full/res/values/strings.xml`, `src/main/res/values/strings.xml`, `src/stub/res/values/strings.xml`.
|
||||
Place the translated XMLs in the corresponding folder to the locale.
|
||||
Translations are highly appreciated via pull requests here on Github.
|
||||
The default (English) strings are mainly in `src/full/res/values/strings.xml`; some are scattered in `src/main/res/values/strings.xml` and `src/stub/res/values/strings.xml`.
|
||||
Translations are highly appreciated via pull requests here on Github.
|
||||
Place translated XMLs in the corresponding locale folder.
|
||||
|
@@ -1,5 +1,9 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
def configProps = new Properties()
|
||||
def configPath = project.hasProperty('configPath') ? project.configPath : rootProject.file('config.prop')
|
||||
configProps.load(new FileInputStream(configPath))
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
@@ -8,18 +12,28 @@ android {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion rootProject.ext.compileSdkVersion
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
storeFile rootProject.file('release-key.jks')
|
||||
storePassword configProps['keyStorePass']
|
||||
keyAlias configProps['keyAlias']
|
||||
keyPassword configProps['keyPass']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
// If keystore exists, sign the APK with custom signature
|
||||
if (signingConfigs.config.storeFile.exists())
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +41,13 @@ android {
|
||||
|
||||
productFlavors {
|
||||
full {
|
||||
versionCode 129
|
||||
versionName "5.8.3"
|
||||
versionName configProps['appVersion']
|
||||
versionCode configProps['appVersionCode'] as Integer
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
argument('butterknife.debuggable', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
stub {
|
||||
versionCode 1
|
||||
@@ -52,14 +71,20 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
fullImplementation project(':utils')
|
||||
implementation "com.android.support:support-core-utils:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:preference-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:cardview-v7:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation "com.android.support:design:${rootProject.ext.supportLibVersion}"
|
||||
fullImplementation 'com.github.topjohnwu:libsu:1.3.0'
|
||||
implementation "androidx.core:core:${rootProject.ext.androidXVersion}"
|
||||
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.0.2'
|
||||
fullImplementation 'com.atlassian.commonmark:commonmark:0.11.0'
|
||||
fullImplementation 'org.kamranzafar:jtar:2.3'
|
||||
fullImplementation 'com.jakewharton:butterknife:8.8.1'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||
|
||||
def butterKnifeVersion = '9.0.0-rc1'
|
||||
if (properties.containsKey('android.injected.invoked.from.ide')) {
|
||||
fullImplementation "com.jakewharton:butterknife-reflect:${butterKnifeVersion}"
|
||||
} else {
|
||||
fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
|
||||
fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
|
||||
}
|
||||
}
|
||||
|
16
app/proguard-rules.pro
vendored
16
app/proguard-rules.pro
vendored
@@ -16,14 +16,20 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Keep all names, we are open source anyway :)
|
||||
-keepnames class ** { *; }
|
||||
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; }
|
||||
-keep class org.bouncycastle.jcajce.provider.digest.SHA1** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
||||
# Snet extention
|
||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
|
||||
|
||||
# Strip logging
|
||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
|
||||
public *** debug(...);
|
||||
}
|
||||
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-allowaccessmodification
|
||||
|
@@ -1,21 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.topjohnwu.magisk">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
android:theme="@style/AppTheme">
|
||||
android:name="a.q"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Activities -->
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name="a.b"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:name="a.c"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
@@ -25,57 +30,75 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:name="a.d"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
android:name="a.e"
|
||||
android:theme="@style/AppTheme.StatusBar"/>
|
||||
<activity
|
||||
android:name=".FlashActivity"
|
||||
android:name="a.f"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:screenOrientation="nosensor"
|
||||
android:theme="@style/AppTheme.StatusBar" />
|
||||
<activity
|
||||
android:name=".NoUIActivity"
|
||||
android:name="a.g"
|
||||
android:theme="@style/AppTheme.Translucent" />
|
||||
|
||||
<!-- Superuser -->
|
||||
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:name="a.p"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
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 android:name=".receivers.BootReceiver">
|
||||
|
||||
<!-- Receiver -->
|
||||
|
||||
<receiver android:name="a.h">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".receivers.PackageReceiver">
|
||||
<receiver android:name="a.i">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".receivers.ManagerUpdate" />
|
||||
<receiver android:name=".receivers.RebootReceiver" />
|
||||
<receiver android:name=".receivers.ShortcutReceiver">
|
||||
<receiver android:name="a.j" />
|
||||
<receiver android:name="a.k" />
|
||||
<receiver android:name="a.l">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.OnBootIntentService" />
|
||||
<!-- Service -->
|
||||
|
||||
<service
|
||||
android:name=".services.UpdateCheckService"
|
||||
android:name="a.m"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name="a.n"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="7095000" />
|
||||
android:value="12451000" />
|
||||
|
||||
</application>
|
||||
|
||||
|
10
app/src/full/java/a/a.java
Normal file
10
app/src/full/java/a/a.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.utils.BootSigner;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public class a extends BootSigner {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/b.java
Normal file
7
app/src/full/java/a/b.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
|
||||
public class b extends MainActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/c.java
Normal file
7
app/src/full/java/a/c.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
|
||||
public class c extends SplashActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/d.java
Normal file
7
app/src/full/java/a/d.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.AboutActivity;
|
||||
|
||||
public class d extends AboutActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/e.java
Normal file
7
app/src/full/java/a/e.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.DonationActivity;
|
||||
|
||||
public class e extends DonationActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/f.java
Normal file
7
app/src/full/java/a/f.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
|
||||
public class f extends FlashActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/h.java
Normal file
7
app/src/full/java/a/h.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.BootReceiver;
|
||||
|
||||
public class h extends BootReceiver {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/i.java
Normal file
7
app/src/full/java/a/i.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.PackageReceiver;
|
||||
|
||||
public class i extends PackageReceiver {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/j.java
Normal file
7
app/src/full/java/a/j.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
|
||||
public class j extends ManagerUpdate {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/k.java
Normal file
7
app/src/full/java/a/k.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
public class k extends RebootReceiver {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/l.java
Normal file
7
app/src/full/java/a/l.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
|
||||
public class l extends ShortcutReceiver {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/m.java
Normal file
7
app/src/full/java/a/m.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
|
||||
public class m extends OnBootService {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/n.java
Normal file
7
app/src/full/java/a/n.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
|
||||
public class n extends UpdateCheckService {
|
||||
/* stub */
|
||||
}
|
22
app/src/full/java/a/o.java
Normal file
22
app/src/full/java/a/o.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package a;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||
|
||||
public class o extends AboutCardRow {
|
||||
/* stub */
|
||||
|
||||
public o(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public o(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public o(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
}
|
7
app/src/full/java/a/p.java
Normal file
7
app/src/full/java/a/p.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
|
||||
public class p extends SuRequestActivity {
|
||||
/* stub */
|
||||
}
|
7
app/src/full/java/a/q.java
Normal file
7
app/src/full/java/a/q.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package a;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
public class q extends MagiskManager {
|
||||
/* stub */
|
||||
}
|
@@ -1,25 +1,23 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
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.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
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;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AboutActivity extends Activity {
|
||||
public class AboutActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
|
||||
@@ -27,7 +25,7 @@ public class AboutActivity extends Activity {
|
||||
@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.donation) AboutCardRow donation;
|
||||
@BindView(R.id.follow_twitter) AboutCardRow twitter;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
@@ -38,7 +36,7 @@ public class AboutActivity extends Activity {
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
ButterKnife.bind(this);
|
||||
new AboutActivity_ViewBinding(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
@@ -52,7 +50,6 @@ public class AboutActivity extends Activity {
|
||||
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d) (%s)",
|
||||
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, getPackageName()));
|
||||
|
||||
appChangelog.removeSummary();
|
||||
appChangelog.setOnClickListener(v -> {
|
||||
new MarkDownWindow(this, getString(R.string.app_changelog),
|
||||
getResources().openRawResource(R.raw.changelog)).exec();
|
||||
@@ -65,14 +62,9 @@ public class AboutActivity extends Activity {
|
||||
appTranslators.setSummary(translators);
|
||||
}
|
||||
|
||||
appSourceCode.removeSummary();
|
||||
appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.SOURCE_CODE_URL))));
|
||||
|
||||
supportThread.removeSummary();
|
||||
supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.XDA_THREAD))));
|
||||
|
||||
donation.removeSummary();
|
||||
donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Const.Url.DONATION_URL))));
|
||||
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();
|
||||
}
|
||||
|
@@ -1,11 +1,7 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.Process;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -34,47 +30,39 @@ public class Const {
|
||||
public static final String BUSYBOX_PATH = "/sbin/.core/busybox";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
public static final File EXTERNAL_PATH = new File(Environment.getExternalStorageDirectory(), "MagiskManager");
|
||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
||||
|
||||
// Versions
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int SNET_VER = 8;
|
||||
|
||||
public static int MIN_MODULE_VER() {
|
||||
return MagiskManager.get().magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
|
||||
return Data.magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400;
|
||||
}
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
"android",
|
||||
MagiskManager.get().getPackageName(),
|
||||
Data.MM().getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
public static final int UNIFIED = 1300;
|
||||
public static final int FBE_AWARE = 1410;
|
||||
public static final int RESETPROP_PERSIST = 1436;
|
||||
public static final int MANAGER_HIDE = 1440;
|
||||
public static final int DTBO_SUPPORT = 1446;
|
||||
public static final int HIDDEN_PATH = 1460;
|
||||
public static final int REMOVE_LEGACY_LINK = 1630;
|
||||
public static final int SEPOL_REFACTOR = 1640;
|
||||
public static final int FIX_ENV = 1650;
|
||||
public static final int DBVER_SIX = 17000;
|
||||
}
|
||||
|
||||
public static class ID {
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
public static final int FETCH_ZIP = 2;
|
||||
public static final int SELECT_BOOT = 3;
|
||||
public static final int ONBOOT_SERVICE_ID = 6;
|
||||
|
||||
// notifications
|
||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
||||
public static final int ONBOOT_NOTIFICATION_ID = 6;
|
||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
||||
public static final String NOTIFICATION_CHANNEL = "magisk_notification";
|
||||
}
|
||||
@@ -82,11 +70,12 @@ public class Const {
|
||||
public static class Url {
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json";
|
||||
public static final String SNET_URL = "https://github.com/topjohnwu/magisk_files/raw/727aa3a8642bf5f0982e5ea89b3f818bd783d5a2/snet.apk";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
public static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
|
||||
public static final String PAYPAL_URL = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=CC7FZ7526MNGG";
|
||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
||||
}
|
||||
@@ -108,7 +97,6 @@ public class Const {
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_FILENAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String INTENT_PERM = "perm_dialog";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
|
||||
@@ -147,16 +135,13 @@ public class Const {
|
||||
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 NOTIFY_NORMAL_LOG = 0;
|
||||
public static final int NOTIFY_USER_TOASTS = 1;
|
||||
public static final int NOTIFY_USER_TO_OWNER = 2;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final String FLASH_SECOND_SLOT = "slot";
|
||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||
public static final String UNINSTALL = "uninstall";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int ORDER_NAME = 0;
|
217
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
217
app/src/full/java/com/topjohnwu/magisk/Data.java
Normal file
@@ -0,0 +1,217 @@
|
||||
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.BootReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.PackageReceiver;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
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;
|
||||
public static int snetVersionCode;
|
||||
public static String snetLink;
|
||||
|
||||
// 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 suAccessState;
|
||||
public static boolean suFingerprint;
|
||||
public static int multiuserMode;
|
||||
public static int suResponseType;
|
||||
public static int suNotificationType;
|
||||
public static int suNamespaceMode;
|
||||
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(BootReceiver.class, a.h.class);
|
||||
classMap.put(PackageReceiver.class, a.i.class);
|
||||
classMap.put(ManagerUpdate.class, a.j.class);
|
||||
classMap.put(RebootReceiver.class, a.k.class);
|
||||
classMap.put(ShortcutReceiver.class, a.l.class);
|
||||
classMap.put(OnBootService.class, a.m.class);
|
||||
classMap.put(UpdateCheckService.class, a.n.class);
|
||||
classMap.put(AboutCardRow.class, a.o.class);
|
||||
classMap.put(SuRequestActivity.class, a.p.class);
|
||||
|
||||
}
|
||||
|
||||
public static void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
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("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS)).exec();
|
||||
}
|
||||
|
||||
public static void importPrefs() {
|
||||
MagiskManager mm = MM();
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, 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);
|
||||
suAccessState = mm.mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mm.mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mm.mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
suFingerprint = mm.mDB.getSettings(Const.Key.SU_FINGERPRINT, 0) != 0;
|
||||
if (suFingerprint && !FingerprintHelper.canUseFingerprint()) {
|
||||
// User revoked the fingerprint
|
||||
mm.mDB.setSettings(Const.Key.SU_FINGERPRINT, 0);
|
||||
suFingerprint = false;
|
||||
}
|
||||
|
||||
// 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.HOSTS, Const.MAGISK_HOST_FILE.exists())
|
||||
.putBoolean(Const.Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
|
||||
.putBoolean(Const.Key.SU_FINGERPRINT, suFingerprint)
|
||||
.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.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.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();
|
||||
}
|
||||
}
|
44
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
44
app/src/full/java/com/topjohnwu/magisk/DonationActivity.java
Normal file
@@ -0,0 +1,44 @@
|
||||
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,11 +1,9 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@@ -16,9 +14,10 @@ import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
@@ -30,11 +29,12 @@ import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class FlashActivity extends Activity {
|
||||
public class FlashActivity extends BaseActivity {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.txtLog) TextView flashLogs;
|
||||
@@ -44,37 +44,33 @@ public class FlashActivity extends Activity {
|
||||
|
||||
private List<String> logs;
|
||||
|
||||
@OnClick(R.id.no_thanks)
|
||||
void dismiss() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@OnClick(R.id.reboot)
|
||||
void reboot() {
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
|
||||
@OnClick(R.id.save_logs)
|
||||
void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
runWithPermission(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, () -> {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = String.format(Locale.US,
|
||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File logFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
logFile.getParentFile().mkdirs();
|
||||
try (FileWriter writer = new FileWriter(logFile)) {
|
||||
for (String s : logs) {
|
||||
writer.write(s);
|
||||
writer.write('\n');
|
||||
File logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try (FileWriter writer = new FileWriter(logFile)) {
|
||||
for (String s : logs) {
|
||||
writer.write(s);
|
||||
writer.write('\n');
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
MagiskManager.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
Utils.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,7 +82,8 @@ public class FlashActivity extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_flash);
|
||||
ButterKnife.bind(this);
|
||||
new FlashActivity_ViewBinding(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
@@ -99,11 +96,23 @@ public class FlashActivity extends Activity {
|
||||
|
||||
logs = new ArrayList<>();
|
||||
CallbackList<String> console = new CallbackList<String>(new ArrayList<>()) {
|
||||
|
||||
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);
|
||||
flashLogs.setText(TextUtils.join("\n", this));
|
||||
sv.postDelayed(() -> sv.fullScroll(ScrollView.FOCUS_DOWN), 10);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String set(int i, String s) {
|
||||
String ret = super.set(i, s);
|
||||
Data.mainHandler.post(this::updateUI);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,18 +128,24 @@ public class FlashActivity extends Activity {
|
||||
new UninstallMagisk(this, uri, console, logs).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_MAGISK:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.DIRECT_MODE).exec();
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.DIRECT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.FLASH_SECOND_SLOT:
|
||||
new InstallMagisk(this, console, logs, uri, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
||||
new InstallMagisk(this, console, logs, InstallMagisk.SECOND_SLOT_MODE).exec();
|
||||
break;
|
||||
case Const.Value.PATCH_BOOT:
|
||||
new InstallMagisk(this, console, logs, uri,
|
||||
new InstallMagisk(this, console, logs,
|
||||
intent.getParcelableExtra(Const.Key.FLASH_SET_BOOT)).exec();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.close)
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent user accidentally press back button
|
||||
@@ -138,14 +153,14 @@ public class FlashActivity extends Activity {
|
||||
|
||||
private static class UninstallMagisk extends FlashZip {
|
||||
|
||||
private UninstallMagisk(Activity context, Uri uri, List<String> console, List<String> logs) {
|
||||
private UninstallMagisk(BaseActivity context, Uri uri, List<String> console, List<String> logs) {
|
||||
super(context, uri, console, logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
if (result == 1) {
|
||||
new Handler().postDelayed(() ->
|
||||
Data.mainHandler.postDelayed(() ->
|
||||
RootUtils.uninstallPkg(getActivity().getPackageName()), 3000);
|
||||
} else {
|
||||
super.onPostExecute(result);
|
||||
|
@@ -1,127 +1,49 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.topjohnwu.magisk.components.Application;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.ShellInitializer;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.ContainerApp;
|
||||
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.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskManager extends Application implements Shell.Container {
|
||||
|
||||
// Topics
|
||||
public final Topic magiskHideDone = new Topic();
|
||||
public final Topic reloadActivity = new Topic();
|
||||
public final Topic moduleLoadDone = new Topic();
|
||||
public final Topic repoLoadDone = new Topic();
|
||||
public final Topic updateCheckDone = new Topic();
|
||||
public final Topic safetyNetDone = new Topic();
|
||||
public final Topic localeDone = new Topic();
|
||||
public class MagiskManager extends ContainerApp {
|
||||
|
||||
// Info
|
||||
public boolean hasInit = false;
|
||||
public String magiskVersionString;
|
||||
public int magiskVersionCode = -1;
|
||||
public String remoteMagiskVersionString;
|
||||
public int remoteMagiskVersionCode = -1;
|
||||
public String remoteManagerVersionString;
|
||||
public int remoteManagerVersionCode = -1;
|
||||
|
||||
public String magiskLink;
|
||||
public String magiskNoteLink;
|
||||
public String managerLink;
|
||||
public String managerNoteLink;
|
||||
public String uninstallerLink;
|
||||
|
||||
public boolean keepVerity = false;
|
||||
public boolean keepEnc = false;
|
||||
|
||||
// Data
|
||||
public Map<String, Module> moduleMap;
|
||||
public List<Locale> locales;
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public int suRequestTimeout;
|
||||
public int suLogTimeout = 14;
|
||||
public int suAccessState;
|
||||
public int multiuserMode;
|
||||
public int suResponseType;
|
||||
public int suNotificationType;
|
||||
public int suNamespaceMode;
|
||||
public String localeConfig;
|
||||
public int updateChannel;
|
||||
public String bootFormat;
|
||||
public int repoOrder;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDatabaseHelper mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
private volatile Shell mShell;
|
||||
|
||||
public MagiskManager() {
|
||||
weakSelf = new WeakReference<>(this);
|
||||
Shell.setContainer(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Shell getShell() {
|
||||
return mShell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShell(@Nullable Shell shell) {
|
||||
mShell = shell;
|
||||
Data.weakApp = new WeakReference<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
Shell.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.setInitializer(ShellInitializer.class);
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.setInitializer(RootUtils.class);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mDB = MagiskDatabaseHelper.getInstance(this);
|
||||
mDB = MagiskDatabaseHelper.getInstance();
|
||||
|
||||
String pkg = mDB.getStrings(Const.Key.SU_MANAGER, null);
|
||||
if (pkg != null && getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
mDB.setStrings(Const.Key.SU_MANAGER, null);
|
||||
RootUtils.uninstallPkg(pkg);
|
||||
Shell.su("pm uninstall " + pkg).exec();
|
||||
}
|
||||
if (TextUtils.equals(pkg, getPackageName())) {
|
||||
try {
|
||||
@@ -131,162 +53,13 @@ public class MagiskManager extends Application implements Shell.Container {
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
setLocale();
|
||||
loadConfig();
|
||||
LocaleManager.setLocale(this);
|
||||
Data.loadConfig();
|
||||
}
|
||||
|
||||
public static MagiskManager get() {
|
||||
return (MagiskManager) weakSelf.get();
|
||||
}
|
||||
|
||||
public void setLocale() {
|
||||
localeConfig = prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Resources res = getBaseContext().getResources();
|
||||
Configuration config = new Configuration(res.getConfiguration());
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
// su
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suAccessState = mDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = mDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = mDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
// config
|
||||
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
|
||||
}
|
||||
|
||||
public void writeConfig() {
|
||||
prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.HOSTS, Const.MAGISK_HOST_FILE.exists())
|
||||
.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.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putString(Const.Key.LOCALE, localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void loadMagiskInfo() {
|
||||
try {
|
||||
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
|
||||
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
|
||||
String s = ShellUtils.fastCmd((magiskVersionCode >= Const.MAGISK_VER.RESETPROP_PERSIST ?
|
||||
"resetprop -p " : "getprop ") + Const.MAGISKHIDE_PROP);
|
||||
magiskHide = s == null || Integer.parseInt(s) != 0;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public void getDefaultInstallFlags() {
|
||||
keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
}
|
||||
|
||||
public void setupUpdateCheck() {
|
||||
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
|
||||
|
||||
if (prefs.getBoolean(Const.Key.CHECK_UPDATES, true)) {
|
||||
if (scheduler.getAllPendingJobs().isEmpty() ||
|
||||
Const.UPDATE_SERVICE_VER > prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(this, 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 {
|
||||
scheduler.cancel(Const.UPDATE_SERVICE_VER);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPrefs() {
|
||||
// Flush prefs to disk
|
||||
prefs.edit().commit();
|
||||
File xml = new File(getFilesDir().getParent() + "/shared_prefs",
|
||||
getPackageName() + "_preferences.xml");
|
||||
Shell.Sync.su(Utils.fmt("for usr in /data/user/*; do cat %s > ${usr}/%s; done", xml, Const.MANAGER_CONFIGS));
|
||||
}
|
||||
|
||||
public void loadPrefs() {
|
||||
SuFile config = new SuFile(Utils.fmt("/data/user/%d/%s", Const.USER_ID, Const.MANAGER_CONFIGS));
|
||||
if (config.exists()) {
|
||||
SharedPreferences.Editor editor = 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();
|
||||
}
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
||||
|
@@ -3,37 +3,41 @@ package com.topjohnwu.magisk;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.fragments.LogFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskFragment;
|
||||
import com.topjohnwu.magisk.fragments.MagiskHideFragment;
|
||||
import com.topjohnwu.magisk.fragments.ModulesFragment;
|
||||
import com.topjohnwu.magisk.fragments.ReposFragment;
|
||||
import com.topjohnwu.magisk.fragments.SettingsFragment;
|
||||
import com.topjohnwu.magisk.fragments.SuperuserFragment;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MainActivity extends Activity
|
||||
public class MainActivity extends BaseActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||
|
||||
private final Handler mDrawerHandler = new Handler();
|
||||
private int mDrawerItem;
|
||||
private boolean fromShortcut = true;
|
||||
private static boolean fromShortcut = false;
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
@BindView(R.id.toolbar) public Toolbar toolbar;
|
||||
@BindView(R.id.drawer_layout) DrawerLayout drawer;
|
||||
@BindView(R.id.nav_view) public NavigationView navigationView;
|
||||
@BindView(R.id.nav_view) NavigationView navigationView;
|
||||
|
||||
private float toolbarElevation;
|
||||
|
||||
@@ -44,27 +48,14 @@ public class MainActivity extends Activity
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
if (!mm.hasInit) {
|
||||
Intent intent = new Intent(this, SplashActivity.class);
|
||||
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||
if (section != null) {
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, section);
|
||||
}
|
||||
startActivity(intent);
|
||||
startActivity(new Intent(this, Data.classMap.get(SplashActivity.class)));
|
||||
finish();
|
||||
}
|
||||
|
||||
String perm = getIntent().getStringExtra(Const.Key.INTENT_PERM);
|
||||
if (perm != null) {
|
||||
ActivityCompat.requestPermissions(this, new String[] { perm }, 0);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
ButterKnife.bind(this);
|
||||
new MainActivity_ViewBinding(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
@@ -86,8 +77,11 @@ public class MainActivity extends Activity
|
||||
drawer.addDrawerListener(toggle);
|
||||
toggle.syncState();
|
||||
|
||||
if (savedInstanceState == null)
|
||||
navigate(getIntent().getStringExtra(Const.Key.OPEN_SECTION));
|
||||
if (savedInstanceState == null) {
|
||||
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
|
||||
fromShortcut = section != null;
|
||||
navigate(section);
|
||||
}
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this);
|
||||
}
|
||||
@@ -118,28 +112,20 @@ public class MainActivity extends Activity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getMagiskManager().reloadActivity };
|
||||
}
|
||||
|
||||
public void checkHideSection() {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
Menu menu = navigationView.getMenu();
|
||||
menu.findItem(R.id.magiskhide).setVisible(
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(!mm.prefs.getBoolean(Const.Key.COREONLY, false)
|
||||
&& Utils.checkNetworkStatus() && Shell.rootAccess() && mm.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
|
||||
mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false));
|
||||
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.downloads).setVisible(Download.checkNetworkStatus(this)
|
||||
&& Shell.rootAccess() && Data.magiskVersionCode >= 0);
|
||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||
menu.findItem(R.id.superuser).setVisible(Shell.rootAccess() &&
|
||||
!(Const.USER_ID > 0 && mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||
!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED));
|
||||
}
|
||||
|
||||
public void navigate(String item) {
|
||||
@@ -167,6 +153,9 @@ public class MainActivity extends Activity
|
||||
case "about":
|
||||
itemId = R.id.app_about;
|
||||
break;
|
||||
case "donation":
|
||||
itemId = R.id.donation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
navigate(itemId);
|
||||
@@ -197,11 +186,14 @@ public class MainActivity extends Activity
|
||||
displayFragment(new LogFragment(), false);
|
||||
break;
|
||||
case R.id.settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
mDrawerItem = bak;
|
||||
displayFragment(new SettingsFragment(), true);
|
||||
break;
|
||||
case R.id.app_about:
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
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;
|
||||
}
|
||||
|
@@ -1,23 +1,10 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
|
||||
public class NoUIActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
String[] perms = getIntent().getStringArrayExtra(Const.Key.INTENT_PERM);
|
||||
if (perms != null) {
|
||||
ActivityCompat.requestPermissions(this, perms, 0);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
@@ -1,321 +0,0 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.AppTheme_StatusBar_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
toolbar.setNavigationOnClickListener(view -> finish());
|
||||
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.settings);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setFloating();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getMagiskManager().reloadActivity };
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener, Topic.Subscriber {
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private PreferenceScreen prefScreen;
|
||||
|
||||
private ListPreference updateChannel, suAccess, autoRes, suNotification,
|
||||
requestTimeout, multiuserMode, namespaceMode;
|
||||
private MagiskManager mm;
|
||||
private PreferenceCategory generalCatagory;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
mm = Utils.getMagiskManager(getActivity());
|
||||
prefs = mm.prefs;
|
||||
prefScreen = getPreferenceScreen();
|
||||
|
||||
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
MagiskManager.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||
suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((pref, o) -> {
|
||||
mm.updateChannel = Integer.parseInt((String) o);
|
||||
if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, null)
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.setEnabled(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.MANAGER_HIDE) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
new HideManager(getActivity()).exec();
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.dlAndReceive(
|
||||
getActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
mm.dumpPrefs();
|
||||
if (ShellUtils.fastCmdResult("pm install " + uri.getPath()))
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
}
|
||||
},
|
||||
mm.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", mm.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||
mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.UNIFIED) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
|
||||
entries[0] = Utils.getLocaleString(MagiskManager.defaultLocale, R.string.system_default);
|
||||
entryValues[0] = "";
|
||||
int i = 1;
|
||||
for (Locale locale : mm.locales) {
|
||||
entries[i] = locale.getDisplayName(locale);
|
||||
entryValues[i++] = locale.toLanguageTag();
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
subscribeTopics();
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
unsubscribeTopics();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
|
||||
switch (key) {
|
||||
case Const.Key.DARK_THEME:
|
||||
mm.isDarkTheme = prefs.getBoolean(key, false);
|
||||
mm.reloadActivity.publish(false);
|
||||
return;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su("magiskhide --enable");
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --disable");
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.Async.su(
|
||||
"cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts");
|
||||
} else {
|
||||
Shell.Async.su(
|
||||
"umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE);
|
||||
}
|
||||
break;
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
mm.setLocale();
|
||||
mm.reloadActivity.publish(false);
|
||||
break;
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
new CheckUpdates().exec();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
mm.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
mm.loadConfig();
|
||||
setSummary();
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[mm.updateChannel]);
|
||||
suAccess.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[mm.suAccessState]);
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[mm.suResponseType]);
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[mm.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary, prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserMode.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[mm.multiuserMode]);
|
||||
namespaceMode.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[mm.suNamespaceMode]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { mm.localeDone };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -7,33 +7,34 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.receivers.ShortcutReceiver;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class SplashActivity extends Activity {
|
||||
public class SplashActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
RootUtils.init();
|
||||
MagiskManager mm = getMagiskManager();
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && Data.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
Utils.setupUpdateCheck();
|
||||
// Load modules
|
||||
Utils.loadModules();
|
||||
}
|
||||
|
||||
mm.repoDB = new RepoDatabaseHelper(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
mm.loadPrefs();
|
||||
Data.importPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
new LoadLocale().exec();
|
||||
LocaleManager.loadAvailableLocales();
|
||||
|
||||
// Create notification channel on Android O
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -43,46 +44,24 @@ public class SplashActivity extends Activity {
|
||||
}
|
||||
|
||||
// Setup shortcuts
|
||||
sendBroadcast(new Intent(this, ShortcutReceiver.class));
|
||||
sendBroadcast(new Intent(this, Data.classMap.get(ShortcutReceiver.class)));
|
||||
|
||||
LoadModules loadModuleTask = new LoadModules();
|
||||
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
if (Download.checkNetworkStatus(this)) {
|
||||
// Fire update check
|
||||
new CheckUpdates().exec();
|
||||
// Add repo update check
|
||||
loadModuleTask.setCallBack(() -> new UpdateRepos(false).exec());
|
||||
}
|
||||
|
||||
// Magisk working as expected
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||
// Update check service
|
||||
mm.setupUpdateCheck();
|
||||
// Fire asynctasks
|
||||
loadModuleTask.exec();
|
||||
CheckUpdates.check();
|
||||
// Repo update check
|
||||
new UpdateRepos().exec();
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
mm.writeConfig();
|
||||
Data.writeConfig();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
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.INTENT_PERM, getIntent().getStringExtra(Const.Key.INTENT_PERM));
|
||||
intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM));
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager.get().locales = Utils.getAvailableLocale();
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
MagiskManager.get().localeDone.publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
259
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
259
app/src/full/java/com/topjohnwu/magisk/SuRequestActivity.java
Normal file
@@ -0,0 +1,259 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuRequestActivity extends BaseActivity {
|
||||
@BindView(R.id.su_popup) LinearLayout suPopup;
|
||||
@BindView(R.id.timeout) Spinner timeout;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appNameView;
|
||||
@BindView(R.id.package_name) TextView packageNameView;
|
||||
@BindView(R.id.grant_btn) Button grant_btn;
|
||||
@BindView(R.id.deny_btn) Button deny_btn;
|
||||
@BindView(R.id.fingerprint) ImageView fingerprintImg;
|
||||
@BindView(R.id.warning) TextView warning;
|
||||
|
||||
private SuConnector connector;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
class SuConnectorV1 extends SuConnector {
|
||||
|
||||
SuConnectorV1(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 response() {
|
||||
try (OutputStream out = getOutputStream()) {
|
||||
out.write((policy.policy == Policy.ALLOW ? "socket:ALLOW" : "socket:DENY").getBytes());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SuConnectorV2 extends SuConnector {
|
||||
|
||||
SuConnectorV2(String name) throws IOException {
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void response() {
|
||||
try (DataOutputStream out = getOutputStream()) {
|
||||
out.writeInt(policy.policy);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.SuRequest_Dark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
// Get policy
|
||||
Intent intent = getIntent();
|
||||
try {
|
||||
connector = intent.getIntExtra("version", 1) == 1 ?
|
||||
new SuConnectorV1(intent.getStringExtra("socket")) :
|
||||
new SuConnectorV2(intent.getStringExtra("socket"));
|
||||
Bundle bundle = connector.readSocketInput();
|
||||
int uid = Integer.parseInt(bundle.getString("uid"));
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
} catch (IOException | PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Never allow com.topjohnwu.magisk (could be malware)
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Data.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
new SuRequestActivity_ViewBinding(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(Data.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
}
|
||||
@Override
|
||||
public void onFinish() {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||
handleAction(Policy.DENY);
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = Data.suFingerprint && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
});
|
||||
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleAction() {
|
||||
connector.response();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
private void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -12,10 +13,10 @@ import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -23,8 +24,9 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
||||
|
||||
@@ -33,36 +35,61 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
private PackageManager pm;
|
||||
private ApplicationFilter filter;
|
||||
|
||||
public ApplicationAdapter() {
|
||||
public ApplicationAdapter(Context context) {
|
||||
fullList = showList = Collections.emptyList();
|
||||
hideList = Collections.emptyList();
|
||||
filter = new ApplicationFilter();
|
||||
pm = MagiskManager.get().getPackageManager();
|
||||
new LoadApps().exec();
|
||||
pm = context.getPackageManager();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
private void loadApps() {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
hideList = Shell.su("magiskhide --ls").exec().getOut();
|
||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled || info.uid == 1000) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return Utils.getAppLabel(a, pm).toLowerCase()
|
||||
.compareTo(Utils.getAppLabel(b, pm).toLowerCase());
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
||||
return new ViewHolder(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
ApplicationInfo info = showList.get(position);
|
||||
|
||||
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||
holder.appName.setText(info.loadLabel(pm));
|
||||
holder.appName.setText(Utils.getAppLabel(info, pm));
|
||||
holder.appPackage.setText(info.packageName);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(hideList.contains(info.packageName));
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Shell.Async.su("magiskhide --add " + info.packageName);
|
||||
Shell.su("magiskhide --add " + info.packageName).submit();
|
||||
hideList.add(info.packageName);
|
||||
} else {
|
||||
Shell.Async.su("magiskhide --rm " + info.packageName);
|
||||
Shell.su("magiskhide --rm " + info.packageName).submit();
|
||||
hideList.remove(info.packageName);
|
||||
}
|
||||
});
|
||||
@@ -78,7 +105,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
new LoadApps().exec();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(this::loadApps);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -90,11 +117,11 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
private class ApplicationFilter extends Filter {
|
||||
class ApplicationFilter extends Filter {
|
||||
|
||||
private boolean lowercaseContains(String s, CharSequence filter) {
|
||||
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
|
||||
@@ -108,7 +135,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
showList = new ArrayList<>();
|
||||
String filter = constraint.toString().toLowerCase();
|
||||
for (ApplicationInfo info : fullList) {
|
||||
if (lowercaseContains(info.loadLabel(pm).toString(), filter)
|
||||
if (lowercaseContains(Utils.getAppLabel(info, pm), filter)
|
||||
|| lowercaseContains(info.packageName, filter)) {
|
||||
showList.add(info);
|
||||
}
|
||||
@@ -122,37 +149,4 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadApps extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
fullList = pm.getInstalledApplications(0);
|
||||
hideList = Shell.Sync.su("magiskhide --ls");
|
||||
for (Iterator<ApplicationInfo> i = fullList.iterator(); i.hasNext(); ) {
|
||||
ApplicationInfo info = i.next();
|
||||
if (Const.HIDE_BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
Collections.sort(fullList, (a, b) -> {
|
||||
boolean ah = hideList.contains(a.packageName);
|
||||
boolean bh = hideList.contains(b.packageName);
|
||||
if (ah == bh) {
|
||||
return a.loadLabel(pm).toString().toLowerCase().compareTo(
|
||||
b.loadLabel(pm).toString().toLowerCase());
|
||||
} else if (ah) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager.get().magiskHideDone.publish(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -11,6 +9,7 @@ import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
@@ -18,8 +17,9 @@ import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> {
|
||||
|
||||
@@ -29,6 +29,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
mList = list;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
||||
@@ -46,9 +47,9 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(module.getName());
|
||||
holder.versionName.setText( TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText( TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText( TextUtils.isEmpty(description) ? noInfo : description);
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(module.isEnabled());
|
||||
@@ -114,7 +115,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new ModulesAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
checkBox.setEnabled(false);
|
||||
|
@@ -2,8 +2,6 @@ package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -11,19 +9,23 @@ import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.database.MagiskDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
@@ -38,6 +40,7 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
this.pm = pm;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
|
||||
@@ -63,14 +66,34 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
holder.appName.setText(policy.appName);
|
||||
holder.packageName.setText(policy.packageName);
|
||||
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if ((isChecked && policy.policy == Policy.DENY) ||
|
||||
(!isChecked && policy.policy == Policy.ALLOW)) {
|
||||
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
|
||||
holder.notificationSwitch.setOnCheckedChangeListener(null);
|
||||
holder.loggingSwitch.setOnCheckedChangeListener(null);
|
||||
|
||||
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||
holder.notificationSwitch.setChecked(policy.notification);
|
||||
holder.loggingSwitch.setChecked(policy.logging);
|
||||
|
||||
holder.masterSwitch.setOnClickListener(v -> {
|
||||
boolean isChecked = holder.masterSwitch.isChecked();
|
||||
Runnable r = () -> {
|
||||
if ((isChecked && policy.policy == Policy.DENY) ||
|
||||
(!isChecked && policy.policy == Policy.ALLOW)) {
|
||||
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
};
|
||||
if (Data.suFingerprint) {
|
||||
holder.masterSwitch.setChecked(!isChecked);
|
||||
FingerprintHelper.showAuthDialog((Activity) v.getContext(), () -> {
|
||||
holder.masterSwitch.setChecked(isChecked);
|
||||
r.run();
|
||||
});
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
});
|
||||
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
@@ -93,7 +116,7 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.delete.setOnClickListener(v -> new AlertDialogBuilder((Activity) v.getContext())
|
||||
holder.delete.setOnClickListener(v -> new CustomAlertDialog((Activity) v.getContext())
|
||||
.setTitle(R.string.su_revoke_title)
|
||||
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
@@ -107,9 +130,6 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.setCancelable(true)
|
||||
.show());
|
||||
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||
holder.notificationSwitch.setChecked(policy.notification);
|
||||
holder.loggingSwitch.setChecked(policy.logging);
|
||||
|
||||
// Hide for now
|
||||
holder.moreInfo.setVisibility(View.GONE);
|
||||
@@ -137,7 +157,7 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -16,18 +14,18 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> {
|
||||
|
||||
@@ -90,29 +88,31 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
Repo repo = repoPairs.get(section).second.get(position);
|
||||
Context context = holder.itemView.getContext();
|
||||
|
||||
holder.title.setText(repo.getName());
|
||||
holder.versionName.setText(repo.getVersion());
|
||||
String name = repo.getName();
|
||||
String version = repo.getVersion();
|
||||
String author = repo.getAuthor();
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||
holder.description.setText(repo.getDescription());
|
||||
String description = repo.getDescription();
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(TextUtils.isEmpty(name) ? noInfo : name);
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||
new MarkDownWindow((BaseActivity) context, null, repo.getDetailUrl()).exec());
|
||||
|
||||
holder.downloadImage.setOnClickListener(v -> {
|
||||
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
||||
new AlertDialogBuilder((Activity) context)
|
||||
new CustomAlertDialog((BaseActivity) context)
|
||||
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, filename))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) ->
|
||||
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||
Utils.getLegalFilename(filename), true).exec()
|
||||
new ProcessRepoZip((BaseActivity) context, repo, true).exec()
|
||||
)
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||
Utils.getLegalFilename(filename), false).exec())
|
||||
new ProcessRepoZip((BaseActivity) context, repo, false).exec())
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
@@ -169,7 +169,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new ReposAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new ReposAdapter$RepoHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -20,8 +19,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||
|
||||
@@ -124,7 +123,7 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new SuLogAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +141,7 @@ public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, S
|
||||
|
||||
LogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
||||
container.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
package com.topjohnwu.magisk.adapters;
|
||||
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
public class TabFragmentAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private List<Fragment> fragmentList;
|
||||
|
@@ -2,9 +2,9 @@ package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.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;
|
||||
@@ -19,10 +19,10 @@ import java.net.HttpURLConnection;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
public static final File dexPath =
|
||||
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
new File(Data.MM().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private ISafetyNetHelper helper;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
@@ -30,9 +30,9 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
}
|
||||
|
||||
private void dlSnet() throws Exception {
|
||||
Shell.Sync.sh("rm -rf " + dexPath.getParent());
|
||||
Shell.sh("rm -rf " + dexPath.getParent()).exec();
|
||||
dexPath.getParentFile().mkdir();
|
||||
HttpURLConnection conn = WebService.request(Const.Url.SNET_URL, null);
|
||||
HttpURLConnection conn = WebService.mustRequest(Data.snetLink, null);
|
||||
try (
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath));
|
||||
InputStream in = new BufferedInputStream(conn.getInputStream())) {
|
||||
@@ -45,17 +45,19 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
private void dyload() throws Exception {
|
||||
DexClassLoader loader = new DexClassLoader(dexPath.getPath(), dexPath.getParent(),
|
||||
null, ISafetyNetHelper.class.getClassLoader());
|
||||
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.SafetyNetHelper");
|
||||
helper = (ISafetyNetHelper) clazz.getConstructors()[0]
|
||||
.newInstance(getActivity(), (ISafetyNetHelper.Callback)
|
||||
code -> MagiskManager.get().safetyNetDone.publish(false, code));
|
||||
if (helper.getVersion() != Const.SNET_VER) {
|
||||
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() < Data.snetVersionCode) {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Exception doInBackground(Void... voids) {
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
try {
|
||||
dyload();
|
||||
@@ -64,21 +66,12 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
dlSnet();
|
||||
dyload();
|
||||
}
|
||||
// Run attestation
|
||||
helper.attest();
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
e.printStackTrace();
|
||||
Topic.publish(false, Topic.SNET_CHECK_DONE, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Exception e) {
|
||||
if (e == null) {
|
||||
helper.attest();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
MagiskManager.get().safetyNetDone.publish(false, -1);
|
||||
}
|
||||
super.onPostExecute(e);
|
||||
}
|
||||
}
|
||||
|
@@ -1,31 +1,50 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
public class CheckUpdates {
|
||||
|
||||
private boolean showNotification;
|
||||
|
||||
public CheckUpdates() {
|
||||
this(false);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public CheckUpdates(boolean b) {
|
||||
showNotification = b;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
private static JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void fetchUpdates() {
|
||||
String jsonStr = "";
|
||||
switch (mm.updateChannel) {
|
||||
switch (Data.updateChannel) {
|
||||
case Const.Value.STABLE_CHANNEL:
|
||||
jsonStr = WebService.getString(Const.Url.STABLE_URL);
|
||||
break;
|
||||
@@ -33,38 +52,54 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
jsonStr = WebService.getString(Data.MM().prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
}
|
||||
|
||||
JSONObject json;
|
||||
try {
|
||||
JSONObject json = new JSONObject(jsonStr);
|
||||
JSONObject magisk = json.getJSONObject("magisk");
|
||||
mm.remoteMagiskVersionString = magisk.getString("version");
|
||||
mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
|
||||
mm.magiskLink = magisk.getString("link");
|
||||
mm.magiskNoteLink = magisk.getString("note");
|
||||
JSONObject manager = json.getJSONObject("app");
|
||||
mm.remoteManagerVersionString = manager.getString("version");
|
||||
mm.remoteManagerVersionCode = manager.getInt("versionCode");
|
||||
mm.managerLink = manager.getString("link");
|
||||
mm.managerNoteLink = manager.getString("note");
|
||||
JSONObject uninstaller = json.getJSONObject("uninstaller");
|
||||
mm.uninstallerLink = uninstaller.getString("link");
|
||||
} catch (JSONException ignored) {}
|
||||
return null;
|
||||
json = new JSONObject(jsonStr);
|
||||
} catch (JSONException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
JSONObject snet = getJson(json, "snet");
|
||||
Data.snetVersionCode = getInt(snet, "versionCode", -1);
|
||||
Data.snetLink = getString(snet, "link", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (showNotification) {
|
||||
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||
ShowUI.managerUpdateNotification();
|
||||
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||
ShowUI.magiskUpdateNotification();
|
||||
public static void check(Runnable cb) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
fetchUpdates();
|
||||
if (cb != null) {
|
||||
if (BuildConfig.VERSION_CODE < Data.remoteManagerVersionCode) {
|
||||
NotificationMgr.managerUpdate();
|
||||
} else if (Data.magiskVersionCode < Data.remoteMagiskVersionCode) {
|
||||
NotificationMgr.magiskUpdate();
|
||||
}
|
||||
cb.run();
|
||||
}
|
||||
}
|
||||
mm.updateCheckDone.publish();
|
||||
super.onPostExecute(v);
|
||||
Topic.publish(Topic.UPDATE_CHECK_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
check(null);
|
||||
}
|
||||
}
|
||||
|
@@ -2,13 +2,13 @@ package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
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.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
@@ -45,7 +45,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
try {
|
||||
console.add("- Copying zip to temp directory");
|
||||
|
||||
@@ -66,12 +66,10 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
}
|
||||
if (!unzipAndCheck()) return 0;
|
||||
console.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||
Shell.Sync.su(console, logs,
|
||||
"cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile + " || echo 'Failed!'"
|
||||
);
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
if (!Shell.su("cd " + mCachedFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess())
|
||||
return -1;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -86,10 +84,7 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
FlashActivity activity = (FlashActivity) getActivity();
|
||||
Shell.Async.su(
|
||||
"rm -rf " + mCachedFile.getParent(),
|
||||
"rm -rf " + Const.TMP_FOLDER_PATH
|
||||
);
|
||||
Shell.su("rm -rf " + mCachedFile.getParent(), "rm -rf " + Const.TMP_FOLDER_PATH).submit();
|
||||
switch (result) {
|
||||
case -1:
|
||||
console.add("! Installation failed");
|
||||
@@ -99,8 +94,8 @@ public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
break;
|
||||
case 1:
|
||||
// Success
|
||||
new LoadModules().exec();
|
||||
// Reload modules
|
||||
Utils.loadModules();
|
||||
break;
|
||||
}
|
||||
activity.reboot.setVisibility(result > 0 ? View.VISIBLE : View.GONE);
|
||||
|
@@ -1,95 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public HideManager(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
private String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
dialog = ProgressDialog.show(getActivity(),
|
||||
getActivity().getString(R.string.hide_manager_toast),
|
||||
getActivity().getString(R.string.hide_manager_toast2));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
// Generate a new app with random package name
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk");
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
if (!PatchAPK.patchPackageID(
|
||||
mm.getPackageCodePath(),
|
||||
new SuFileOutputStream(repack),
|
||||
Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult(Shell.getShell(), "pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
|
||||
mm.dumpPrefs();
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean b) {
|
||||
dialog.dismiss();
|
||||
if (!b) {
|
||||
MagiskManager.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(b);
|
||||
}
|
||||
}
|
@@ -8,16 +8,22 @@ 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.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
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;
|
||||
@@ -26,15 +32,19 @@ 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.util.AbstractList;
|
||||
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;
|
||||
@@ -42,7 +52,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
private static final int FIX_ENV_MODE = 2;
|
||||
public static final int SECOND_SLOT_MODE = 3;
|
||||
|
||||
private Uri bootUri, mZip;
|
||||
private Uri bootUri;
|
||||
private List<String> console, logs;
|
||||
private String mBoot;
|
||||
private int mode;
|
||||
@@ -50,22 +60,21 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
private ProgressDialog dialog;
|
||||
private MagiskManager mm;
|
||||
|
||||
public InstallMagisk(Activity context, Uri zip) {
|
||||
public InstallMagisk(Activity context) {
|
||||
super(context);
|
||||
mZip = zip;
|
||||
mm = MagiskManager.get();
|
||||
mm = Data.MM();
|
||||
mode = FIX_ENV_MODE;
|
||||
}
|
||||
|
||||
public InstallMagisk(Activity context, List<String> console, List<String> logs, Uri zip, int mode) {
|
||||
this(context, zip);
|
||||
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 zip, Uri boot) {
|
||||
this(context, console, logs, zip, PATCH_MODE);
|
||||
public InstallMagisk(FlashActivity context, List<String> console, List<String> logs, Uri boot) {
|
||||
this(context, console, logs, PATCH_MODE);
|
||||
bootUri = boot;
|
||||
}
|
||||
|
||||
@@ -74,37 +83,92 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
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 = new NOPList<>();
|
||||
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, null);
|
||||
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 = mm.getContentResolver().openInputStream(mZip)) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
BufferedInputStream buf = new BufferedInputStream(in);
|
||||
buf.mark(Integer.MAX_VALUE);
|
||||
ZipUtils.unzip(buf, installDir, arch + "/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "common/", true);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "chromeos/", false);
|
||||
buf.reset();
|
||||
ZipUtils.unzip(buf, installDir, "META-INF/com/google/android/update-binary", true);
|
||||
buf.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
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.Sync.sh(Utils.fmt("chmod -R 755 %s/*; %s/magiskinit -x magisk %s/magisk",
|
||||
installDir, installDir, installDir));
|
||||
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 locally");
|
||||
console.add("- Copying image to cache");
|
||||
// Copy boot image to local
|
||||
try (InputStream in = mm.getContentResolver().openInputStream(bootUri);
|
||||
OutputStream out = new FileOutputStream(mBoot)
|
||||
@@ -150,16 +214,13 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
}
|
||||
|
||||
// Patch boot image
|
||||
Shell.Sync.sh(console, logs,
|
||||
"cd " + installDir,
|
||||
Utils.fmt("KEEPFORCEENCRYPT=%b KEEPVERITY=%b sh update-binary indep " +
|
||||
"boot_patch.sh %s || echo 'Failed!'",
|
||||
mm.keepEnc, mm.keepVerity, mBoot));
|
||||
|
||||
if (TextUtils.equals(console.get(console.size() - 1), "Failed!"))
|
||||
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.Sync.sh("mv bin/busybox busybox",
|
||||
Shell.Job job = Shell.sh("mv bin/busybox busybox",
|
||||
"rm -rf magisk.apk bin boot.img update-binary",
|
||||
"cd /");
|
||||
|
||||
@@ -172,18 +233,20 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
) {
|
||||
SignBoot.doSignature("/boot", in, out, null, null);
|
||||
}
|
||||
Shell.Sync.su("mv -f " + signed + " " + patched);
|
||||
job.add("mv -f " + signed + " " + patched);
|
||||
}
|
||||
job.exec();
|
||||
return patched;
|
||||
}
|
||||
|
||||
private void outputBoot(File patched) throws IOException {
|
||||
private boolean outputBoot(File patched) throws IOException {
|
||||
switch (mode) {
|
||||
case PATCH_MODE:
|
||||
File dest = new File(Const.EXTERNAL_PATH, "patched_boot" + mm.bootFormat);
|
||||
String fmt = mm.prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
File dest = new File(Download.EXTERNAL_PATH, "patched_boot" + fmt);
|
||||
dest.getParentFile().mkdirs();
|
||||
OutputStream out;
|
||||
switch (mm.bootFormat) {
|
||||
switch (fmt) {
|
||||
case ".img.tar":
|
||||
out = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||
((TarOutputStream) out).putNextEntry(new TarEntry(patched, "boot.img"));
|
||||
@@ -197,7 +260,7 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
ShellUtils.pump(in, out);
|
||||
out.close();
|
||||
}
|
||||
Shell.Sync.su("rm -f " + patched);
|
||||
Shell.sh("rm -f " + patched).exec();
|
||||
console.add("");
|
||||
console.add("****************************");
|
||||
console.add(" Patched image is placed in ");
|
||||
@@ -206,26 +269,42 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
break;
|
||||
case SECOND_SLOT_MODE:
|
||||
case DIRECT_MODE:
|
||||
Shell.Sync.sh(console, logs,
|
||||
Utils.fmt("direct_install %s %s %s", patched, mBoot, installDir));
|
||||
if (!mm.keepVerity)
|
||||
Shell.Sync.sh(console, logs, "find_dtbo_image", "patch_dtbo_image");
|
||||
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.Sync.sh("rm -rf /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.Sync.sh("rm -rf " + installDir);
|
||||
Shell.sh("rm -rf " + installDir).exec();
|
||||
installDir.mkdirs();
|
||||
}
|
||||
|
||||
@@ -240,13 +319,16 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
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");
|
||||
char slot[] = ShellUtils.fastCmd("echo $SLOT").toCharArray();
|
||||
if (slot[1] == 'a') slot[1] = 'b';
|
||||
else slot[1] = 'a';
|
||||
mBoot = ShellUtils.fastCmd("SLOT=" + String.valueOf(slot),
|
||||
"find_boot_image", "echo \"$BOOTIMAGE\"");
|
||||
Shell.Async.su("mount_partitions");
|
||||
mBoot = ShellUtils.fastCmd(
|
||||
"SLOT=" + target,
|
||||
"find_boot_image",
|
||||
"SLOT=" + slot,
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
break;
|
||||
case FIX_ENV_MODE:
|
||||
mBoot = "";
|
||||
@@ -257,12 +339,13 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.add("- Target image: " + mBoot);
|
||||
if (mode == DIRECT_MODE || mode == SECOND_SLOT_MODE)
|
||||
console.add("- Target image: " + mBoot);
|
||||
|
||||
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
String arch;
|
||||
|
||||
if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// 32-bit only
|
||||
if (abis.contains("x86")) arch = "x86";
|
||||
else arch = "arm";
|
||||
@@ -278,12 +361,15 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
try {
|
||||
extractFiles(arch);
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
Shell.Sync.sh("fix_env");
|
||||
Shell.su("fix_env").exec();
|
||||
} else {
|
||||
File patched = patchBoot();
|
||||
if (patched == null)
|
||||
return false;
|
||||
outputBoot(patched);
|
||||
if (!outputBoot(patched))
|
||||
return false;
|
||||
if (mode == SECOND_SLOT_MODE)
|
||||
postOTA();
|
||||
console.add("- All done!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -297,31 +383,16 @@ public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (mode == FIX_ENV_MODE) {
|
||||
dialog.dismiss();
|
||||
MagiskManager.toast(result ? R.string.setup_done : R.string.setup_fail, Toast.LENGTH_LONG);
|
||||
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.Async.sh("rm -rf " + installDir);
|
||||
Shell.sh("rm -rf " + installDir).submit();
|
||||
console.add("! Installation failed");
|
||||
activity.reboot.setVisibility(View.GONE);
|
||||
}
|
||||
activity.buttonPanel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NOPList<E> extends AbstractList<E> {
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {}
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LoadModules extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
private List<String> getModList() {
|
||||
String command = "ls -d " + Const.MAGISK_PATH + "/* | grep -v lost+found";
|
||||
return Shell.Sync.su(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
mm.moduleMap = new ValueSortedMap<>();
|
||||
|
||||
for (String path : getModList()) {
|
||||
Module module = new Module(path);
|
||||
mm.moduleMap.put(module.getId(), module);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager.get().moduleLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
@@ -17,6 +17,8 @@ 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;
|
||||
@@ -38,7 +40,7 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
String md;
|
||||
if (mUrl != null) {
|
||||
md = WebService.getString(mUrl);
|
||||
@@ -54,9 +56,9 @@ public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||
}
|
||||
String css;
|
||||
try (
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
mm.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
InputStream in = mm.getResources().openRawResource(
|
||||
Data.isDarkTheme ? R.raw.dark : R.raw.light);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
) {
|
||||
ShellUtils.pump(in, out);
|
||||
css = out.toString();
|
||||
|
@@ -9,8 +9,6 @@ public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<P
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
|
||||
private Runnable callback = null;
|
||||
|
||||
public ParallelTask() {}
|
||||
|
||||
public ParallelTask(Activity context) {
|
||||
@@ -22,18 +20,7 @@ public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<P
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ParallelTask<Params, Progress, Result> exec(Params... params) {
|
||||
public void exec(Params... params) {
|
||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
if (callback != null) callback.run();
|
||||
}
|
||||
|
||||
public ParallelTask<Params, Progress, Result> setCallBack(Runnable next) {
|
||||
callback = next;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
154
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
154
app/src/full/java/com/topjohnwu/magisk/asyncs/PatchAPK.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.os.AsyncTask;
|
||||
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.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class PatchAPK {
|
||||
|
||||
private static String genPackageName(String prefix, int length) {
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.append(prefix);
|
||||
length -= prefix.length();
|
||||
SecureRandom random = new SecureRandom();
|
||||
String base = "abcdefghijklmnopqrstuvwxyz";
|
||||
String alpha = base + base.toUpperCase();
|
||||
String full = alpha + "0123456789..........";
|
||||
char next, prev = '\0';
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (prev == '.' || i == length - 1 || i == 0) {
|
||||
next = alpha.charAt(random.nextInt(alpha.length()));
|
||||
} else {
|
||||
next = full.charAt(random.nextInt(full.length()));
|
||||
}
|
||||
builder.append(next);
|
||||
prev = next;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static int findOffset(byte buf[], byte pattern[]) {
|
||||
int offset = -1;
|
||||
for (int i = 0; i < buf.length - pattern.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; ++j) {
|
||||
if (buf[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* It seems that AAPT sometimes generate another type of string format */
|
||||
private static boolean fallbackPatch(byte xml[], String from, String to) {
|
||||
|
||||
byte[] target = new byte[from.length() * 2 + 2];
|
||||
for (int i = 0; i < from.length(); ++i) {
|
||||
target[i * 2] = (byte) from.charAt(i);
|
||||
}
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
byte[] dest = new byte[target.length - 2];
|
||||
for (int i = 0; i < to.length(); ++i) {
|
||||
dest[i * 2] = (byte) to.charAt(i);
|
||||
}
|
||||
System.arraycopy(dest, 0, xml, offset, dest.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean findAndPatch(byte xml[], String from, String to) {
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return fallbackPatch(xml, from, to);
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean patchAndHide() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
// Generate a new app with random package name
|
||||
SuFile repack = new SuFile("/data/local/tmp/repack.apk");
|
||||
String pkg = genPackageName("com.", Const.ORIG_PKG_NAME.length());
|
||||
|
||||
try {
|
||||
JarMap apk = new JarMap(mm.getPackageCodePath());
|
||||
if (!patchPackageID(apk, Const.ORIG_PKG_NAME, pkg))
|
||||
return false;
|
||||
SignAPK.sign(apk, new SuFileOutputStream(repack));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install the application
|
||||
if (!ShellUtils.fastCmdResult("pm install " + repack))
|
||||
return false;
|
||||
|
||||
repack.delete();
|
||||
|
||||
mm.mDB.setStrings(Const.Key.SU_MANAGER, pkg);
|
||||
mm.mDB.flush();
|
||||
Data.exportPrefs();
|
||||
RootUtils.uninstallPkg(Const.ORIG_PKG_NAME);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean patchPackageID(JarMap apk, String from, String to) {
|
||||
try {
|
||||
JarEntry je = apk.getJarEntry(Const.ANDROID_MANIFEST);
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
if (!findAndPatch(xml, from, to))
|
||||
return false;
|
||||
if (!findAndPatch(xml, from + ".provider", to + ".provider"))
|
||||
return false;
|
||||
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void hideManager(Activity activity) {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.hide_manager_toast),
|
||||
activity.getString(R.string.hide_manager_toast2));
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
boolean b = patchAndHide();
|
||||
Data.mainHandler.post(() -> {
|
||||
dialog.cancel();
|
||||
if (!b) {
|
||||
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,19 +1,20 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
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.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
@@ -33,21 +34,21 @@ import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private boolean mInstall;
|
||||
private String mLink;
|
||||
private File mFile;
|
||||
private Repo mRepo;
|
||||
private int progress = 0, total = -1;
|
||||
private Handler mHandler;
|
||||
|
||||
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
|
||||
public ProcessRepoZip(BaseActivity context, Repo repo, boolean install) {
|
||||
super(context);
|
||||
mLink = link;
|
||||
mFile = new File(Const.EXTERNAL_PATH, filename);
|
||||
mInstall = install;
|
||||
mHandler = new Handler();
|
||||
mRepo = repo;
|
||||
mInstall = install && Shell.rootAccess();
|
||||
mFile = new File(Download.EXTERNAL_PATH, repo.getDownloadFilename());
|
||||
}
|
||||
|
||||
private void removeTopFolder(File input, File output) throws IOException {
|
||||
@@ -74,28 +75,26 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseActivity getActivity() {
|
||||
return (BaseActivity) super.getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
mFile.getParentFile().mkdirs();
|
||||
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return null;
|
||||
try {
|
||||
// Request zip from Internet
|
||||
HttpURLConnection conn;
|
||||
do {
|
||||
conn = WebService.request(mLink, null);
|
||||
total = conn.getContentLength();
|
||||
if (total < 0)
|
||||
conn.disconnect();
|
||||
else
|
||||
break;
|
||||
} while (true);
|
||||
HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl(), null);
|
||||
total = conn.getContentLength();
|
||||
|
||||
// Temp files
|
||||
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||
@@ -112,7 +111,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
}
|
||||
conn.disconnect();
|
||||
|
||||
mHandler.post(() -> {
|
||||
Data.mainHandler.post(() -> {
|
||||
progressDialog.setTitle(R.string.zip_process_title);
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||
});
|
||||
@@ -136,30 +135,28 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
Activity activity = getActivity();
|
||||
BaseActivity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
progressDialog.dismiss();
|
||||
if (result) {
|
||||
Uri uri = Uri.fromFile(mFile);
|
||||
if (Shell.rootAccess() && mInstall) {
|
||||
Intent intent = new Intent(activity, FlashActivity.class);
|
||||
if (mInstall) {
|
||||
Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class));
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
} else {
|
||||
MagiskManager.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
Utils.toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||
}
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
|
||||
com.topjohnwu.magisk.components.Activity.runWithPermission(
|
||||
getActivity(), new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||
() -> super.exec(voids));
|
||||
return this;
|
||||
public void exec(Void... voids) {
|
||||
getActivity().runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, super::exec);
|
||||
}
|
||||
|
||||
private class ProgressInputStream extends FilterInputStream {
|
||||
@@ -170,14 +167,15 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
|
||||
private void updateDlProgress(int step) {
|
||||
progress += step;
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, (int) (100 * (double) progress / total + 0.5)));
|
||||
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg,
|
||||
(int) (100 * (double) progress / total + 0.5)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
int b = super.read();
|
||||
if (b > 0) {
|
||||
mHandler.post(() -> updateDlProgress(1));
|
||||
Data.mainHandler.post(() -> updateDlProgress(1));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
@@ -191,7 +189,7 @@ public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||
int read = super.read(b, off, len);
|
||||
if (read > 0) {
|
||||
mHandler.post(() -> updateDlProgress(read));
|
||||
Data.mainHandler.post(() -> updateDlProgress(read));
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
public class RestoreImages extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public RestoreImages(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
Activity a = getActivity();
|
||||
dialog = ProgressDialog.show(a, a.getString(R.string.restore_img), a.getString(R.string.restore_img_msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
return ShellUtils.fastCmdResult("restore_imgs");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
dialog.cancel();
|
||||
if (result) {
|
||||
MagiskManager.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
MagiskManager.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,13 +2,13 @@ package com.topjohnwu.magisk.asyncs;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.ReposFragment;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
@@ -20,64 +20,41 @@ import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
public class UpdateRepos {
|
||||
|
||||
private static final int CHECK_ETAG = 0;
|
||||
private static final int LOAD_NEXT = 1;
|
||||
private static final int LOAD_PREV = 2;
|
||||
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
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 List<String> etags, newEtags = new LinkedList<>();
|
||||
private Set<String> cached;
|
||||
private boolean forceUpdate;
|
||||
private AtomicInteger taskCount = new AtomicInteger(0);
|
||||
final private Object allDone = new Object();
|
||||
|
||||
public UpdateRepos(boolean force) {
|
||||
mm = MagiskManager.get();
|
||||
mm.repoLoadDone.reset();
|
||||
forceUpdate = force;
|
||||
static {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private void queueTask(Runnable task) {
|
||||
// Thread pool's queue has an upper bound, batch it with 64 tasks
|
||||
while (taskCount.get() >= 64) {
|
||||
waitTasks();
|
||||
}
|
||||
taskCount.incrementAndGet();
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
task.run();
|
||||
if (taskCount.decrementAndGet() == 0) {
|
||||
synchronized (allDone) {
|
||||
allDone.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
private MagiskManager mm;
|
||||
private Set<String> cached;
|
||||
private ExecutorService threadPool;
|
||||
|
||||
public UpdateRepos() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
private void waitTasks() {
|
||||
if (taskCount.get() == 0)
|
||||
return;
|
||||
synchronized (allDone) {
|
||||
try {
|
||||
allDone.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Wait again
|
||||
waitTasks();
|
||||
}
|
||||
}
|
||||
threadPool.shutdown();
|
||||
try {
|
||||
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
|
||||
private boolean loadJSON(String jsonString) throws JSONException, ParseException {
|
||||
@@ -89,20 +66,17 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject rawRepo = jsonArray.getJSONObject(i);
|
||||
String id = rawRepo.getString("description");
|
||||
String name = rawRepo.getString("name");
|
||||
String id = rawRepo.getString("name");
|
||||
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
|
||||
Set<String> set = Collections.synchronizedSet(cached);
|
||||
queueTask(() -> {
|
||||
threadPool.execute(() -> {
|
||||
Repo repo = mm.repoDB.getRepo(id);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(name);
|
||||
repo = new Repo(id);
|
||||
else
|
||||
set.remove(id);
|
||||
cached.remove(id);
|
||||
repo.update(date);
|
||||
mm.repoDB.addRepo(repo);
|
||||
publishProgress();
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(id);
|
||||
@@ -112,99 +86,74 @@ public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean loadPage(int page, int mode) {
|
||||
/* 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) {
|
||||
Map<String, String> header = new HashMap<>();
|
||||
if (mode == CHECK_ETAG && page < etags.size())
|
||||
header.put(Const.Key.IF_NONE_MATCH, etags.get(page));
|
||||
if (page == 0)
|
||||
header.put(Const.Key.IF_NONE_MATCH, mm.prefs.getString(Const.Key.ETAG_KEY, ""));
|
||||
String url = Utils.fmt(Const.Url.REPO_URL, page + 1);
|
||||
|
||||
try {
|
||||
HttpURLConnection conn = WebService.request(url, header);
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Current page is not updated, check the next page
|
||||
return loadPage(page + 1, CHECK_ETAG);
|
||||
}
|
||||
// No updates
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Current page is the last page
|
||||
if (!loadJSON(WebService.getString(conn)))
|
||||
return mode != CHECK_ETAG;
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If one page is updated, we force update all pages */
|
||||
|
||||
// Update ETAG
|
||||
String etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
if (mode == LOAD_PREV) {
|
||||
// We are loading a previous page, push the new tag to the front
|
||||
newEtags.add(0, etag);
|
||||
} else {
|
||||
newEtags.add(etag);
|
||||
if (page == 0) {
|
||||
String etag = header.get(Const.Key.ETAG_KEY);
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, etag).apply();
|
||||
}
|
||||
|
||||
String links = header.get(Const.Key.LINK_KEY);
|
||||
if (links != null) {
|
||||
for (String s : links.split(", ")) {
|
||||
if (mode != LOAD_PREV && s.contains("next")) {
|
||||
// Force load all next pages
|
||||
loadPage(page + 1, LOAD_NEXT);
|
||||
return links == null || !links.contains("next") || loadPage(page + 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (mode != LOAD_NEXT && s.contains("prev")) {
|
||||
// Back propagation
|
||||
loadPage(page - 1, LOAD_PREV);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
waitTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
if (ReposFragment.adapter != null)
|
||||
ReposFragment.adapter.notifyDBChanged();
|
||||
}
|
||||
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);
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mm.repoLoadDone.setPending();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
etags = Arrays.asList(mm.prefs.getString(Const.Key.ETAG_KEY, "").split(","));
|
||||
cached = mm.repoDB.getRepoIDSet();
|
||||
|
||||
if (loadPage(0, CHECK_ETAG)) {
|
||||
waitTasks();
|
||||
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
|
||||
// Update ETag
|
||||
mm.prefs.edit().putString(Const.Key.ETAG_KEY, TextUtils.join(",", newEtags)).apply();
|
||||
} else if (forceUpdate) {
|
||||
Cursor c = mm.repoDB.getRawCursor();
|
||||
while (c.moveToNext()) {
|
||||
Repo repo = new Repo(c);
|
||||
queueTask(() -> {
|
||||
try {
|
||||
repo.update();
|
||||
mm.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
mm.repoDB.removeRepo(repo);
|
||||
}
|
||||
});
|
||||
if (loadPage(0)) {
|
||||
waitTasks();
|
||||
// The leftover cached means they are removed from online repo
|
||||
mm.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
waitTasks();
|
||||
}
|
||||
return null;
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
mm.repoLoadDone.publish();
|
||||
super.onPostExecute(v);
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
||||
|
@@ -28,19 +28,17 @@ import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
/**
|
||||
* @author dvdandroid
|
||||
*/
|
||||
public class AboutCardRow extends LinearLayout {
|
||||
|
||||
private final String title;
|
||||
private final Drawable icon;
|
||||
|
||||
private final TextView mTitle;
|
||||
private final TextView mSummary;
|
||||
private final ImageView mIcon;
|
||||
|
||||
private final View mView;
|
||||
@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);
|
||||
@@ -53,37 +51,28 @@ public class AboutCardRow extends LinearLayout {
|
||||
public AboutCardRow(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LayoutInflater.from(context).inflate(R.layout.info_item_row, this);
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AboutCardRow, 0, 0);
|
||||
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();
|
||||
}
|
||||
|
||||
mView = findViewById(R.id.container);
|
||||
|
||||
mTitle = (TextView) findViewById(android.R.id.title);
|
||||
mSummary = (TextView) findViewById(android.R.id.summary);
|
||||
mIcon = (ImageView) findViewById(android.R.id.icon);
|
||||
|
||||
mTitle.setText(title);
|
||||
mIcon.setImageDrawable(icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
super.setOnClickListener(l);
|
||||
|
||||
mView.setOnClickListener(l);
|
||||
}
|
||||
|
||||
public void setSummary(String s) {
|
||||
mSummary.setVisibility(VISIBLE);
|
||||
mSummary.setText(s);
|
||||
}
|
||||
|
||||
public void removeSummary() {
|
||||
mSummary.setVisibility(GONE);
|
||||
}
|
||||
}
|
@@ -1,153 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class AlertDialogBuilder extends AlertDialog.Builder {
|
||||
|
||||
@BindView(R.id.button_panel) LinearLayout buttons;
|
||||
@BindView(R.id.message_panel) LinearLayout messagePanel;
|
||||
|
||||
@BindView(R.id.negative) Button negative;
|
||||
@BindView(R.id.positive) Button positive;
|
||||
@BindView(R.id.neutral) Button neutral;
|
||||
@BindView(R.id.message) TextView messageView;
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
|
||||
private AlertDialog dialog;
|
||||
|
||||
public AlertDialogBuilder(@NonNull Activity context) {
|
||||
super(context);
|
||||
setup();
|
||||
}
|
||||
|
||||
public AlertDialogBuilder(@NonNull Activity context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
|
||||
ButterKnife.bind(this, v);
|
||||
super.setView(v);
|
||||
negative.setVisibility(View.GONE);
|
||||
positive.setVisibility(View.GONE);
|
||||
neutral.setVisibility(View.GONE);
|
||||
buttons.setVisibility(View.GONE);
|
||||
messagePanel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setTitle(int titleId) {
|
||||
return super.setTitle(titleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setMessage(@Nullable CharSequence message) {
|
||||
messageView.setText(message);
|
||||
messagePanel.setVisibility(View.VISIBLE);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setMessage(@StringRes int messageId) {
|
||||
return setMessage(getContext().getString(messageId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
buttons.setVisibility(View.VISIBLE);
|
||||
positive.setVisibility(View.VISIBLE);
|
||||
positive.setText(text);
|
||||
positiveListener = listener;
|
||||
positive.setOnClickListener((v) -> {
|
||||
if (positiveListener != null) {
|
||||
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setPositiveButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
buttons.setVisibility(View.VISIBLE);
|
||||
negative.setVisibility(View.VISIBLE);
|
||||
negative.setText(text);
|
||||
negativeListener = listener;
|
||||
negative.setOnClickListener((v) -> {
|
||||
if (negativeListener != null) {
|
||||
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNegativeButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
buttons.setVisibility(View.VISIBLE);
|
||||
neutral.setVisibility(View.VISIBLE);
|
||||
neutral.setText(text);
|
||||
neutralListener = listener;
|
||||
neutral.setOnClickListener((v) -> {
|
||||
if (neutralListener != null) {
|
||||
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog.Builder setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNeutralButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
dialog = super.create();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class BaseFragment extends Fragment implements Topic.AutoSubscriber {
|
||||
|
||||
public MagiskManager mm;
|
||||
protected Unbinder unbinder = null;
|
||||
|
||||
public BaseFragment() {
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Topic.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Topic.unsubscribe(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (unbinder != null)
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode) {
|
||||
startActivityForResult(intent, requestCode, this::onActivityResult);
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, BaseActivity.ActivityResultListener listener) {
|
||||
((BaseActivity) requireActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
((BaseActivity) requireActivity()).runWithPermission(permissions,callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return FlavorActivity.EMPTY_INT_ARRAY;
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class CustomAlertDialog extends AlertDialog.Builder {
|
||||
|
||||
private DialogInterface.OnClickListener positiveListener;
|
||||
private DialogInterface.OnClickListener negativeListener;
|
||||
private DialogInterface.OnClickListener neutralListener;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private ViewHolder vh;
|
||||
|
||||
public class ViewHolder {
|
||||
@BindView(R.id.dialog_layout) public LinearLayout dialogLayout;
|
||||
@BindView(R.id.button_panel) public LinearLayout buttons;
|
||||
|
||||
@BindView(R.id.message) public TextView messageView;
|
||||
@BindView(R.id.negative) public Button negative;
|
||||
@BindView(R.id.positive) public Button positive;
|
||||
@BindView(R.id.neutral) public Button neutral;
|
||||
|
||||
ViewHolder(View v) {
|
||||
new CustomAlertDialog$ViewHolder_ViewBinding(this, v);
|
||||
messageView.setVisibility(View.GONE);
|
||||
negative.setVisibility(View.GONE);
|
||||
positive.setVisibility(View.GONE);
|
||||
neutral.setVisibility(View.GONE);
|
||||
buttons.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
|
||||
vh = new ViewHolder(v);
|
||||
super.setView(v);
|
||||
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CustomAlertDialog(@NonNull Activity context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
public ViewHolder getViewHolder() {
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(int layoutResId) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setView(View view) { return this; }
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@Nullable CharSequence message) {
|
||||
vh.messageView.setVisibility(View.VISIBLE);
|
||||
vh.messageView.setText(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setMessage(@StringRes int messageId) {
|
||||
return setMessage(getContext().getString(messageId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.positive.setVisibility(View.VISIBLE);
|
||||
vh.positive.setText(text);
|
||||
positiveListener = listener;
|
||||
vh.positive.setOnClickListener((v) -> {
|
||||
if (positiveListener != null) {
|
||||
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setPositiveButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.negative.setVisibility(View.VISIBLE);
|
||||
vh.negative.setText(text);
|
||||
negativeListener = listener;
|
||||
vh.negative.setOnClickListener((v) -> {
|
||||
if (negativeListener != null) {
|
||||
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNegativeButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
|
||||
vh.buttons.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setVisibility(View.VISIBLE);
|
||||
vh.neutral.setText(text);
|
||||
neutralListener = listener;
|
||||
vh.neutral.setOnClickListener((v) -> {
|
||||
if (neutralListener != null) {
|
||||
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomAlertDialog setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
|
||||
return setNeutralButton(getContext().getString(textId), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
dialog = super.create();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
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,50 +1,58 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Keep;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StyleRes;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
public abstract class FlavorActivity extends AppCompatActivity {
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
private AssetManager swappedAssetManager = null;
|
||||
private Resources swappedResources = null;
|
||||
private Resources.Theme backupTheme = null;
|
||||
public abstract class FlavorActivity extends AppCompatActivity implements Topic.AutoSubscriber {
|
||||
|
||||
private ActivityResultListener activityResultListener;
|
||||
static int[] EMPTY_INT_ARRAY = new int[0];
|
||||
public MagiskManager mm;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
Configuration config = base.getResources().getConfiguration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
applyOverrideConfiguration(config);
|
||||
mm = Data.MM();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDarkTheme() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public MagiskManager getMagiskManager() {
|
||||
return (MagiskManager) super.getApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
|
||||
if (getMagiskManager().isDarkTheme && getDarkTheme() != -1) {
|
||||
Topic.subscribe(this);
|
||||
if (Data.isDarkTheme && getDarkTheme() != -1) {
|
||||
setTheme(getDarkTheme());
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).unsubscribeTopics();
|
||||
}
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -63,46 +71,18 @@ public abstract class FlavorActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources.Theme getTheme() {
|
||||
return backupTheme == null ? super.getTheme() : backupTheme;
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (activityResultListener != null)
|
||||
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||
activityResultListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetManager getAssets() {
|
||||
return swappedAssetManager == null ? super.getAssets() : swappedAssetManager;
|
||||
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||
activityResultListener = listener;
|
||||
super.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
private AssetManager getAssets(String apk) {
|
||||
try {
|
||||
AssetManager asset = AssetManager.class.newInstance();
|
||||
AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk);
|
||||
return asset;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return swappedResources == null ? super.getResources() : swappedResources;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void swapResources(String dexPath) {
|
||||
AssetManager asset = getAssets(dexPath);
|
||||
if (asset != null) {
|
||||
backupTheme = super.getTheme();
|
||||
Resources res = super.getResources();
|
||||
swappedResources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration());
|
||||
swappedAssetManager = asset;
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void restoreResources() {
|
||||
swappedAssetManager = null;
|
||||
swappedResources = null;
|
||||
backupTheme = null;
|
||||
public interface ActivityResultListener {
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +0,0 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
public class Fragment extends android.support.v4.app.Fragment {
|
||||
|
||||
public MagiskManager getApplication() {
|
||||
return Utils.getMagiskManager(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).subscribeTopics();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (this instanceof Topic.Subscriber) {
|
||||
((Topic.Subscriber) this).unsubscribeTopics();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode) {
|
||||
startActivityForResult(intent, requestCode, this::onActivityResult);
|
||||
}
|
||||
|
||||
public void startActivityForResult(Intent intent, int requestCode, Activity.ActivityResultListener listener) {
|
||||
((Activity) getActivity()).startActivityForResult(intent, requestCode, listener);
|
||||
}
|
||||
|
||||
public void runWithPermission(String[] permissions, Runnable callback) {
|
||||
Activity.runWithPermission(getActivity(), permissions, callback);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
class InstallMethodDialog extends AlertDialog.Builder {
|
||||
|
||||
InstallMethodDialog(BaseActivity activity, List<String> options) {
|
||||
super(activity);
|
||||
setTitle(R.string.select_method);
|
||||
setItems(options.toArray(new String [0]), (dialog, idx) -> {
|
||||
Intent intent;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
Utils.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT).setType("*/*");
|
||||
activity.runWithPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, () ->
|
||||
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT &&
|
||||
resultCode == Activity.RESULT_OK && data != null) {
|
||||
Intent i = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
activity.startActivity(i);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
break;
|
||||
case 0:
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
Data.remoteMagiskVersionString, Data.remoteMagiskVersionCode);
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
}, Data.magiskLink, filename);
|
||||
break;
|
||||
case 2:
|
||||
intent = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
case 3:
|
||||
new CustomAlertDialog(activity)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.install_inactive_slot_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Intent it = new Intent(activity, Data.classMap.get(FlashActivity.class))
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
|
||||
activity.startActivity(it);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
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 filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
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, filename));
|
||||
setCancelable(true);
|
||||
setPositiveButton(R.string.install, (d, i) -> activity.runWithPermission(
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
mm.sendBroadcast(intent);
|
||||
}))
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,14 +2,15 @@ package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class SnackbarMaker {
|
||||
|
||||
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
||||
@@ -34,13 +35,13 @@ public class SnackbarMaker {
|
||||
}
|
||||
|
||||
private static void setup(Snackbar snack) {
|
||||
TextView text = snack.getView().findViewById(android.support.design.R.id.snackbar_text);
|
||||
TextView text = snack.getView().findViewById(com.google.android.material.R.id.snackbar_text);
|
||||
text.setMaxLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static void showUri(Activity activity, Uri uri) {
|
||||
make(activity, activity.getString(R.string.internal_storage,
|
||||
"/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
|
||||
"/Download/" + Utils.getNameFromUri(activity, uri)),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.ok, (v)->{}).show();
|
||||
}
|
||||
|
@@ -0,0 +1,57 @@
|
||||
package com.topjohnwu.magisk.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
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.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class UninstallDialog extends CustomAlertDialog {
|
||||
|
||||
public UninstallDialog(@NonNull Activity activity) {
|
||||
super(activity);
|
||||
MagiskManager mm = Data.MM();
|
||||
setTitle(R.string.uninstall_magisk_title);
|
||||
setMessage(R.string.uninstall_magisk_msg);
|
||||
setNeutralButton(R.string.restore_img, (d, i) -> {
|
||||
ProgressDialog dialog = ProgressDialog.show(activity,
|
||||
activity.getString(R.string.restore_img),
|
||||
activity.getString(R.string.restore_img_msg));
|
||||
Shell.su("restore_imgs").submit(result -> {
|
||||
dialog.cancel();
|
||||
if (result.isSuccess()) {
|
||||
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!TextUtils.isEmpty(Data.uninstallerLink)) {
|
||||
setPositiveButton(R.string.complete_uninstall, (d, i) ->
|
||||
Download.receive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(context, Data.classMap.get(FlashActivity.class))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}, Data.uninstallerLink, "magisk-uninstaller.zip"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +1,35 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
|
||||
private String mId = null, mName, mVersion, mAuthor, mDescription;
|
||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
||||
private int mVersionCode = -1, minMagiskVersion = -1;
|
||||
|
||||
protected BaseModule() {}
|
||||
protected BaseModule() {
|
||||
mId = mName = mVersion = mAuthor = mDescription = "";
|
||||
}
|
||||
|
||||
protected BaseModule(Cursor c) {
|
||||
mId = c.getString(c.getColumnIndex("id"));
|
||||
mName = c.getString(c.getColumnIndex("name"));
|
||||
mVersion = c.getString(c.getColumnIndex("version"));
|
||||
mId = nonNull(c.getString(c.getColumnIndex("id")));
|
||||
mName = nonNull(c.getString(c.getColumnIndex("name")));
|
||||
mVersion = nonNull(c.getString(c.getColumnIndex("version")));
|
||||
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
|
||||
mAuthor = c.getString(c.getColumnIndex("author"));
|
||||
mDescription = c.getString(c.getColumnIndex("description"));
|
||||
mAuthor = nonNull(c.getString(c.getColumnIndex("author")));
|
||||
mDescription = nonNull(c.getString(c.getColumnIndex("description")));
|
||||
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
|
||||
}
|
||||
|
||||
private String nonNull(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("id", mId);
|
||||
|
@@ -14,9 +14,9 @@ public class Module extends BaseModule {
|
||||
parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = new SuFile(path + "/remove");
|
||||
mDisableFile = new SuFile(path + "/disable");
|
||||
mUpdateFile = new SuFile(path + "/update");
|
||||
mRemoveFile = new SuFile(path, "remove");
|
||||
mDisableFile = new SuFile(path, "disable");
|
||||
mUpdateFile = new SuFile(path, "update");
|
||||
|
||||
if (getId() == null) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
|
@@ -4,7 +4,10 @@ import android.content.ContentValues;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
public class Policy implements Comparable<Policy>{
|
||||
@@ -25,7 +28,7 @@ public class Policy implements Comparable<Policy>{
|
||||
this.uid = uid;
|
||||
packageName = pkgs[0];
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
appName = info.loadLabel(pm).toString();
|
||||
appName = Utils.getAppLabel(info, pm);
|
||||
}
|
||||
|
||||
public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
|
@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
@@ -14,16 +14,14 @@ import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
|
||||
private String repoName;
|
||||
private Date mLastUpdate;
|
||||
|
||||
public Repo(String name) {
|
||||
repoName = name;
|
||||
public Repo(String id) {
|
||||
setId(id);
|
||||
}
|
||||
|
||||
public Repo(Cursor c) {
|
||||
super(c);
|
||||
repoName = c.getString(c.getColumnIndex("repo_name"));
|
||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||
}
|
||||
|
||||
@@ -32,17 +30,14 @@ public class Repo extends BaseModule {
|
||||
try {
|
||||
parseProps(props);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
|
||||
throw new IllegalRepoException("Repo [" + getId() + "] parse error: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (getId() == null) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
|
||||
}
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
throw new IllegalRepoException("Repo [" + getId() + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
|
||||
Logger.debug("Repo [" + repoName + "] is outdated");
|
||||
Logger.debug("Repo [" + getId() + "] is outdated");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,36 +49,34 @@ public class Repo extends BaseModule {
|
||||
@Override
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = super.getContentValues();
|
||||
values.put("repo_name", repoName);
|
||||
values.put("last_update", mLastUpdate.getTime());
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getRepoName() {
|
||||
return repoName;
|
||||
}
|
||||
|
||||
public String getZipUrl() {
|
||||
return String.format(Const.Url.ZIP_URL, repoName);
|
||||
return String.format(Const.Url.ZIP_URL, getId());
|
||||
}
|
||||
|
||||
public String getManifestUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "module.prop");
|
||||
return String.format(Const.Url.FILE_URL, getId(), "module.prop");
|
||||
}
|
||||
|
||||
public String getDetailUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "README.md");
|
||||
return String.format(Const.Url.FILE_URL, getId(), "README.md");
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
|
||||
MagiskManager.locale).format(mLastUpdate);
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(mLastUpdate);
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
|
||||
public String getDownloadFilename() {
|
||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
}
|
||||
|
||||
public class IllegalRepoException extends Exception {
|
||||
IllegalRepoException(String message) {
|
||||
super(message);
|
||||
|
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -47,10 +47,10 @@ public class SuLogEntry {
|
||||
}
|
||||
|
||||
public String getDateString() {
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
}
|
||||
|
||||
public String getTimeString() {
|
||||
return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date);
|
||||
return new SimpleDateFormat("h:mm a", LocaleManager.locale).format(date);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -9,6 +7,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ValueSortedMap<K, V extends Comparable<? super V>> extends HashMap<K, V> {
|
||||
|
||||
private List<V> sorted = new ArrayList<>();
|
||||
|
@@ -7,15 +7,16 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
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.Const;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
@@ -27,85 +28,84 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MagiskDatabaseHelper {
|
||||
|
||||
private static final int DATABASE_VER = 5;
|
||||
private static final int DATABASE_VER = 6;
|
||||
private static final int OLD_DATABASE_VER = 5;
|
||||
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 static final File MANAGER_DB =
|
||||
new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
|
||||
|
||||
private PackageManager pm;
|
||||
private SQLiteDatabase db;
|
||||
|
||||
@NonNull
|
||||
public static MagiskDatabaseHelper getInstance(MagiskManager mm) {
|
||||
public static MagiskDatabaseHelper getInstance() {
|
||||
try {
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
return new MagiskDatabaseHelper();
|
||||
} catch (Exception e) {
|
||||
// Let's cleanup everything and try again
|
||||
Shell.Sync.su("db_clean '*'");
|
||||
return new MagiskDatabaseHelper(mm);
|
||||
Shell.su("db_clean '*'").exec();
|
||||
return new MagiskDatabaseHelper();
|
||||
}
|
||||
}
|
||||
|
||||
private MagiskDatabaseHelper(MagiskManager mm) {
|
||||
pm = mm.getPackageManager();
|
||||
db = openDatabase(mm);
|
||||
private MagiskDatabaseHelper() {
|
||||
pm = Data.MM().getPackageManager();
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
db = openDatabase();
|
||||
db.disableWriteAheadLogging();
|
||||
int version = db.getVersion();
|
||||
if (version < DATABASE_VER) {
|
||||
onUpgrade(db, version);
|
||||
} else if (version > DATABASE_VER) {
|
||||
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(DATABASE_VER);
|
||||
db.setVersion(version);
|
||||
clearOutdated();
|
||||
}
|
||||
|
||||
private SQLiteDatabase openDatabase(MagiskManager mm) {
|
||||
final File DB_FILE = new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
|
||||
private SQLiteDatabase openDatabase() {
|
||||
MagiskManager mm = Data.MM();
|
||||
Context de = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||
? mm.createDeviceProtectedStorageContext() : mm;
|
||||
if (!DB_FILE.canWrite()) {
|
||||
if (!MANAGER_DB.canWrite()) {
|
||||
if (!Shell.rootAccess()) {
|
||||
// We don't want the app to crash, create a db and return
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
}
|
||||
mm.loadMagiskInfo();
|
||||
// Cleanup
|
||||
Shell.Sync.su("db_clean " + Const.USER_ID);
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.FBE_AWARE) {
|
||||
// Super old legacy mode
|
||||
return mm.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else if (mm.magiskVersionCode < Const.MAGISK_VER.HIDDEN_PATH) {
|
||||
// Legacy mode with FBE aware
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
de.moveDatabaseFrom(mm, "su.db");
|
||||
}
|
||||
return de.openOrCreateDatabase("su.db", Context.MODE_PRIVATE, null);
|
||||
} else {
|
||||
// Global database
|
||||
final SuFile GLOBAL_DB = new SuFile("/data/adb/magisk.db");
|
||||
mm.deleteDatabase("su.db");
|
||||
de.deleteDatabase("su.db");
|
||||
if (mm.magiskVersionCode < Const.MAGISK_VER.SEPOL_REFACTOR) {
|
||||
// We need some additional policies on old versions
|
||||
Shell.Sync.su("db_sepatch");
|
||||
}
|
||||
if (!GLOBAL_DB.exists()) {
|
||||
Shell.Sync.su("db_init");
|
||||
SQLiteDatabase.openOrCreateDatabase(GLOBAL_DB, null).close();
|
||||
Shell.Sync.su("db_restore");
|
||||
}
|
||||
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();
|
||||
}
|
||||
Shell.Sync.su("db_setup " + Process.myUid());
|
||||
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(DB_FILE, null);
|
||||
return SQLiteDatabase.openOrCreateDatabase(MANAGER_DB, null);
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion) {
|
||||
private void onUpgrade(SQLiteDatabase db, int oldVersion) {
|
||||
if (oldVersion == 0) {
|
||||
createTables(db);
|
||||
oldVersion = 3;
|
||||
@@ -123,7 +123,7 @@ public class MagiskDatabaseHelper {
|
||||
POLICY_TABLE, POLICY_TABLE));
|
||||
db.execSQL(Utils.fmt("DROP TABLE %s_old", POLICY_TABLE));
|
||||
|
||||
MagiskManager.get().deleteDatabase("sulog.db");
|
||||
Data.MM().deleteDatabase("sulog.db");
|
||||
++oldVersion;
|
||||
}
|
||||
if (oldVersion == 2) {
|
||||
@@ -138,11 +138,16 @@ public class MagiskDatabaseHelper {
|
||||
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
|
||||
public void onDowngrade(SQLiteDatabase db) {
|
||||
MagiskManager.toast(R.string.su_db_corrupt, Toast.LENGTH_LONG);
|
||||
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);
|
||||
@@ -170,11 +175,16 @@ public class MagiskDatabaseHelper {
|
||||
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
db.close();
|
||||
init();
|
||||
}
|
||||
|
||||
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() - MagiskManager.get().suLogTimeout * 86400000), null);
|
||||
db.delete(LOG_TABLE, Utils.fmt("time < %d", System.currentTimeMillis() - Data.suLogTimeout * 86400000), null);
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
@@ -236,7 +246,7 @@ public class MagiskDatabaseHelper {
|
||||
String dateString = null, newString;
|
||||
while (c.moveToNext()) {
|
||||
Date date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
|
@@ -5,25 +5,27 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
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.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final int DATABASE_VER = 3;
|
||||
private static final int DATABASE_VER = 4;
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
private MagiskManager mm;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mm = Utils.getMagiskManager(context);
|
||||
mm = Data.MM();
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Remove outdated repos
|
||||
@@ -38,21 +40,14 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
try {
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
|
||||
"PRIMARY KEY(id))");
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
oldVersion = 3;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Reset database
|
||||
onDowngrade(db, DATABASE_VER, 0);
|
||||
if (oldVersion != newVersion) {
|
||||
// Nuke old DB and create new table
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +58,17 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||
removeRepo(repo.getId());
|
||||
}
|
||||
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
@@ -79,10 +76,12 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public Repo getRepo(String id) {
|
||||
@@ -100,7 +99,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (mm.repoOrder) {
|
||||
switch (Data.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
@@ -108,7 +107,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
new String[] { String.valueOf(Data.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
@@ -121,4 +120,18 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void registerAdapter(ReposAdapter a) {
|
||||
adapter = a;
|
||||
}
|
||||
|
||||
public void unregisterAdapter() {
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
private void notifyAdapter() {
|
||||
if (adapter != null) {
|
||||
Data.mainHandler.post(adapter::notifyDBChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,23 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class LogFragment extends Fragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
public class LogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.container) ViewPager viewPager;
|
||||
@BindView(R.id.tab) TabLayout tab;
|
||||
@@ -28,13 +27,13 @@ public class LogFragment extends Fragment {
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
unbinder = new LogFragment_ViewBinding(this, v);
|
||||
|
||||
((MainActivity) getActivity()).toolbar.setElevation(0);
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
if (!(Const.USER_ID > 0 && getApplication().multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
if (!(Const.USER_ID > 0 && Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
}
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||
@@ -45,11 +44,4 @@ public class LogFragment extends Fragment {
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
}
|
@@ -1,13 +1,10 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,35 +15,45 @@ import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MainActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.components.EnvFixDialog;
|
||||
import com.topjohnwu.magisk.components.ExpandableView;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.MagiskInstallDialog;
|
||||
import com.topjohnwu.magisk.components.ManagerInstallDialog;
|
||||
import com.topjohnwu.magisk.components.UninstallDialog;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.ISafetyNetHelper;
|
||||
import com.topjohnwu.magisk.utils.ShowUI;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindColor;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskFragment extends Fragment
|
||||
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
|
||||
public class MagiskFragment extends BaseFragment
|
||||
implements SwipeRefreshLayout.OnRefreshListener, ExpandableView, Topic.Subscriber {
|
||||
|
||||
private Container expandableContainer = new Container();
|
||||
|
||||
private MagiskManager mm;
|
||||
private Unbinder unbinder;
|
||||
private static boolean shownDialog = false;
|
||||
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.swipeRefreshLayout) public SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
@BindView(R.id.core_only_notice) CardView coreOnlyNotice;
|
||||
|
||||
@BindView(R.id.magisk_update) RelativeLayout magiskUpdate;
|
||||
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||
@@ -75,7 +82,7 @@ public class MagiskFragment extends Fragment
|
||||
@BindColor(R.color.red500) int colorBad;
|
||||
@BindColor(R.color.green500) int colorOK;
|
||||
@BindColor(R.color.yellow500) int colorWarn;
|
||||
@BindColor(R.color.grey500) int colorNeutral;
|
||||
@BindColor(R.color.green500) int colorNeutral;
|
||||
@BindColor(R.color.blue500) int colorInfo;
|
||||
|
||||
@OnClick(R.id.safetyNet_title)
|
||||
@@ -84,19 +91,12 @@ public class MagiskFragment extends Fragment
|
||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
new CheckSafetyNet(requireActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (!TextUtils.equals(mm.getPackageName(), Const.ORIG_PKG_NAME)) {
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.cannot_check_sn_title)
|
||||
.setMessage(R.string.cannot_check_sn_notice)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
} else if (!CheckSafetyNet.dexPath.exists()) {
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
new CustomAlertDialog(requireActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(true)
|
||||
@@ -114,36 +114,35 @@ public class MagiskFragment extends Fragment
|
||||
shownDialog = true;
|
||||
|
||||
// Show Manager update first
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
ShowUI.managerInstallDialog(getActivity());
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
new ManagerInstallDialog((BaseActivity) requireActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||
ShowUI.magiskInstallDialog(getActivity());
|
||||
new MagiskInstallDialog((BaseActivity) getActivity()).show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.uninstall_button)
|
||||
void uninstall() {
|
||||
ShowUI.uninstallDialog(getActivity());
|
||||
new UninstallDialog(requireActivity()).show();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_magisk, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
getActivity().setTitle(R.string.magisk);
|
||||
|
||||
mm = getApplication();
|
||||
unbinder = new MagiskFragment_ViewBinding(this, v);
|
||||
requireActivity().setTitle(R.string.magisk);
|
||||
|
||||
expandableContainer.expandLayout = expandLayout;
|
||||
setupExpandable();
|
||||
|
||||
keepVerityChkbox.setChecked(mm.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(mm.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepEnc = checked);
|
||||
keepVerityChkbox.setChecked(Data.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(Data.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> Data.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
@@ -153,7 +152,7 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
mm.loadMagiskInfo();
|
||||
Data.loadMagiskInfo();
|
||||
updateUI();
|
||||
|
||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||
@@ -162,69 +161,75 @@ public class MagiskFragment extends Fragment
|
||||
|
||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||
|
||||
mm.safetyNetDone.reset();
|
||||
mm.updateCheckDone.reset();
|
||||
mm.remoteMagiskVersionString = null;
|
||||
mm.remoteMagiskVersionCode = -1;
|
||||
Topic.reset(getSubscribedTopics());
|
||||
Data.remoteMagiskVersionString = null;
|
||||
Data.remoteMagiskVersionCode = -1;
|
||||
collapse();
|
||||
|
||||
shownDialog = false;
|
||||
|
||||
// Trigger state check
|
||||
if (Utils.checkNetworkStatus()) {
|
||||
new CheckUpdates().exec();
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
CheckUpdates.check();
|
||||
} else {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
if (topic == mm.updateCheckDone) {
|
||||
updateCheckUI();
|
||||
} else if (topic == mm.safetyNetDone) {
|
||||
updateSafetyNetUI((int) topic.getResults()[0]);
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.SNET_CHECK_DONE, Topic.UPDATE_CHECK_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
switch (topic) {
|
||||
case Topic.SNET_CHECK_DONE:
|
||||
updateSafetyNetUI((int) result[0]);
|
||||
break;
|
||||
case Topic.UPDATE_CHECK_DONE:
|
||||
updateCheckUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { mm.updateCheckDone, mm.safetyNetDone };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Container getContainer() {
|
||||
return expandableContainer;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
((MainActivity) getActivity()).checkHideSection();
|
||||
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;
|
||||
}
|
||||
|
||||
boolean hasNetwork = Utils.checkNetworkStatus();
|
||||
private void updateUI() {
|
||||
((MainActivity) requireActivity()).checkHideSection();
|
||||
|
||||
boolean hasNetwork = Download.checkNetworkStatus(mm);
|
||||
boolean hasRoot = Shell.rootAccess();
|
||||
boolean isUpToDate = mm.magiskVersionCode > Const.MAGISK_VER.UNIFIED;
|
||||
|
||||
magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
|
||||
uninstallButton.setVisibility(hasRoot ? View.VISIBLE : View.GONE);
|
||||
coreOnlyNotice.setVisibility(mm.prefs.getBoolean(Const.Key.COREONLY, false) ? View.VISIBLE : View.GONE);
|
||||
|
||||
int image, color;
|
||||
|
||||
if (mm.magiskVersionCode < 0) {
|
||||
if (Data.magiskVersionCode < 0) {
|
||||
color = colorBad;
|
||||
image = R.drawable.ic_cancel;
|
||||
magiskVersionText.setText(R.string.magisk_version_error);
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + mm.magiskVersionString));
|
||||
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + Data.magiskVersionString));
|
||||
}
|
||||
|
||||
magiskStatusIcon.setImageResource(image);
|
||||
@@ -234,7 +239,9 @@ public class MagiskFragment extends Fragment
|
||||
private void updateCheckUI() {
|
||||
int image, color;
|
||||
|
||||
if (mm.remoteMagiskVersionCode < 0) {
|
||||
safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (Data.remoteMagiskVersionCode < 0) {
|
||||
color = colorNeutral;
|
||||
image = R.drawable.ic_help;
|
||||
magiskUpdateText.setText(R.string.invalid_update_channel);
|
||||
@@ -242,11 +249,11 @@ public class MagiskFragment extends Fragment
|
||||
} else {
|
||||
color = colorOK;
|
||||
image = R.drawable.ic_check_circle;
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
|
||||
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + Data.remoteMagiskVersionString));
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||
} else if (Data.magiskVersionCode > 0 && Data.remoteMagiskVersionCode > Data.magiskVersionCode) {
|
||||
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||
} else {
|
||||
installText.setText(R.string.install);
|
||||
@@ -261,12 +268,12 @@ public class MagiskFragment extends Fragment
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (!shownDialog) {
|
||||
if (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
if (Data.remoteMagiskVersionCode > Data.magiskVersionCode
|
||||
|| Data.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||
install();
|
||||
} else if (mm.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
} else if (Data.remoteMagiskVersionCode >= Const.MAGISK_VER.FIX_ENV &&
|
||||
!ShellUtils.fastCmdResult("env_check")) {
|
||||
ShowUI.envFixDialog(getActivity());
|
||||
new EnvFixDialog(requireActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,12 +299,6 @@ public class MagiskFragment extends Fragment
|
||||
} else {
|
||||
@StringRes int resid;
|
||||
switch (response) {
|
||||
case ISafetyNetHelper.CAUSE_SERVICE_DISCONNECTED:
|
||||
resid = R.string.safetyNet_network_loss;
|
||||
break;
|
||||
case ISafetyNetHelper.CAUSE_NETWORK_LOST:
|
||||
resid = R.string.safetyNet_service_disconnected;
|
||||
break;
|
||||
case ISafetyNetHelper.RESPONSE_ERR:
|
||||
resid = R.string.safetyNet_res_invalid;
|
||||
break;
|
@@ -1,9 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -11,19 +8,22 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
SearchView search;
|
||||
|
||||
private ApplicationAdapter appAdapter;
|
||||
|
||||
@@ -37,15 +37,15 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||
|
||||
appAdapter = new ApplicationAdapter(requireActivity());
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
|
||||
|
||||
appAdapter = new ApplicationAdapter();
|
||||
recyclerView.setAdapter(appAdapter);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
|
||||
|
||||
searchListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
@@ -61,7 +61,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().setTitle(R.string.magiskhide);
|
||||
requireActivity().setTitle(R.string.magiskhide);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -69,24 +69,18 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||
SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||
search.setOnQueryTextListener(searchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MAGISK_HIDE_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
appAdapter.filter(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().magiskHideDone };
|
||||
appAdapter.filter(search.getQuery().toString());
|
||||
}
|
||||
}
|
@@ -1,10 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -17,25 +14,23 @@ import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class MagiskLogFragment extends Fragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.txtLog) TextView txtLog;
|
||||
@BindView(R.id.svLog) ScrollView svLog;
|
||||
@@ -46,7 +41,7 @@ public class MagiskLogFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
txtLog.setTextIsSelectable(true);
|
||||
return view;
|
||||
@@ -64,12 +59,6 @@ public class MagiskLogFragment extends Fragment {
|
||||
readLogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu);
|
||||
@@ -92,54 +81,39 @@ public class MagiskLogFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
public void readLogs() {
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (ShellUtils.isValidOutput(out)) {
|
||||
txtLog.setText(TextUtils.join("\n", out));
|
||||
} else {
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {
|
||||
private void readLogs() {
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " | tail -n 5000").submit(result -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (result.getOut().isEmpty())
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
}
|
||||
}, "cat " + Const.MAGISK_LOG + " | tail -n 5000");
|
||||
else
|
||||
txtLog.setText(TextUtils.join("\n", result.getOut()));
|
||||
svLog.postDelayed(() -> svLog.fullScroll(ScrollView.FOCUS_DOWN), 100);
|
||||
hsvLog.postDelayed(() -> hsvLog.fullScroll(ScrollView.FOCUS_LEFT), 100);
|
||||
});
|
||||
}
|
||||
|
||||
public void saveLogs() {
|
||||
private void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = Utils.fmt("magisk_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File targetFile = new File(Const.EXTERNAL_PATH + "/logs", filename);
|
||||
targetFile.getParentFile().mkdirs();
|
||||
File logFile = new File(Download.EXTERNAL_PATH, filename);
|
||||
try {
|
||||
targetFile.createNewFile();
|
||||
logFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.Async.su(new Shell.Async.Callback() {
|
||||
@Override
|
||||
public void onTaskResult(@Nullable List<String> out, @Nullable List<String> err) {
|
||||
SnackbarMaker.make(txtLog, targetFile.getPath(), Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskError(@NonNull Throwable throwable) {}
|
||||
}, "cat " + Const.MAGISK_LOG + " > " + targetFile);
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(txtLog, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
Shell.Async.su("echo -n > " + Const.MAGISK_LOG);
|
||||
private void clearLogs() {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
txtLog.setText(R.string.log_is_empty);
|
||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@@ -1,12 +1,9 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -15,30 +12,36 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public class ModulesFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
|
||||
@OnClick(R.id.fab)
|
||||
public void selectFile() {
|
||||
void selectFile() {
|
||||
runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("application/zip");
|
||||
@@ -50,14 +53,14 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
unbinder = new ModulesFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
new LoadModules().exec();
|
||||
Utils.loadModules();
|
||||
});
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@@ -72,37 +75,31 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
}
|
||||
});
|
||||
|
||||
getActivity().setTitle(R.string.modules);
|
||||
requireActivity().setTitle(R.string.modules);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
updateUI();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { getApplication().moduleLoadDone };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
updateUI((Map<String, Module>) result[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||
Intent intent = new Intent(getActivity(), Data.classMap.get(FlashActivity.class));
|
||||
intent.setData(data.getData()).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_reboot, menu);
|
||||
@@ -112,25 +109,25 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.reboot:
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
return true;
|
||||
case R.id.reboot_recovery:
|
||||
Shell.Async.su("/system/bin/reboot recovery");
|
||||
Shell.su("/system/bin/reboot recovery").submit();
|
||||
return true;
|
||||
case R.id.reboot_bootloader:
|
||||
Shell.Async.su("/system/bin/reboot bootloader");
|
||||
Shell.su("/system/bin/reboot bootloader").submit();
|
||||
return true;
|
||||
case R.id.reboot_download:
|
||||
Shell.Async.su("/system/bin/reboot download");
|
||||
Shell.su("/system/bin/reboot download").submit();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
private void updateUI(Map<String, Module> moduleMap) {
|
||||
listModules.clear();
|
||||
listModules.addAll(getApplication().moduleMap.values());
|
||||
listModules.addAll(moduleMap.values());
|
||||
if (listModules.size() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
@@ -1,10 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -14,25 +11,30 @@ import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
public class ReposFragment extends BaseFragment implements Topic.Subscriber {
|
||||
|
||||
private Unbinder unbinder;
|
||||
private MagiskManager mm;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||
|
||||
public static ReposAdapter adapter;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -42,47 +44,43 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
mm = getApplication();
|
||||
unbinder = new ReposFragment_ViewBinding(this, view);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(mm.repoLoadDone.isPending());
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
new UpdateRepos(true).exec();
|
||||
new UpdateRepos().exec(true);
|
||||
});
|
||||
|
||||
getActivity().setTitle(R.string.downloads);
|
||||
requireActivity().setTitle(R.string.downloads);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
adapter = new ReposAdapter(mm.repoDB, mm.moduleMap);
|
||||
recyclerView.setAdapter(adapter);
|
||||
super.onResume();
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.MODULE_LOAD_DONE, Topic.REPO_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTopicPublished(Topic topic) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Topic[] getSubscription() {
|
||||
return new Topic[] { mm.repoLoadDone };
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
if (topic == Topic.MODULE_LOAD_DONE) {
|
||||
adapter = new ReposAdapter(mm.repoDB, (Map<String, Module>) result[0]);
|
||||
mm.repoDB.registerAdapter(adapter);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
}
|
||||
if (Topic.isPublished(getSubscribedTopics())) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
|
||||
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,9 +106,9 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders, mm.repoOrder, (d, which) -> {
|
||||
mm.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, mm.repoOrder).apply();
|
||||
.setSingleChoiceItems(R.array.sorting_orders, Data.repoOrder, (d, which) -> {
|
||||
Data.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, Data.repoOrder).apply();
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
@@ -121,6 +119,6 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
mm.repoDB.unregisterAdapter();
|
||||
}
|
||||
}
|
@@ -0,0 +1,342 @@
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
Topic.Subscriber, Topic.AutoSubscriber {
|
||||
|
||||
private PreferenceScreen prefScreen;
|
||||
|
||||
private ListPreference updateChannel, suAccess, autoRes, suNotification,
|
||||
requestTimeout, multiuserMode, namespaceMode;
|
||||
private MagiskManager mm;
|
||||
private PreferenceCategory generalCatagory;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey);
|
||||
mm = Data.MM();
|
||||
prefScreen = getPreferenceScreen();
|
||||
|
||||
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||
Preference hideManager = findPreference("hide");
|
||||
Preference restoreManager = findPreference("restore");
|
||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
mm.repoDB.clearRepo();
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||
return true;
|
||||
});
|
||||
|
||||
updateChannel = (ListPreference) findPreference(Const.Key.UPDATE_CHANNEL);
|
||||
suAccess = (ListPreference) findPreference(Const.Key.ROOT_ACCESS);
|
||||
autoRes = (ListPreference) findPreference(Const.Key.SU_AUTO_RESPONSE);
|
||||
requestTimeout = (ListPreference) findPreference(Const.Key.SU_REQUEST_TIMEOUT);
|
||||
suNotification = (ListPreference) findPreference(Const.Key.SU_NOTIFICATION);
|
||||
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((p, o) -> {
|
||||
String prev =String.valueOf(Data.updateChannel);
|
||||
int channel = Integer.parseInt((String) o);
|
||||
if (channel == Const.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(requireActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(R.string.ok, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.CUSTOM_CHANNEL,
|
||||
url.getText().toString()).apply())
|
||||
.setNegativeButton(R.string.close, (d, i) ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.setOnCancelListener(d ->
|
||||
mm.prefs.edit().putString(Const.Key.UPDATE_CHANNEL, prev).apply())
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
setSummary();
|
||||
|
||||
// Disable dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserMode);
|
||||
}
|
||||
|
||||
// Disable re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reauth.setEnabled(false);
|
||||
reauth.setChecked(false);
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Disable fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
fingerprint.setEnabled(false);
|
||||
fingerprint.setChecked(false);
|
||||
fingerprint.setSummary(R.string.disable_fingerprint);
|
||||
}
|
||||
|
||||
if (Shell.rootAccess()) {
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
PatchAPK.hideManager(requireActivity());
|
||||
return true;
|
||||
});
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
} else {
|
||||
if (Download.checkNetworkStatus(mm)) {
|
||||
restoreManager.setOnPreferenceClickListener((pref) -> {
|
||||
Download.receive(
|
||||
requireActivity(), new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Data.exportPrefs();
|
||||
Shell.su("cp " + uri.getPath() + " /data/local/tmp/manager.apk").exec();
|
||||
if (ShellUtils.fastCmdResult("pm install /data/local/tmp/manager.apk")) {
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
RootUtils.uninstallPkg(context.getPackageName());
|
||||
return;
|
||||
}
|
||||
Shell.su("rm -f /data/local/tmp/manager.apk").exec();
|
||||
}
|
||||
},
|
||||
Data.managerLink,
|
||||
Utils.fmt("MagiskManager-v%s.apk", Data.remoteManagerVersionString)
|
||||
);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
}
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
} else {
|
||||
generalCatagory.removePreference(restoreManager);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess() || (Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
prefScreen.removePreference(suCategory);
|
||||
}
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
prefScreen.removePreference(magiskCategory);
|
||||
generalCatagory.removePreference(hideManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocalePreference(ListPreference lp) {
|
||||
CharSequence[] entries = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
CharSequence[] entryValues = new CharSequence[LocaleManager.locales.size() + 1];
|
||||
entries[0] = LocaleManager.getString(LocaleManager.defaultLocale, R.string.system_default);
|
||||
entryValues[0] = "";
|
||||
int i = 1;
|
||||
for (Locale locale : LocaleManager.locales) {
|
||||
entries[i] = locale.getDisplayName(locale);
|
||||
entryValues[i++] = locale.toLanguageTag();
|
||||
}
|
||||
lp.setEntries(entries);
|
||||
lp.setEntryValues(entryValues);
|
||||
lp.setSummary(LocaleManager.locale.getDisplayName(LocaleManager.locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mm.prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
Topic.subscribe(this);
|
||||
requireActivity().setTitle(R.string.settings);
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mm.prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
Topic.unsubscribe(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
switch (key) {
|
||||
case Const.Key.ROOT_ACCESS:
|
||||
case Const.Key.SU_MULTIUSER_MODE:
|
||||
case Const.Key.SU_MNT_NS:
|
||||
mm.mDB.setSettings(key, Utils.getPrefsInt(prefs, key));
|
||||
break;
|
||||
}
|
||||
Data.loadConfig();
|
||||
setSummary();
|
||||
switch (key) {
|
||||
case Const.Key.DARK_THEME:
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.COREONLY:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
try {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile();
|
||||
} catch (IOException ignored) {}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete();
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG);
|
||||
break;
|
||||
case Const.Key.MAGISKHIDE:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit();
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.HOSTS:
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("cp -af /system/etc/hosts " + Const.MAGISK_HOST_FILE,
|
||||
"mount -o bind " + Const.MAGISK_HOST_FILE + " /system/etc/hosts")
|
||||
.submit();
|
||||
} else {
|
||||
Shell.su("umount -l /system/etc/hosts",
|
||||
"rm -f " + Const.MAGISK_HOST_FILE)
|
||||
.submit();
|
||||
}
|
||||
break;
|
||||
case Const.Key.LOCALE:
|
||||
LocaleManager.setLocale(mm);
|
||||
requireActivity().recreate();
|
||||
break;
|
||||
case Const.Key.UPDATE_CHANNEL:
|
||||
case Const.Key.CUSTOM_CHANNEL:
|
||||
CheckUpdates.check();
|
||||
break;
|
||||
case Const.Key.CHECK_UPDATES:
|
||||
Utils.setupUpdateCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
String key = preference.getKey();
|
||||
switch (key) {
|
||||
case Const.Key.SU_FINGERPRINT:
|
||||
boolean checked = ((SwitchPreference) preference).isChecked();
|
||||
((SwitchPreference) preference).setChecked(!checked);
|
||||
FingerprintHelper.showAuthDialog(requireActivity(), () -> {
|
||||
((SwitchPreference) preference).setChecked(checked);
|
||||
Data.suFingerprint = checked;
|
||||
mm.mDB.setSettings(key, checked ? 1 : 0);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSummary() {
|
||||
updateChannel.setSummary(getResources()
|
||||
.getStringArray(R.array.update_channel)[Data.updateChannel]);
|
||||
suAccess.setSummary(getResources()
|
||||
.getStringArray(R.array.su_access)[Data.suAccessState]);
|
||||
autoRes.setSummary(getResources()
|
||||
.getStringArray(R.array.auto_response)[Data.suResponseType]);
|
||||
suNotification.setSummary(getResources()
|
||||
.getStringArray(R.array.su_notification)[Data.suNotificationType]);
|
||||
requestTimeout.setSummary(
|
||||
getString(R.string.request_timeout_summary,
|
||||
mm.prefs.getString(Const.Key.SU_REQUEST_TIMEOUT, "10")));
|
||||
multiuserMode.setSummary(getResources()
|
||||
.getStringArray(R.array.multiuser_summary)[Data.multiuserMode]);
|
||||
namespaceMode.setSummary(getResources()
|
||||
.getStringArray(R.array.namespace_summary)[Data.suNamespaceMode]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPublish(int topic, Object[] result) {
|
||||
setLocalePreference((ListPreference) findPreference(Const.Key.LOCALE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSubscribedTopics() {
|
||||
return new int[] {Topic.LOCALE_FETCH_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
return new PreferenceGroupAdapter(preferenceScreen) {
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
Preference preference = getItem(position);
|
||||
if (preference instanceof PreferenceCategory)
|
||||
setZeroPaddingToLayoutChildren(holder.itemView);
|
||||
else {
|
||||
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
|
||||
if (iconFrame != null) {
|
||||
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setZeroPaddingToLayoutChildren(View view) {
|
||||
if (!(view instanceof ViewGroup))
|
||||
return;
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
int childCount = viewGroup.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
setZeroPaddingToLayoutChildren(viewGroup.getChildAt(i));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
viewGroup.setPaddingRelative(0, viewGroup.getPaddingTop(), viewGroup.getPaddingEnd(), viewGroup.getPaddingBottom());
|
||||
else
|
||||
viewGroup.setPadding(0, viewGroup.getPaddingTop(), viewGroup.getPaddingRight(), viewGroup.getPaddingBottom());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -11,20 +9,19 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class SuLogFragment extends Fragment {
|
||||
public class SuLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private Unbinder unbinder;
|
||||
private MagiskManager mm;
|
||||
private SuLogAdapter adapter;
|
||||
|
||||
@Override
|
||||
@@ -44,8 +41,7 @@ public class SuLogFragment extends Fragment {
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||
unbinder = ButterKnife.bind(this, v);
|
||||
mm = getApplication();
|
||||
unbinder = new SuLogFragment_ViewBinding(this, v);
|
||||
adapter = new SuLogAdapter(mm.mDB);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
@@ -80,10 +76,4 @@ public class SuLogFragment extends Fragment {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
}
|
@@ -1,39 +1,53 @@
|
||||
package com.topjohnwu.magisk;
|
||||
package com.topjohnwu.magisk.fragments;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.components.BaseFragment;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.Unbinder;
|
||||
|
||||
public class SuperuserFragment extends Fragment {
|
||||
public class SuperuserFragment extends BaseFragment {
|
||||
|
||||
private Unbinder unbinder;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
|
||||
private PackageManager pm;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
|
||||
unbinder = ButterKnife.bind(this, view);
|
||||
unbinder = new SuperuserFragment_ViewBinding(this, view);
|
||||
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
MagiskManager mm = getApplication();
|
||||
pm = requireActivity().getPackageManager();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
requireActivity().setTitle(getString(R.string.superuser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
displayPolicyList();
|
||||
}
|
||||
|
||||
private void displayPolicyList() {
|
||||
List<Policy> policyList = mm.mDB.getPolicyList(pm);
|
||||
|
||||
if (policyList.size() == 0) {
|
||||
@@ -44,20 +58,6 @@ public class SuperuserFragment extends Fragment {
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getActivity().setTitle(getString(R.string.superuser));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unbinder.unbind();
|
||||
}
|
||||
|
||||
}
|
@@ -3,19 +3,33 @@ package com.topjohnwu.magisk.receivers;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.services.OnBootIntentService;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.services.OnBootService;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(new Intent(context, OnBootIntentService.class));
|
||||
} else {
|
||||
context.startService(new Intent(context, OnBootIntentService.class));
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
|
||||
switch (intent.getExtras().getString("action", "boot")) {
|
||||
case "request":
|
||||
Intent i = new Intent(context, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", intent.getStringExtra("socket"))
|
||||
.putExtra("version", 2)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
break;
|
||||
case "log":
|
||||
SuConnector.handleLogs(intent, 2);
|
||||
break;
|
||||
case "boot":
|
||||
OnBootService.enqueueWork(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,20 +6,21 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.asyncs.PatchAPK;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class ManagerUpdate extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Utils.dlAndReceive(
|
||||
Download.receive(
|
||||
context, new PatchedInstall(),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_LINK),
|
||||
intent.getStringExtra(Const.Key.INTENT_SET_FILENAME)
|
||||
@@ -31,13 +32,14 @@ public class ManagerUpdate extends BroadcastReceiver {
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
if (!context.getPackageName().equals(Const.ORIG_PKG_NAME)) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
String o = uri.getPath();
|
||||
String p = o.substring(0, o.lastIndexOf('.')) + "-patched.apk";
|
||||
String orig = uri.getPath();
|
||||
String patch = orig.substring(0, orig.lastIndexOf('.')) + "-patched.apk";
|
||||
try {
|
||||
PatchAPK.patchPackageID(o, new BufferedOutputStream(new FileOutputStream(p)),
|
||||
Const.ORIG_PKG_NAME, context.getPackageName());
|
||||
} catch (FileNotFoundException ignored) { }
|
||||
super.onDownloadDone(context, Uri.fromFile(new File(p)));
|
||||
JarMap apk = new JarMap(orig);
|
||||
PatchAPK.patchPackageID(apk, Const.ORIG_PKG_NAME, context.getPackageName());
|
||||
SignAPK.sign(apk, new BufferedOutputStream(new FileOutputStream(patch)));
|
||||
super.onDownloadDone(context, Uri.fromFile(new File(patch)));
|
||||
} catch (Exception ignored) { }
|
||||
});
|
||||
} else {
|
||||
super.onDownloadDone(context, uri);
|
||||
|
@@ -4,15 +4,15 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class PackageReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
String pkg = intent.getData().getEncodedSchemeSpecificPart();
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PackageReceiver extends BroadcastReceiver {
|
||||
break;
|
||||
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||
mm.mDB.deletePolicy(pkg);
|
||||
Shell.Async.su("magiskhide --rm " + pkg);
|
||||
Shell.su("magiskhide --rm " + pkg).submit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,6 @@ import com.topjohnwu.superuser.Shell;
|
||||
public class RebootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Shell.Async.su("/system/bin/reboot");
|
||||
Shell.su("/system/bin/reboot").submit();
|
||||
}
|
||||
}
|
||||
|
@@ -7,28 +7,24 @@ import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class ShortcutReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
MagiskManager mm = Data.MM();
|
||||
ShortcutManager manager = context.getSystemService(ShortcutManager.class);
|
||||
if (TextUtils.equals(intent.getAction(), Intent.ACTION_LOCALE_CHANGED)) {
|
||||
// It is triggered with locale change, manual load Magisk info
|
||||
mm.loadMagiskInfo();
|
||||
}
|
||||
manager.setDynamicShortcuts(getShortCuts(mm));
|
||||
}
|
||||
}
|
||||
@@ -36,12 +32,12 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
|
||||
private ArrayList<ShortcutInfo> getShortCuts(MagiskManager mm) {
|
||||
ArrayList<ShortcutInfo> shortCuts = new ArrayList<>();
|
||||
if (Shell.rootAccess() &&
|
||||
!(Const.USER_ID > 0 &&
|
||||
mm.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
boolean root = Shell.rootAccess();
|
||||
if (root && !(Const.USER_ID > 0 &&
|
||||
Data.multiuserMode == Const.Value.MULTIUSER_MODE_OWNER_MANAGED)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "superuser")
|
||||
.setShortLabel(mm.getString(R.string.superuser))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.setIntent(new Intent(mm, Data.classMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "superuser")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
@@ -49,11 +45,10 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
.setRank(0)
|
||||
.build());
|
||||
}
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode >= Const.MAGISK_VER.UNIFIED
|
||||
&& mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false)) {
|
||||
if (root && mm.prefs.getBoolean(Const.Key.MAGISKHIDE, false)) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "magiskhide")
|
||||
.setShortLabel(mm.getString(R.string.magiskhide))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.setIntent(new Intent(mm, Data.classMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
@@ -61,11 +56,10 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
.setRank(1)
|
||||
.build());
|
||||
}
|
||||
if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) &&
|
||||
Shell.rootAccess() && mm.magiskVersionCode >= 0) {
|
||||
if (!mm.prefs.getBoolean(Const.Key.COREONLY, false) && root && Data.magiskVersionCode >= 0) {
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "modules")
|
||||
.setShortLabel(mm.getString(R.string.modules))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.setIntent(new Intent(mm, Data.classMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "modules")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
@@ -74,7 +68,7 @@ public class ShortcutReceiver extends BroadcastReceiver {
|
||||
.build());
|
||||
shortCuts.add(new ShortcutInfo.Builder(mm, "downloads")
|
||||
.setShortLabel(mm.getString(R.string.download))
|
||||
.setIntent(new Intent(mm, SplashActivity.class)
|
||||
.setIntent(new Intent(mm, Data.classMap.get(SplashActivity.class))
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
|
||||
|
@@ -1,44 +0,0 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
|
||||
public class OnBootIntentService extends IntentService {
|
||||
|
||||
public OnBootIntentService() {
|
||||
super("OnBootIntentService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForeground(Const.ID.ONBOOT_NOTIFICATION_ID,
|
||||
new NotificationCompat.Builder(this, Const.ID.NOTIFICATION_CHANNEL)
|
||||
.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle("Startup Operations")
|
||||
.setContentText("Running startup operations...")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
/* Pixel 2 (XL) devices will need to patch dtbo.img.
|
||||
* However, that is not possible if Magisk is installed by
|
||||
* patching boot image with Magisk Manager and fastboot flash
|
||||
* the boot image, 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.
|
||||
* */
|
||||
MagiskManager.get().loadMagiskInfo();
|
||||
RootUtils.patchDTBO();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.topjohnwu.magisk.services;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.utils.NotificationMgr;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
|
||||
public class OnBootService extends JobIntentService {
|
||||
|
||||
public static void enqueueWork(Context context) {
|
||||
enqueueWork(context, Data.classMap.get(OnBootService.class), Const.ID.ONBOOT_SERVICE_ID, new Intent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
/* 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.
|
||||
* */
|
||||
if (Shell.rootAccess() && ShellUtils.fastCmdResult("mm_patch_dtbo"))
|
||||
NotificationMgr.dtboPatched();
|
||||
}
|
||||
}
|
@@ -4,15 +4,14 @@ import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
|
||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
public class UpdateCheckService extends JobService {
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
Utils.getMagiskManager(this).loadMagiskInfo();
|
||||
new CheckUpdates(true)
|
||||
.setCallBack(() -> jobFinished(params, false)).exec();
|
||||
Shell.getShell();
|
||||
CheckUpdates.check(() -> jobFinished(params, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,300 +1,21 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.SuRequestActivity;
|
||||
import com.topjohnwu.magisk.components.BaseActivity;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class RequestActivity extends Activity {
|
||||
|
||||
@BindView(R.id.su_popup) LinearLayout suPopup;
|
||||
@BindView(R.id.timeout) Spinner timeout;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appNameView;
|
||||
@BindView(R.id.package_name) TextView packageNameView;
|
||||
@BindView(R.id.grant_btn) Button grant_btn;
|
||||
@BindView(R.id.deny_btn) Button deny_btn;
|
||||
@BindView(R.id.fingerprint) ImageView fingerprintImg;
|
||||
@BindView(R.id.warning) TextView warning;
|
||||
|
||||
private String socketPath;
|
||||
private LocalSocket socket;
|
||||
private PackageManager pm;
|
||||
private MagiskManager mm;
|
||||
|
||||
private boolean hasTimeout;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
return R.style.SuRequest_Dark;
|
||||
}
|
||||
public class RequestActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
pm = getPackageManager();
|
||||
mm = Utils.getMagiskManager(this);
|
||||
mm.mDB.clearOutdated();
|
||||
|
||||
Intent intent = getIntent();
|
||||
socketPath = intent.getStringExtra("socket");
|
||||
hasTimeout = intent.getBooleanExtra("timeout", true);
|
||||
|
||||
new FileObserver(socketPath) {
|
||||
@Override
|
||||
public void onEvent(int fileEvent, String path) {
|
||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}.startWatching();
|
||||
|
||||
new SocketManager(this).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showRequest() {
|
||||
switch (mm.suResponseType) {
|
||||
case Const.Value.SU_AUTO_DENY:
|
||||
handleAction(Policy.DENY, 0);
|
||||
return;
|
||||
case Const.Value.SU_AUTO_ALLOW:
|
||||
handleAction(Policy.ALLOW, 0);
|
||||
return;
|
||||
case Const.Value.SU_PROMPT:
|
||||
default:
|
||||
}
|
||||
|
||||
// If not interactive, response directly
|
||||
if (policy.policy != Policy.INTERACTIVE) {
|
||||
handleAction();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_request);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
appNameView.setText(policy.appName);
|
||||
packageNameView.setText(policy.packageName);
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.allow_timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
|
||||
timer = new CountDownTimer(mm.suRequestTimeout * 1000, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
|
||||
}
|
||||
@Override
|
||||
public void onFinish() {
|
||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||
handleAction(Policy.DENY);
|
||||
}
|
||||
};
|
||||
|
||||
boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.startAuth();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
});
|
||||
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||
|
||||
if (hasTimeout) {
|
||||
timer.start();
|
||||
} else {
|
||||
cancelTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void handleAction() {
|
||||
String response;
|
||||
if (policy.policy == Policy.ALLOW) {
|
||||
response = "socket:ALLOW";
|
||||
} else {
|
||||
response = "socket:DENY";
|
||||
}
|
||||
try {
|
||||
socket.getOutputStream().write((response).getBytes());
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Intent intent = new Intent(this, Data.classMap.get(SuRequestActivity.class))
|
||||
.putExtra("socket", getIntent().getStringExtra("socket"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
void handleAction(int action) {
|
||||
handleAction(action, Const.Value.timeoutList[timeout.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
void handleAction(int action, int time) {
|
||||
policy.policy = action;
|
||||
if (time >= 0) {
|
||||
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||
mm.mDB.addPolicy(policy);
|
||||
}
|
||||
handleAction();
|
||||
}
|
||||
|
||||
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
|
||||
|
||||
SocketManager(Activity context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
socket = new LocalSocket();
|
||||
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
|
||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||
ContentValues payload = new ContentValues();
|
||||
|
||||
while (true) {
|
||||
int nameLen = is.readInt();
|
||||
byte[] nameBytes = new byte[nameLen];
|
||||
is.readFully(nameBytes);
|
||||
String name = new String(nameBytes);
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
|
||||
int dataLen = is.readInt();
|
||||
byte[] dataBytes = new byte[dataLen];
|
||||
is.readFully(dataBytes);
|
||||
String data = new String(dataBytes);
|
||||
payload.put(name, data);
|
||||
}
|
||||
|
||||
if (payload.getAsInteger("uid") == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int uid = payload.getAsInteger("uid");
|
||||
policy = mm.mDB.getPolicy(uid);
|
||||
if (policy == null) {
|
||||
policy = new Policy(uid, pm);
|
||||
}
|
||||
|
||||
/* Never allow com.topjohnwu.magisk (could be malware) */
|
||||
if (TextUtils.equals(policy.packageName, Const.ORIG_PKG_NAME))
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (result) {
|
||||
showRequest();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,88 +3,13 @@ package com.topjohnwu.magisk.superuser;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.util.Date;
|
||||
import com.topjohnwu.magisk.utils.SuConnector;
|
||||
|
||||
public class SuReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int fromUid, toUid, pid, mode;
|
||||
String command, action;
|
||||
Policy policy;
|
||||
|
||||
MagiskManager mm = Utils.getMagiskManager(context);
|
||||
|
||||
if (intent == null) return;
|
||||
|
||||
mode = intent.getIntExtra("mode", -1);
|
||||
if (mode < 0) return;
|
||||
|
||||
if (mode == Const.Value.NOTIFY_USER_TO_OWNER) {
|
||||
MagiskManager.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
|
||||
|
||||
action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
|
||||
policy = mm.mDB.getPolicy(fromUid);
|
||||
if (policy == null) {
|
||||
try {
|
||||
policy = new Policy(fromUid, context.getPackageManager());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
|
||||
String message;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
message = context.getString(R.string.su_allow_toast, policy.appName);
|
||||
log.action = true;
|
||||
break;
|
||||
case "deny":
|
||||
message = context.getString(R.string.su_deny_toast, policy.appName);
|
||||
log.action = false;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (policy.notification && mm.suNotificationType == Const.Value.NOTIFICATION_TOAST) {
|
||||
MagiskManager.toast(message, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
if (mode == Const.Value.NOTIFY_NORMAL_LOG && policy.logging) {
|
||||
toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
log.toUid = toUid;
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
mm.mDB.addLog(log);
|
||||
}
|
||||
SuConnector.handleLogs(intent, 1);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
import com.topjohnwu.utils.SignBoot;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
public class BootSigner {
|
||||
|
||||
@Keep
|
||||
|
@@ -1,15 +1,26 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.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;
|
||||
|
||||
@@ -27,16 +38,65 @@ public abstract class FingerprintHelper {
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
MagiskManager mm = Data.MM();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public static void showAuthDialog(Activity activity, Runnable onSuccess) {
|
||||
CustomAlertDialog dialog = new CustomAlertDialog(activity);
|
||||
CustomAlertDialog.ViewHolder vh = dialog.getViewHolder();
|
||||
try {
|
||||
FingerprintHelper helper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
vh.messageView.setTextColor(Color.RED);
|
||||
vh.messageView.setText(R.string.auth_fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
dialog.dismiss();
|
||||
onSuccess.run();
|
||||
}
|
||||
};
|
||||
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
|
||||
fingerprint.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50));
|
||||
Resources.Theme theme = activity.getTheme();
|
||||
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
|
||||
fingerprint.setTint(ta.getColor(0, Color.GRAY));
|
||||
ta.recycle();
|
||||
vh.messageView.setCompoundDrawables(null, null, null, fingerprint);
|
||||
vh.messageView.setCompoundDrawablePadding(Utils.dpInPx(20));
|
||||
vh.messageView.setGravity(Gravity.CENTER);
|
||||
dialog.setMessage(R.string.auth_fingerprint)
|
||||
.setNegativeButton(R.string.close, (d, w) -> helper.cancel())
|
||||
.setOnCancelListener(d -> helper.cancel())
|
||||
.show();
|
||||
helper.authenticate();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = mm.getSystemService(FingerprintManager.class);
|
||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
@@ -62,30 +122,10 @@ public abstract class FingerprintHelper {
|
||||
|
||||
public abstract void onAuthenticationFailed();
|
||||
|
||||
public void startAuth() {
|
||||
public void authenticate() {
|
||||
cancel = new CancellationSignal();
|
||||
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||
manager.authenticate(cryptoObject, cancel, 0, new FingerprintManager.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
FingerprintHelper.this.onAuthenticationSucceeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
FingerprintHelper.this.onAuthenticationFailed();
|
||||
}
|
||||
}, null);
|
||||
manager.authenticate(cryptoObject, cancel, 0, new Callback(), null);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
@@ -109,4 +149,26 @@ public abstract class FingerprintHelper {
|
||||
keygen.init(builder.build());
|
||||
return keygen.generateKey();
|
||||
}
|
||||
|
||||
private class Callback extends FingerprintManager.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
FingerprintHelper.this.onAuthenticationError(errorCode, errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
FingerprintHelper.this.onAuthenticationSucceeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
FingerprintHelper.this.onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,18 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
public interface ISafetyNetHelper {
|
||||
|
||||
int CAUSE_SERVICE_DISCONNECTED = 0x01;
|
||||
int CAUSE_NETWORK_LOST = 0x02;
|
||||
int RESPONSE_ERR = 0x04;
|
||||
int CONNECTION_FAIL = 0x08;
|
||||
int RESPONSE_ERR = 0x01;
|
||||
int CONNECTION_FAIL = 0x02;
|
||||
|
||||
int BASIC_PASS = 0x10;
|
||||
int CTS_PASS = 0x20;
|
||||
|
||||
void attest();
|
||||
|
||||
int getVersion();
|
||||
|
||||
interface Callback {
|
||||
@Keep
|
||||
void onResponse(int responseCode);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,77 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
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 void setLocale(MagiskManager mm) {
|
||||
String localeConfig = mm.prefs.getString(Const.Key.LOCALE, "");
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = Locale.forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
Resources res = mm.getResources();
|
||||
Configuration config = res.getConfiguration();
|
||||
config.setLocale(locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static String getString(Locale locale, @StringRes int id) {
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(locale);
|
||||
return Data.MM().createConfigurationContext(config).getString(id);
|
||||
}
|
||||
|
||||
public static void loadAvailableLocales() {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Resources res = Data.MM().getResources();
|
||||
Locale locale;
|
||||
|
||||
@StringRes int compareId = R.string.download_file_error;
|
||||
|
||||
// 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 = 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);
|
||||
});
|
||||
}
|
||||
}
|
@@ -3,8 +3,7 @@ package com.topjohnwu.magisk.utils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
|
||||
import java.util.Locale;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
|
||||
public class Logger {
|
||||
|
||||
|
@@ -0,0 +1,87 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
public class NotificationMgr {
|
||||
|
||||
public static void magiskUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(SplashActivity.class));
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(Data.classMap.get(SplashActivity.class));
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, Data.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void managerUpdate() {
|
||||
MagiskManager mm = Data.MM();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
Data.remoteManagerVersionString, Data.remoteManagerVersionCode);
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(ManagerUpdate.class));
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, Data.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void dtboPatched() {
|
||||
MagiskManager mm = Data.MM();
|
||||
|
||||
Intent intent = new Intent(mm, Data.classMap.get(RebootReceiver.class));
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.dtbo_patched_title))
|
||||
.setContentText(mm.getString(R.string.dtbo_patched_reboot))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
public class PatchAPK {
|
||||
|
||||
private static int findOffset(byte buf[], byte pattern[]) {
|
||||
int offset = -1;
|
||||
for (int i = 0; i < buf.length - pattern.length; ++i) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; ++j) {
|
||||
if (buf[i + j] != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* It seems that AAPT sometimes generate another type of string format */
|
||||
private static boolean fallbackPatch(byte xml[], String from, String to) {
|
||||
|
||||
byte[] target = new byte[from.length() * 2 + 2];
|
||||
for (int i = 0; i < from.length(); ++i) {
|
||||
target[i * 2] = (byte) from.charAt(i);
|
||||
}
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
byte[] dest = new byte[target.length - 2];
|
||||
for (int i = 0; i < to.length(); ++i) {
|
||||
dest[i * 2] = (byte) to.charAt(i);
|
||||
}
|
||||
System.arraycopy(dest, 0, xml, offset, dest.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean findAndPatch(byte xml[], String from, String to) {
|
||||
byte target[] = (from + '\0').getBytes();
|
||||
int offset = findOffset(xml, target);
|
||||
if (offset < 0)
|
||||
return fallbackPatch(xml, from, to);
|
||||
System.arraycopy(to.getBytes(), 0, xml, offset, to.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean patchPackageID(String fileName, OutputStream out, String from, String to) {
|
||||
try {
|
||||
JarMap apk = new JarMap(fileName);
|
||||
JarEntry je = apk.getJarEntry(Const.ANDROID_MANIFEST);
|
||||
byte xml[] = apk.getRawData(je);
|
||||
|
||||
if (!findAndPatch(xml, from, to))
|
||||
return false;
|
||||
if (!findAndPatch(xml, from + ".provider", to + ".provider"))
|
||||
return false;
|
||||
|
||||
// Write in changes
|
||||
apk.getOutputStream(je).write(xml);
|
||||
|
||||
// Sign the APK
|
||||
ZipUtils.signZip(apk, out);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,14 +1,38 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
public class RootUtils {
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
|
||||
InputStream managerUtils = context.getResources().openRawResource(R.raw.utils);
|
||||
job.add(magiskUtils).add(managerUtils);
|
||||
|
||||
public static void init() {
|
||||
if (Shell.rootAccess()) {
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
SuFile file = new SuFile("/sbin/.core/img");
|
||||
if (file.exists()) {
|
||||
@@ -19,20 +43,17 @@ public class RootUtils {
|
||||
Const.MAGISK_PATH = new SuFile("/magisk");
|
||||
}
|
||||
Const.MAGISK_HOST_FILE = new SuFile(Const.MAGISK_PATH + "/.core/hosts");
|
||||
}
|
||||
}
|
||||
|
||||
public static void uninstallPkg(String pkg) {
|
||||
Shell.Sync.su("db_clean " + Const.USER_ID, "pm uninstall " + pkg);
|
||||
}
|
||||
|
||||
public static void patchDTBO() {
|
||||
if (Shell.rootAccess()) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (mm.magiskVersionCode >= Const.MAGISK_VER.DTBO_SUPPORT) {
|
||||
if (Boolean.parseBoolean(ShellUtils.fastCmd("mm_patch_dtbo")))
|
||||
ShowUI.dtboPatchedNotification();
|
||||
}
|
||||
Data.loadMagiskInfo();
|
||||
} else {
|
||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||
job.add(nonroot);
|
||||
}
|
||||
|
||||
job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec();
|
||||
|
||||
Data.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Data.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.superuser.BusyBox;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ShellInitializer extends Shell.Initializer {
|
||||
|
||||
static {
|
||||
BusyBox.BB_PATH = new File(Const.BUSYBOX_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRootShellInit(Context context, @NonNull Shell shell) throws Exception {
|
||||
try (InputStream magiskUtils = context.getResources().openRawResource(R.raw.util_functions);
|
||||
InputStream managerUtils = context.getResources().openRawResource(R.raw.utils)
|
||||
) {
|
||||
shell.execTask((in, err, out) -> {
|
||||
ShellUtils.pump(magiskUtils, in);
|
||||
ShellUtils.pump(managerUtils, in);
|
||||
});
|
||||
}
|
||||
shell.run(null, null,
|
||||
"mount_partitions",
|
||||
"get_flags",
|
||||
"run_migrations");
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,273 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.asyncs.RestoreImages;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShowUI {
|
||||
|
||||
public static void magiskUpdateNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
Intent intent = new Intent(mm, SplashActivity.class);
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(SplashActivity.class);
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, mm.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void managerUpdateNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
|
||||
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void dtboPatchedNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
Intent intent = new Intent(mm, RebootReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
.setContentTitle(mm.getString(R.string.dtbo_patched_title))
|
||||
.setContentText(mm.getString(R.string.dtbo_patched_reboot))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void envFixDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.env_fix_title)
|
||||
.setMessage(R.string.env_fix_msg)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||
Utils.dlAndReceive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
new InstallMagisk(activity, uri).exec();
|
||||
}
|
||||
}, mm.magiskLink, filename);
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void magiskInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.fmt("Magisk-v%s(%d).zip",
|
||||
mm.remoteMagiskVersionString, mm.remoteMagiskVersionCode);
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.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));
|
||||
}
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.select_method)
|
||||
.setItems(
|
||||
options.toArray(new String [0]),
|
||||
(dialog, idx) -> {
|
||||
DownloadReceiver receiver = null;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
if (mm.remoteMagiskVersionCode < 1400) {
|
||||
MagiskManager.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
MagiskManager.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
((com.topjohnwu.magisk.components.Activity) activity)
|
||||
.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT
|
||||
&& resultCode == Activity.RESULT_OK && data != null) {
|
||||
Utils.dlAndReceive(
|
||||
activity,
|
||||
new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
mm.startActivity(intent);
|
||||
}
|
||||
},
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
case 0:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
SnackbarMaker.showUri(activity, uri);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
|
||||
Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri).putExtra(Const.Key.FLASH_ACTION,
|
||||
Const.Value.FLASH_SECOND_SLOT);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
default:
|
||||
}
|
||||
Utils.dlAndReceive(activity, receiver, mm.magiskLink, filename);
|
||||
}
|
||||
).show();
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(mm.magiskNoteLink)) {
|
||||
b.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (mm.magiskNoteLink.contains("forum.xda-developers")) {
|
||||
// Open forum links in browser
|
||||
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.magiskNoteLink));
|
||||
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mm.startActivity(openLink);
|
||||
} else {
|
||||
new MarkDownWindow(activity, null, mm.magiskNoteLink).exec();
|
||||
}
|
||||
});
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
|
||||
public static void managerInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.fmt("MagiskManager-v%s(%d).apk",
|
||||
mm.remoteManagerVersionString, mm.remoteManagerVersionCode);
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg, filename))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
com.topjohnwu.magisk.components.Activity.runWithPermission(activity,
|
||||
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, () -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_FILENAME, filename);
|
||||
mm.sendBroadcast(intent);
|
||||
});
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null);
|
||||
if (!TextUtils.isEmpty(mm.managerNoteLink)) {
|
||||
b.setNeutralButton(R.string.app_changelog, (d, i) ->
|
||||
new MarkDownWindow(activity, null, mm.managerNoteLink).exec());
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
|
||||
public static void uninstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
AlertDialog.Builder b = new AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.uninstall_magisk_title)
|
||||
.setMessage(R.string.uninstall_magisk_msg)
|
||||
.setNeutralButton(R.string.restore_img, (d, i) -> new RestoreImages(activity).exec());
|
||||
if (!TextUtils.isEmpty(mm.uninstallerLink)) {
|
||||
b.setPositiveButton(R.string.complete_uninstall, (d, i) ->
|
||||
Utils.dlAndReceive(activity, new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Context context, Uri uri) {
|
||||
Intent intent = new Intent(context, FlashActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}, mm.uninstallerLink, "magisk-uninstaller.zip"));
|
||||
}
|
||||
b.show();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user