mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-15 17:11:47 -08:00
Compare commits
269 Commits
manager-v4
...
manager-v5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13bf1b27b4 | ||
|
|
f742bb1c47 | ||
|
|
aa0b9e2db2 | ||
|
|
c10076f7ed | ||
|
|
bcd92499f2 | ||
|
|
b2bb0d4f72 | ||
|
|
e140481f14 | ||
|
|
186bd11463 | ||
|
|
a0490d6687 | ||
|
|
beef740ade | ||
|
|
2ac7786a90 | ||
|
|
a3fb5e910f | ||
|
|
319afe86b5 | ||
|
|
762ab66b86 | ||
|
|
0c239a42de | ||
|
|
e9322fba26 | ||
|
|
39b6df27b3 | ||
|
|
b1ee284e7f | ||
|
|
e986332bf2 | ||
|
|
48f9b27381 | ||
|
|
42a6e0dd10 | ||
|
|
d4798b02ac | ||
|
|
963edfe8ab | ||
|
|
53237f3ae0 | ||
|
|
64da9281a4 | ||
|
|
ab7fd9799d | ||
|
|
f6bcc84251 | ||
|
|
35dc3d9df9 | ||
|
|
566714a75d | ||
|
|
c92f30b122 | ||
|
|
294ad094c4 | ||
|
|
c1a0f520f9 | ||
|
|
773c24b7fc | ||
|
|
8f926c7ca9 | ||
|
|
c562cbc2bb | ||
|
|
3fbbb0865a | ||
|
|
7d5f612a48 | ||
|
|
4a5a36440b | ||
|
|
43dd5cfea1 | ||
|
|
7b5fec1842 | ||
|
|
5762ded601 | ||
|
|
a3abb86daa | ||
|
|
4f5c656b05 | ||
|
|
a31cddbe7b | ||
|
|
b4ecd93f1c | ||
|
|
0acc23e058 | ||
|
|
cdd5f9b628 | ||
|
|
4c9f5f4655 | ||
|
|
b80ba13cb4 | ||
|
|
8260bdc09c | ||
|
|
24f856e02b | ||
|
|
3aa619b928 | ||
|
|
4cb5e98d94 | ||
|
|
272910575e | ||
|
|
a15a62f4bc | ||
|
|
53cf11db8c | ||
|
|
01052fbe47 | ||
|
|
a5e1e075c7 | ||
|
|
6be32ac688 | ||
|
|
b362c0ef38 | ||
|
|
bba9969e31 | ||
|
|
007ba24809 | ||
|
|
df21539311 | ||
|
|
2592cb6019 | ||
|
|
f7df17a7ed | ||
|
|
62f42b72f8 | ||
|
|
a1ba4fda6f | ||
|
|
1c06b04c45 | ||
|
|
2ee22fd374 | ||
|
|
4c230d9e61 | ||
|
|
727294fbbe | ||
|
|
478c43969b | ||
|
|
79b5303350 | ||
|
|
ce4b742b25 | ||
|
|
a9dc15bda5 | ||
|
|
ba6387ff5c | ||
|
|
8fa98508b7 | ||
|
|
decdbaecf9 | ||
|
|
6d87cf9be0 | ||
|
|
94f434c4a6 | ||
|
|
7ba867c30b | ||
|
|
3424395e10 | ||
|
|
926c7359a2 | ||
|
|
ec0af99a2e | ||
|
|
b4d948886c | ||
|
|
4d8d79372a | ||
|
|
04a589722c | ||
|
|
d4a10e2873 | ||
|
|
4998ad6c7e | ||
|
|
a07ca5ff50 | ||
|
|
f07e7571ab | ||
|
|
834c16485c | ||
|
|
04a4265ef3 | ||
|
|
0ec473195d | ||
|
|
0bf09256b0 | ||
|
|
db8fd2c913 | ||
|
|
dbe6e5b3d7 | ||
|
|
cc81cd446b | ||
|
|
439c7118f1 | ||
|
|
d8154a5815 | ||
|
|
4e3787bc0d | ||
|
|
02e0955924 | ||
|
|
a78950e822 | ||
|
|
1ce1a94a35 | ||
|
|
977b6d9f67 | ||
|
|
b5e6dbd797 | ||
|
|
833e6688f1 | ||
|
|
bc22c9f84f | ||
|
|
2149a7d116 | ||
|
|
29175d2c17 | ||
|
|
803454d5c8 | ||
|
|
36cf32dc42 | ||
|
|
657f4ab303 | ||
|
|
ea6552615d | ||
|
|
4bf3287fce | ||
|
|
832c2034c2 | ||
|
|
b0aa26e1f1 | ||
|
|
e52baeb967 | ||
|
|
8268eb9a83 | ||
|
|
3cc458abd9 | ||
|
|
337b4c4268 | ||
|
|
001f8657f6 | ||
|
|
ea884e7fa1 | ||
|
|
1b1394cf5d | ||
|
|
1eef930dbb | ||
|
|
1e175e74ed | ||
|
|
75a46c365e | ||
|
|
8e7b8825f5 | ||
|
|
2ecbca303b | ||
|
|
8195a4d616 | ||
|
|
7ba40f925f | ||
|
|
345cd1795f | ||
|
|
959aaee045 | ||
|
|
53477f0f59 | ||
|
|
5716218f41 | ||
|
|
9df6b9d5c0 | ||
|
|
ec46031d36 | ||
|
|
55b84d166a | ||
|
|
34ae8bacec | ||
|
|
cb4e5ca0f7 | ||
|
|
0ba45468c4 | ||
|
|
710502784e | ||
|
|
0275a8558d | ||
|
|
58acc75cf6 | ||
|
|
874ababb9f | ||
|
|
3771e6b0cd | ||
|
|
33eaefa966 | ||
|
|
cd7e236d57 | ||
|
|
54c0b7c7d5 | ||
|
|
a2177daec2 | ||
|
|
628386b453 | ||
|
|
b222bfb3e0 | ||
|
|
ab199d883d | ||
|
|
356065d1ee | ||
|
|
76e7c5623d | ||
|
|
085fba050a | ||
|
|
295334d3ac | ||
|
|
36124ddca4 | ||
|
|
bd6585765e | ||
|
|
c325deb4ed | ||
|
|
73bb0b10ee | ||
|
|
72820b162c | ||
|
|
89e5b8d057 | ||
|
|
da4f53ebbb | ||
|
|
8458553b74 | ||
|
|
55ecc41d06 | ||
|
|
28fcdf2cbb | ||
|
|
24087679a8 | ||
|
|
5ac6a8cb4a | ||
|
|
668d85d14e | ||
|
|
c11a3dc95c | ||
|
|
56f57c20a2 | ||
|
|
240d14779a | ||
|
|
3550d1e61c | ||
|
|
6513ad249c | ||
|
|
50297b1880 | ||
|
|
f189b78b9e | ||
|
|
5c0250f495 | ||
|
|
2093f726e9 | ||
|
|
10efe3859d | ||
|
|
6933bcf7bb | ||
|
|
2ea046cd80 | ||
|
|
f4097a372b | ||
|
|
87ea2a2bef | ||
|
|
cc14a1c361 | ||
|
|
bcdface60d | ||
|
|
4dc9419d2e | ||
|
|
d2bcac813e | ||
|
|
080c37a7f6 | ||
|
|
f9a3838db6 | ||
|
|
1e61db104b | ||
|
|
30a9c7718d | ||
|
|
34b052b5d3 | ||
|
|
aaa12853ad | ||
|
|
b0ab55b0bf | ||
|
|
d2f8496f4e | ||
|
|
1a69b16d36 | ||
|
|
b5e8673e62 | ||
|
|
264c6a50b6 | ||
|
|
493642eb38 | ||
|
|
28d42b9164 | ||
|
|
42f29062ca | ||
|
|
c4377ed6c2 | ||
|
|
7d283ed65f | ||
|
|
bf1f941e50 | ||
|
|
789fef34ba | ||
|
|
1daf5a611c | ||
|
|
6aed1db67e | ||
|
|
cf68854770 | ||
|
|
711392c73b | ||
|
|
9573c32481 | ||
|
|
a15f80f79d | ||
|
|
23e7475f06 | ||
|
|
1eb571b787 | ||
|
|
dd3b716d85 | ||
|
|
28649c07e3 | ||
|
|
961e02be0d | ||
|
|
a161491bfd | ||
|
|
e0b4d1c1e4 | ||
|
|
fd4aaab137 | ||
|
|
42d14d5ca2 | ||
|
|
d3ff482c9b | ||
|
|
f682368eeb | ||
|
|
4a5d033efb | ||
|
|
343161b195 | ||
|
|
bc576a9659 | ||
|
|
19e407fcc4 | ||
|
|
bc7327d004 | ||
|
|
666fa1c797 | ||
|
|
0eda4a7821 | ||
|
|
862058fd2b | ||
|
|
69e5bcd57d | ||
|
|
efeddda328 | ||
|
|
ff6938280e | ||
|
|
1e4425b30f | ||
|
|
b5d1d8cdad | ||
|
|
029be5ccca | ||
|
|
29c2d785b5 | ||
|
|
abda8cfa32 | ||
|
|
44e7d79d4c | ||
|
|
9a1dc8ee0e | ||
|
|
27879c3f01 | ||
|
|
29096eb5d7 | ||
|
|
a573baea03 | ||
|
|
5af07c4531 | ||
|
|
44e36feb09 | ||
|
|
2a7d996881 | ||
|
|
738f943a68 | ||
|
|
47e62a5681 | ||
|
|
1ecbfd7590 | ||
|
|
67c139a04b | ||
|
|
31cc008249 | ||
|
|
9cb026439d | ||
|
|
e6f10176c6 | ||
|
|
0917c79470 | ||
|
|
597baa986d | ||
|
|
75cc4b4843 | ||
|
|
aac088d496 | ||
|
|
a822e5bbc5 | ||
|
|
c527249c21 | ||
|
|
9ef798f534 | ||
|
|
e69b99f089 | ||
|
|
55b8079e86 | ||
|
|
e272dbe9af | ||
|
|
962f8354ac | ||
|
|
20e4a960f7 | ||
|
|
82249cb50a | ||
|
|
fad417e553 | ||
|
|
5ba692f50c |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,6 +3,10 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
.idea/
|
.idea/
|
||||||
/build
|
/build
|
||||||
app/app-release.apk
|
app/release
|
||||||
*.hprof
|
*.hprof
|
||||||
app/.externalNativeBuild/
|
app/.externalNativeBuild/
|
||||||
|
*.sh
|
||||||
|
public.certificate.x509.pem
|
||||||
|
private.key.pk8
|
||||||
|
*.apk
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# Magisk Manager
|
# Magisk Manager
|
||||||
I used Java 8 features in the app, and official supported is added in Android Studio 2.4
|
This is one of the submodules used in Magisk. The project is licensed under GPL v3 (or newer).
|
||||||
Aware that Android Studio 2.4 is currently in the Preview Channel
|
More info are written in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk)
|
||||||
You need to install CMake and NDK to build the zipadjust library for zip preprocessing
|
|
||||||
|
## Building Notes
|
||||||
|
You need to install CMake and NDK to build the zipadjust library.
|
||||||
|
There are several files required to let Magisk Manager work properly, and they can be copied by using the build script in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk). These files are: `magisk_uninstaller.sh`, `util_functions.sh`, `public.certificate.x509.pem`, and `private.key.pk8` under the `app/src/main/assets` folder.
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 26
|
||||||
buildToolsVersion "25.0.3"
|
buildToolsVersion "26.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.topjohnwu.magisk"
|
applicationId "com.topjohnwu.magisk"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 26
|
||||||
versionCode 31
|
versionCode 57
|
||||||
versionName "4.3.3"
|
versionName "5.4.0"
|
||||||
ndk {
|
ndk {
|
||||||
moduleName 'zipadjust'
|
moduleName 'zipadjust'
|
||||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
}
|
}
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
argument('butterknife.debuggable', 'false')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +33,8 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
dexOptions {
|
dexOptions {
|
||||||
preDexLibraries = true
|
preDexLibraries true
|
||||||
|
javaMaxHeapSize "2g"
|
||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -38,31 +44,23 @@ android {
|
|||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation'
|
||||||
}
|
}
|
||||||
retrolambda {
|
|
||||||
javaVersion JavaVersion.VERSION_1_7
|
|
||||||
defaultMethods false
|
|
||||||
incremental true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven { url "https://maven.google.com" }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation project(':common')
|
||||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
implementation project(':jarsigner')
|
||||||
compile 'com.android.support:cardview-v7:25.3.1'
|
implementation 'com.android.support:recyclerview-v7:26.1.0'
|
||||||
compile 'com.android.support:design:25.3.1'
|
implementation 'com.android.support:cardview-v7:26.1.0'
|
||||||
compile 'com.android.support:support-v4:25.3.1'
|
implementation 'com.android.support:design:26.1.0'
|
||||||
compile 'com.jakewharton:butterknife:8.5.1'
|
implementation 'com.android.support:support-v4:26.1.0'
|
||||||
compile 'com.thoughtbot:expandablerecyclerview:1.4'
|
implementation 'com.jakewharton:butterknife:8.8.1'
|
||||||
compile 'us.feras.mdv:markdownview:1.1.0'
|
implementation 'com.atlassian.commonmark:commonmark:0.10.0'
|
||||||
compile 'com.madgag.spongycastle:core:1.54.0.0'
|
implementation 'org.kamranzafar:jtar:2.3'
|
||||||
compile 'com.madgag.spongycastle:prov:1.54.0.0'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
|
||||||
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
|
|
||||||
compile 'com.madgag.spongycastle:pg:1.54.0.0'
|
|
||||||
compile 'com.google.android.gms:play-services-safetynet:9.0.1'
|
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
app/proguard-rules.pro
vendored
14
app/proguard-rules.pro
vendored
@@ -16,15 +16,9 @@
|
|||||||
# public *;
|
# public *;
|
||||||
#}
|
#}
|
||||||
|
|
||||||
-keep class android.support.v7.internal.** { *; }
|
# Keep all names, we are open source anyway :)
|
||||||
-keep interface android.support.v7.internal.** { *; }
|
-keepnames class ** { *; }
|
||||||
-keep class android.support.v7.** { *; }
|
|
||||||
-keep interface android.support.v7.** { *; }
|
|
||||||
|
|
||||||
# SpongyCastle
|
# BouncyCastle
|
||||||
-keep class org.spongycastle.** { *; }
|
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||||
-dontwarn javax.naming.**
|
-dontwarn javax.naming.**
|
||||||
|
|
||||||
# retrolambda
|
|
||||||
-dontwarn java.lang.invoke.*
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="com.topjohnwu.magisk"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.topjohnwu.magisk">
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MagiskManager"
|
android:name=".MagiskManager"
|
||||||
@@ -17,12 +17,12 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
android:directBootAware="true"
|
||||||
|
tools:ignore="UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:exported="true"/>
|
android:exported="true" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SplashActivity"
|
android:name=".SplashActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
@@ -30,16 +30,22 @@
|
|||||||
android:theme="@style/SplashTheme">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".AboutActivity"
|
android:name=".AboutActivity"
|
||||||
android:theme="@style/AppTheme.Transparent"/>
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:theme="@style/AppTheme.Transparent" />
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
|
<activity
|
||||||
|
android:name=".FlashActivity"
|
||||||
|
android:screenOrientation="nosensor"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
|
android:theme="@style/AppTheme.Transparent" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".superuser.RequestActivity"
|
android:name=".superuser.RequestActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
@@ -52,21 +58,27 @@
|
|||||||
android:taskAffinity="internal.superuser"
|
android:taskAffinity="internal.superuser"
|
||||||
android:theme="@style/SuRequest" />
|
android:theme="@style/SuRequest" />
|
||||||
|
|
||||||
<receiver
|
<receiver android:name=".superuser.SuReceiver" />
|
||||||
android:name=".superuser.SuReceiver" />
|
|
||||||
|
|
||||||
<receiver android:name=".receivers.BootReceiver">
|
<receiver android:name=".receivers.BootReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver android:name=".receivers.PackageReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
|
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||||
|
|
||||||
<service android:name=".services.BootupIntentService" />
|
<data android:scheme="package" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<receiver android:name=".receivers.ManagerUpdate" />
|
||||||
|
|
||||||
|
<service android:name=".services.OnBootIntentService" />
|
||||||
<service
|
<service
|
||||||
android:name=".services.UpdateCheckService"
|
android:name=".services.UpdateCheckService"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:exported="true"
|
||||||
android:exported="true" />
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="android.support.v4.content.FileProvider"
|
||||||
@@ -78,10 +90,11 @@
|
|||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<!-- Hardcode GMS version -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.version"
|
android:name="com.google.android.gms.version"
|
||||||
android:value="@integer/google_play_services_version" />
|
android:value="11400000" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
[ -z $BOOTMODE ] && BOOTMODE=false
|
|
||||||
|
|
||||||
# This path should work in any cases
|
|
||||||
TMPDIR=/dev/tmp
|
|
||||||
|
|
||||||
BOOTTMP=$TMPDIR/boottmp
|
|
||||||
MAGISKBIN=/data/magisk
|
|
||||||
CHROMEDIR=$MAGISKBIN/chromeos
|
|
||||||
|
|
||||||
SYSTEMLIB=/system/lib
|
|
||||||
[ -d /system/lib64 ] && SYSTEMLIB=/system/lib64
|
|
||||||
|
|
||||||
# Default permissions
|
|
||||||
umask 022
|
|
||||||
|
|
||||||
ui_print_wrapper() {
|
|
||||||
type ui_print >/dev/null && ui_print "$1" || echo "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
grep_prop() {
|
|
||||||
REGEX="s/^$1=//p"
|
|
||||||
shift
|
|
||||||
FILES=$@
|
|
||||||
if [ -z "$FILES" ]; then
|
|
||||||
FILES='/system/build.prop'
|
|
||||||
fi
|
|
||||||
cat $FILES 2>/dev/null | sed -n "$REGEX" | head -n 1
|
|
||||||
}
|
|
||||||
|
|
||||||
find_boot_image() {
|
|
||||||
if [ -z "$BOOTIMAGE" ]; then
|
|
||||||
for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do
|
|
||||||
BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || readlink /dev/block/platform/*/by-name/$PARTITION || readlink /dev/block/platform/*/*/by-name/$PARTITION`
|
|
||||||
if [ ! -z "$BOOTIMAGE" ]; then break; fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
if [ -z "$BOOTIMAGE" ]; then
|
|
||||||
FSTAB="/etc/recovery.fstab"
|
|
||||||
[ ! -f "$FSTAB" ] && FSTAB="/etc/recovery.fstab.bak"
|
|
||||||
[ -f "$FSTAB" ] && BOOTIMAGE=`grep -E '\b/boot\b' "$FSTAB" | grep -oE '/dev/[a-zA-Z0-9_./-]*'`
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
# Set permissions
|
|
||||||
chmod -R 755 $CHROMEDIR/futility $MAGISKBIN 2>/dev/null
|
|
||||||
# Temporary busybox for installation
|
|
||||||
mkdir -p $TMPDIR/busybox
|
|
||||||
$MAGISKBIN/busybox --install -s $TMPDIR/busybox
|
|
||||||
rm -f $TMPDIR/busybox/su $TMPDIR/busybox/sh $TMPDIR/busybox/reboot
|
|
||||||
PATH=$TMPDIR/busybox:$PATH
|
|
||||||
|
|
||||||
# Find the boot image
|
|
||||||
find_boot_image
|
|
||||||
if [ -z "$BOOTIMAGE" ]; then
|
|
||||||
ui_print_wrapper "! Unable to detect boot image"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ui_print_wrapper "- Found Boot Image: $BOOTIMAGE"
|
|
||||||
|
|
||||||
rm -rf $BOOTTMP 2>/dev/null
|
|
||||||
mkdir -p $BOOTTMP
|
|
||||||
cd $BOOTTMP
|
|
||||||
|
|
||||||
ui_print_wrapper "- Unpacking boot image"
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --unpack $BOOTIMAGE
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
ui_print_wrapper "! Unable to unpack boot image"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update our previous backup to new format if exists
|
|
||||||
if [ -f /data/stock_boot.img ]; then
|
|
||||||
SHA1=`LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --sha1 /data/stock_boot.img | tail -n 1`
|
|
||||||
STOCKDUMP=/data/stock_boot_${SHA1}.img
|
|
||||||
mv /data/stock_boot.img $STOCKDUMP
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --compress $STOCKDUMP
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect boot image state
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --cpio-test ramdisk.cpio
|
|
||||||
case $? in
|
|
||||||
0 )
|
|
||||||
ui_print_wrapper "! Magisk is not installed!"
|
|
||||||
ui_print_wrapper "! Nothing to uninstall"
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
1 )
|
|
||||||
# Find SHA1 of stock boot image
|
|
||||||
if [ -z $SHA1 ]; then
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --cpio-extract ramdisk.cpio init.magisk.rc init.magisk.rc
|
|
||||||
SHA1=`grep_prop "# STOCKSHA1" init.magisk.rc`
|
|
||||||
[ ! -z $SHA1 ] && STOCKDUMP=/data/stock_boot_${SHA1}.img
|
|
||||||
rm -f init.magisk.rc
|
|
||||||
fi
|
|
||||||
if [ -f ${STOCKDUMP}.gz ]; then
|
|
||||||
ui_print_wrapper "- Boot image backup found!"
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --decompress ${STOCKDUMP}.gz stock_boot.img
|
|
||||||
else
|
|
||||||
ui_print_wrapper "! Boot image backup unavailable"
|
|
||||||
ui_print_wrapper "- Restoring ramdisk with backup"
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --cpio-restore ramdisk.cpio
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --repack $BOOTIMAGE stock_boot.img
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
2 )
|
|
||||||
ui_print_wrapper "- SuperSU patched image detected"
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --cpio-restore ramdisk.cpio
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $MAGISKBIN/magiskboot --repack $BOOTIMAGE stock_boot.img
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Sign chromeos boot
|
|
||||||
if [ -f chromeos ]; then
|
|
||||||
echo > config
|
|
||||||
echo > bootloader
|
|
||||||
LD_LIBRARY_PATH=$SYSTEMLIB $CHROMEDIR/futility vbutil_kernel --pack stock_boot.img.signed --keyblock $CHROMEDIR/kernel.keyblock --signprivate $CHROMEDIR/kernel_data_key.vbprivk --version 1 --vmlinuz stock_boot.img --config config --arch arm --bootloader bootloader --flags 0x1
|
|
||||||
rm -f stock_boot.img
|
|
||||||
mv stock_boot.img.signed stock_boot.img
|
|
||||||
fi
|
|
||||||
|
|
||||||
ui_print_wrapper "- Flashing stock/reverted image"
|
|
||||||
[ ! -L "$BOOTIMAGE" ] && dd if=/dev/zero of=$BOOTIMAGE bs=4096 2>/dev/null
|
|
||||||
dd if=stock_boot.img of=$BOOTIMAGE bs=4096
|
|
||||||
|
|
||||||
ui_print_wrapper "- Removing Magisk files"
|
|
||||||
rm -rf /cache/magisk.log /cache/last_magisk.log /cache/magiskhide.log /cache/.disable_magisk \
|
|
||||||
/cache/magisk /cache/magisk_merge /cache/magisk_mount /cache/unblock /cache/magisk_uninstaller.sh \
|
|
||||||
/data/Magisk.apk /data/magisk.apk /data/magisk.img /data/magisk_merge.img \
|
|
||||||
/data/busybox /data/magisk /data/custom_ramdisk_patch.sh 2>/dev/null
|
|
||||||
|
|
||||||
$BOOTMODE && reboot
|
|
||||||
Binary file not shown.
@@ -1,27 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
|
|
||||||
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
|
||||||
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
|
||||||
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
|
||||||
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
|
|
||||||
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
|
||||||
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
|
||||||
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
|
||||||
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
|
|
||||||
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
|
|
||||||
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
|
|
||||||
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
|
|
||||||
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
|
|
||||||
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
|
|
||||||
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
|
|
||||||
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
|
||||||
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
|
||||||
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
|
||||||
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
|
|
||||||
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
|
|
||||||
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
|
|
||||||
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
|
|
||||||
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
|
|
||||||
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
|
|
||||||
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
@@ -13,23 +12,22 @@ import android.text.Spanned;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.components.AboutCardRow;
|
import com.topjohnwu.magisk.components.AboutCardRow;
|
||||||
import com.topjohnwu.magisk.components.Activity;
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class AboutActivity extends Activity {
|
public class AboutActivity extends Activity {
|
||||||
|
|
||||||
private static final String DONATION_URL = "http://topjohnwu.github.io/donate";
|
private static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
|
||||||
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||||
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
||||||
|
|
||||||
@@ -45,10 +43,8 @@ public class AboutActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
|
if (getMagiskManager().isDarkTheme) {
|
||||||
Logger.dev("AboutActivity: Theme is " + theme);
|
setTheme(R.style.AppTheme_Transparent_Dark);
|
||||||
if (getApplicationContext().isDarkTheme) {
|
|
||||||
setTheme(R.style.AppTheme_Dark);
|
|
||||||
}
|
}
|
||||||
setContentView(R.layout.activity_about);
|
setContentView(R.layout.activity_about);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
@@ -62,7 +58,7 @@ public class AboutActivity extends Activity {
|
|||||||
ab.setDisplayHomeAsUpEnabled(true);
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
appVersionInfo.setSummary(BuildConfig.VERSION_NAME);
|
appVersionInfo.setSummary(String.format(Locale.US, "%s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||||
|
|
||||||
String changes = null;
|
String changes = null;
|
||||||
try (InputStream is = getAssets().open("changelog.html")) {
|
try (InputStream is = getAssets().open("changelog.html")) {
|
||||||
@@ -135,18 +131,4 @@ public class AboutActivity extends Activity {
|
|||||||
setFloating();
|
setFloating();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFloating() {
|
|
||||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
|
||||||
if (isTablet) {
|
|
||||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
|
||||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
|
||||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
|
||||||
params.alpha = 1.0f;
|
|
||||||
params.dimAmount = 0.6f;
|
|
||||||
params.flags |= 2;
|
|
||||||
getWindow().setAttributes(params);
|
|
||||||
setFinishOnTouchOutside(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
153
app/src/main/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
153
app/src/main/java/com/topjohnwu/magisk/FlashActivity.java
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.FlashZip;
|
||||||
|
import com.topjohnwu.magisk.asyncs.InstallMagisk;
|
||||||
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
|
import com.topjohnwu.magisk.container.AdaptiveList;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
|
||||||
|
public class FlashActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String SET_ACTION = "action";
|
||||||
|
public static final String SET_BOOT = "boot";
|
||||||
|
public static final String SET_ENC = "enc";
|
||||||
|
public static final String SET_VERITY = "verity";
|
||||||
|
|
||||||
|
public static final String FLASH_ZIP = "flash";
|
||||||
|
public static final String PATCH_BOOT = "patch";
|
||||||
|
public static final String FLASH_MAGISK = "magisk";
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
@BindView(R.id.flash_logs) RecyclerView flashLogs;
|
||||||
|
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
||||||
|
@BindView(R.id.reboot) Button reboot;
|
||||||
|
|
||||||
|
@OnClick(R.id.no_thanks)
|
||||||
|
public void dismiss() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.reboot)
|
||||||
|
public void reboot() {
|
||||||
|
getShell().su_raw("reboot");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_flash);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
AdaptiveList<String> rootShellOutput = new AdaptiveList<>(flashLogs);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setTitle(R.string.flashing);
|
||||||
|
}
|
||||||
|
setFloating();
|
||||||
|
setFinishOnTouchOutside(false);
|
||||||
|
if (!Shell.rootAccess())
|
||||||
|
reboot.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
flashLogs.setAdapter(new FlashLogAdapter(rootShellOutput));
|
||||||
|
|
||||||
|
// We must receive a Uri of the target zip
|
||||||
|
Intent intent = getIntent();
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
|
||||||
|
boolean keepEnc = intent.getBooleanExtra(SET_ENC, false);
|
||||||
|
boolean keepVerity = intent.getBooleanExtra(SET_VERITY, false);
|
||||||
|
|
||||||
|
switch (getIntent().getStringExtra(SET_ACTION)) {
|
||||||
|
case FLASH_ZIP:
|
||||||
|
new FlashZip(this, uri, rootShellOutput)
|
||||||
|
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||||
|
.exec();
|
||||||
|
break;
|
||||||
|
case PATCH_BOOT:
|
||||||
|
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, (Uri) intent.getParcelableExtra(SET_BOOT))
|
||||||
|
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||||
|
.exec();
|
||||||
|
break;
|
||||||
|
case FLASH_MAGISK:
|
||||||
|
String boot = intent.getStringExtra(SET_BOOT);
|
||||||
|
if (getMagiskManager().remoteMagiskVersionCode < 1370) {
|
||||||
|
// Use legacy installation method
|
||||||
|
getShell().su_raw(
|
||||||
|
"echo \"BOOTIMAGE=" + boot + "\" > /dev/.magisk",
|
||||||
|
"echo \"KEEPFORCEENCRYPT=" + keepEnc + "\" >> /dev/.magisk",
|
||||||
|
"echo \"KEEPVERITY=" + keepVerity + "\" >> /dev/.magisk"
|
||||||
|
);
|
||||||
|
new FlashZip(this, uri, rootShellOutput)
|
||||||
|
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||||
|
.exec();
|
||||||
|
} else {
|
||||||
|
// Use new installation method
|
||||||
|
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, boot)
|
||||||
|
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
|
||||||
|
.exec();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
// Prevent user accidentally press back button
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||||
|
|
||||||
|
private List<String> mList;
|
||||||
|
|
||||||
|
FlashLogAdapter(List<String> list) {
|
||||||
|
mList = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.list_item_flashlog, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
holder.text.setText(mList.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mList.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.textView) TextView text;
|
||||||
|
|
||||||
|
public ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.CountDownTimer;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.ProcessMagiskZip;
|
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
|
||||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import butterknife.Unbinder;
|
|
||||||
|
|
||||||
public class InstallFragment extends Fragment implements CallbackEvent.Listener<Void> {
|
|
||||||
|
|
||||||
|
|
||||||
private static final String UNINSTALLER = "magisk_uninstaller.sh";
|
|
||||||
|
|
||||||
@BindView(R.id.current_version_title) TextView currentVersionTitle;
|
|
||||||
@BindView(R.id.install_title) TextView installTitle;
|
|
||||||
@BindView(R.id.block_spinner) Spinner spinner;
|
|
||||||
@BindView(R.id.detect_bootimage) Button detectButton;
|
|
||||||
@BindView(R.id.install_button) CardView installButton;
|
|
||||||
@BindView(R.id.install_text) TextView installText;
|
|
||||||
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
|
||||||
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
|
||||||
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
|
||||||
|
|
||||||
@OnClick(R.id.detect_bootimage)
|
|
||||||
public void toAutoDetect() {
|
|
||||||
if (magiskManager.bootBlock != null) {
|
|
||||||
spinner.setSelection(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.install_button)
|
|
||||||
public void install() {
|
|
||||||
String bootImage = null;
|
|
||||||
if (magiskManager.blockList != null) {
|
|
||||||
int idx = spinner.getSelectedItemPosition();
|
|
||||||
if (magiskManager.bootBlock != null) {
|
|
||||||
if (idx > 0) {
|
|
||||||
bootImage = magiskManager.blockList.get(idx - 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (idx > 0) {
|
|
||||||
bootImage = magiskManager.blockList.get(idx - 1);
|
|
||||||
} else {
|
|
||||||
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String finalBootImage = bootImage;
|
|
||||||
String filename = "Magisk-v" + magiskManager.remoteMagiskVersion + ".zip";
|
|
||||||
new AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(getString(R.string.repo_install_title, getString(R.string.magisk)))
|
|
||||||
.setMessage(getString(R.string.repo_install_msg, filename))
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(Shell.rootAccess() ? R.string.install : R.string.download,
|
|
||||||
(dialogInterface, i) -> Utils.dlAndReceive(
|
|
||||||
getActivity(),
|
|
||||||
new DownloadReceiver() {
|
|
||||||
private String boot = finalBootImage;
|
|
||||||
private boolean enc = keepEncChkbox.isChecked();
|
|
||||||
private boolean verity = keepVerityChkbox.isChecked();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDownloadDone(Uri uri) {
|
|
||||||
new ProcessMagiskZip(getActivity(), uri, boot, enc, verity).exec();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
magiskManager.magiskLink,
|
|
||||||
Utils.getLegalFilename(filename)))
|
|
||||||
.setNeutralButton(R.string.release_notes, (dialog, which) -> {
|
|
||||||
if (magiskManager.releaseNoteLink != null) {
|
|
||||||
Intent openReleaseNoteLink = new Intent(Intent.ACTION_VIEW, Uri.parse(magiskManager.releaseNoteLink));
|
|
||||||
openReleaseNoteLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
magiskManager.startActivity(openReleaseNoteLink);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.uninstall_button)
|
|
||||||
public void uninstall() {
|
|
||||||
new AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.uninstall_magisk_title)
|
|
||||||
.setMessage(R.string.uninstall_magisk_msg)
|
|
||||||
.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
|
|
||||||
try {
|
|
||||||
InputStream in = magiskManager.getAssets().open(UNINSTALLER);
|
|
||||||
File uninstaller = new File(magiskManager.getCacheDir(), UNINSTALLER);
|
|
||||||
FileOutputStream out = new FileOutputStream(uninstaller);
|
|
||||||
byte[] bytes = new byte[1024];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(bytes)) != -1) {
|
|
||||||
out.write(bytes, 0, read);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
ProgressDialog progress = new ProgressDialog(getActivity());
|
|
||||||
progress.setTitle(R.string.reboot);
|
|
||||||
progress.show();
|
|
||||||
new CountDownTimer(5000, 1000) {
|
|
||||||
@Override
|
|
||||||
public void onTick(long millisUntilFinished) {
|
|
||||||
progress.setMessage(getString(R.string.reboot_countdown, millisUntilFinished / 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinish() {
|
|
||||||
progress.setMessage(getString(R.string.reboot_countdown, 0));
|
|
||||||
Shell.su(true, "mv -f " + uninstaller + " /cache/" + UNINSTALLER,
|
|
||||||
"reboot");
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Unbinder unbinder;
|
|
||||||
private MagiskManager magiskManager;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.fragment_install, container, false);
|
|
||||||
unbinder = ButterKnife.bind(this, v);
|
|
||||||
magiskManager = getApplication();
|
|
||||||
if (magiskManager.magiskVersion < 0) {
|
|
||||||
currentVersionTitle.setText(getString(R.string.current_magisk_title, getString(R.string.version_none)));
|
|
||||||
} else {
|
|
||||||
currentVersionTitle.setText(getString(R.string.current_magisk_title, "v" + magiskManager.magiskVersionString));
|
|
||||||
}
|
|
||||||
installTitle.setText(getString(R.string.install_magisk_title, "v" + String.format(Locale.US, "%.1f", magiskManager.remoteMagiskVersion)));
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
if (magiskManager.blockList == null || !Shell.rootAccess()) {
|
|
||||||
uninstallButton.setVisibility(View.GONE);
|
|
||||||
installText.setText(R.string.download);
|
|
||||||
detectButton.setEnabled(false);
|
|
||||||
keepEncChkbox.setEnabled(false);
|
|
||||||
keepVerityChkbox.setEnabled(false);
|
|
||||||
spinner.setEnabled(false);
|
|
||||||
} else {
|
|
||||||
uninstallButton.setVisibility(magiskManager.magiskVersion > 10.3 ? View.VISIBLE : View.GONE);
|
|
||||||
installText.setText(R.string.download_install);
|
|
||||||
detectButton.setEnabled(true);
|
|
||||||
keepEncChkbox.setEnabled(true);
|
|
||||||
keepVerityChkbox.setEnabled(true);
|
|
||||||
spinner.setEnabled(true);
|
|
||||||
|
|
||||||
List<String> items = new ArrayList<>();
|
|
||||||
if (magiskManager.bootBlock != null) {
|
|
||||||
items.add(getString(R.string.auto_detect, magiskManager.bootBlock));
|
|
||||||
} else {
|
|
||||||
items.add(getString(R.string.cannot_auto_detect));
|
|
||||||
}
|
|
||||||
items.addAll(magiskManager.blockList);
|
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
|
|
||||||
android.R.layout.simple_spinner_item, items);
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
spinner.setAdapter(adapter);
|
|
||||||
toAutoDetect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
getActivity().setTitle(R.string.install);
|
|
||||||
magiskManager.blockDetectionDone.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
magiskManager.blockDetectionDone.unRegister(this);
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
unbinder.unbind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,8 @@ public class LogFragment extends Fragment {
|
|||||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||||
unbinder = ButterKnife.bind(this, v);
|
unbinder = ButterKnife.bind(this, v);
|
||||||
|
|
||||||
|
((MainActivity) getActivity()).toolbar.setElevation(0);
|
||||||
|
|
||||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||||
|
|
||||||
if (getApplication().isSuClient) {
|
if (getApplication().isSuClient) {
|
||||||
|
|||||||
368
app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java
Normal file
368
app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckSafetyNet;
|
||||||
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 static final int CAUSE_SERVICE_DISCONNECTED = 0x00001;
|
||||||
|
public static final int CAUSE_NETWORK_LOST = 0x00010;
|
||||||
|
public static final int RESPONSE_ERR = 0x00100;
|
||||||
|
|
||||||
|
public static final int BASIC_PASS = 0x01000;
|
||||||
|
public static final int CTS_PASS = 0x10000;
|
||||||
|
|
||||||
|
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.magisk_update_card) CardView magiskUpdateCard;
|
||||||
|
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
|
||||||
|
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
||||||
|
@BindView(R.id.magisk_update_progress) ProgressBar magiskUpdateProgress;
|
||||||
|
|
||||||
|
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
||||||
|
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
||||||
|
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
|
||||||
|
@BindView(R.id.root_status) TextView rootStatusText;
|
||||||
|
|
||||||
|
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
|
||||||
|
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
|
||||||
|
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
||||||
|
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
||||||
|
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
||||||
|
@BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
|
||||||
|
@BindView(R.id.cts_status) TextView ctsStatusText;
|
||||||
|
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
|
||||||
|
@BindView(R.id.basic_status) TextView basicStatusText;
|
||||||
|
|
||||||
|
@BindView(R.id.bootimage_card) CardView bootImageCard;
|
||||||
|
@BindView(R.id.block_spinner) Spinner spinner;
|
||||||
|
@BindView(R.id.detect_bootimage) Button detectButton;
|
||||||
|
@BindView(R.id.install_option_card) CardView installOptionCard;
|
||||||
|
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
|
||||||
|
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
|
||||||
|
@BindView(R.id.install_button) CardView installButton;
|
||||||
|
@BindView(R.id.install_text) TextView installText;
|
||||||
|
@BindView(R.id.uninstall_button) CardView uninstallButton;
|
||||||
|
|
||||||
|
@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.blue500) int colorInfo;
|
||||||
|
|
||||||
|
@OnClick(R.id.safetyNet_title)
|
||||||
|
void safetyNet() {
|
||||||
|
Runnable task = () -> {
|
||||||
|
safetyNetProgress.setVisibility(View.VISIBLE);
|
||||||
|
safetyNetRefreshIcon.setVisibility(View.GONE);
|
||||||
|
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
||||||
|
new CheckSafetyNet(getActivity()).exec();
|
||||||
|
collapse();
|
||||||
|
};
|
||||||
|
if (mm.snet_version < 0) {
|
||||||
|
// Show dialog
|
||||||
|
new AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.proprietary_title)
|
||||||
|
.setMessage(R.string.proprietary_notice)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.yes, (d, i) -> task.run())
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.install_button)
|
||||||
|
void install() {
|
||||||
|
shownDialog = true;
|
||||||
|
|
||||||
|
// Show Manager update first
|
||||||
|
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
|
Utils.showManagerInstallDialog(getActivity());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
|
||||||
|
Utils.showMagiskInstallDialog(this,
|
||||||
|
keepEncChkbox.isChecked(), keepVerityChkbox.isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.uninstall_button)
|
||||||
|
void uninstall() {
|
||||||
|
Utils.showUninstallDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(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();
|
||||||
|
|
||||||
|
expandableContainer.expandLayout = expandLayout;
|
||||||
|
setupExpandable();
|
||||||
|
|
||||||
|
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
mm.getMagiskInfo();
|
||||||
|
updateUI();
|
||||||
|
|
||||||
|
magiskUpdateText.setText(R.string.checking_for_updates);
|
||||||
|
magiskUpdateProgress.setVisibility(View.VISIBLE);
|
||||||
|
magiskUpdateIcon.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
||||||
|
|
||||||
|
mm.safetyNetDone.hasPublished = false;
|
||||||
|
mm.updateCheckDone.hasPublished = false;
|
||||||
|
mm.remoteMagiskVersionString = null;
|
||||||
|
mm.remoteMagiskVersionCode = -1;
|
||||||
|
collapse();
|
||||||
|
|
||||||
|
shownDialog = false;
|
||||||
|
|
||||||
|
// Trigger state check
|
||||||
|
if (Utils.checkNetworkStatus(mm)) {
|
||||||
|
new CheckUpdates(getActivity()).exec();
|
||||||
|
} else {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
if (topic == mm.updateCheckDone) {
|
||||||
|
updateCheckUI();
|
||||||
|
} else if (topic == mm.safetyNetDone) {
|
||||||
|
updateSafetyNetUI((int) result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedBootImage() {
|
||||||
|
if (Shell.rootAccess()) {
|
||||||
|
if (mm.bootBlock != null) {
|
||||||
|
return mm.bootBlock;
|
||||||
|
} else {
|
||||||
|
int idx = spinner.getSelectedItemPosition();
|
||||||
|
if (idx > 0) {
|
||||||
|
return mm.blockList.get(idx - 1);
|
||||||
|
} else {
|
||||||
|
SnackbarMaker.make(getActivity(),
|
||||||
|
R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
((MainActivity) getActivity()).checkHideSection();
|
||||||
|
|
||||||
|
boolean hasNetwork = Utils.checkNetworkStatus(getActivity());
|
||||||
|
boolean hasRoot = Shell.rootAccess();
|
||||||
|
boolean isUpToDate = mm.magiskVersionCode > 1300;
|
||||||
|
|
||||||
|
magiskUpdateCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
bootImageCard.setVisibility(hasNetwork && hasRoot ? View.VISIBLE : View.GONE);
|
||||||
|
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
|
||||||
|
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
int image, color;
|
||||||
|
|
||||||
|
if (mm.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));
|
||||||
|
}
|
||||||
|
|
||||||
|
magiskStatusIcon.setImageResource(image);
|
||||||
|
magiskStatusIcon.setColorFilter(color);
|
||||||
|
|
||||||
|
switch (Shell.rootStatus) {
|
||||||
|
case 0:
|
||||||
|
color = colorBad;
|
||||||
|
image = R.drawable.ic_cancel;
|
||||||
|
rootStatusText.setText(R.string.not_rooted);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (mm.suVersion != null) {
|
||||||
|
color = colorOK;
|
||||||
|
image = R.drawable.ic_check_circle;
|
||||||
|
rootStatusText.setText(mm.suVersion);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case -1:
|
||||||
|
default:
|
||||||
|
color = colorNeutral;
|
||||||
|
image = R.drawable.ic_help;
|
||||||
|
rootStatusText.setText(R.string.root_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootStatusIcon.setImageResource(image);
|
||||||
|
rootStatusIcon.setColorFilter(color);
|
||||||
|
|
||||||
|
List<String> items = new ArrayList<>();
|
||||||
|
if (mm.bootBlock != null) {
|
||||||
|
items.add(getString(R.string.auto_detect, mm.bootBlock));
|
||||||
|
spinner.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
items.add(getString(R.string.cannot_auto_detect));
|
||||||
|
if (mm.blockList != null)
|
||||||
|
items.addAll(mm.blockList);
|
||||||
|
}
|
||||||
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
|
||||||
|
android.R.layout.simple_spinner_item, items);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
spinner.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCheckUI() {
|
||||||
|
int image, color;
|
||||||
|
|
||||||
|
if (mm.remoteMagiskVersionCode < 0) {
|
||||||
|
color = colorNeutral;
|
||||||
|
image = R.drawable.ic_help;
|
||||||
|
magiskUpdateText.setText(R.string.cannot_check_updates);
|
||||||
|
} else {
|
||||||
|
color = colorOK;
|
||||||
|
image = R.drawable.ic_check_circle;
|
||||||
|
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
|
||||||
|
}
|
||||||
|
|
||||||
|
installButton.setVisibility(View.VISIBLE);
|
||||||
|
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
|
||||||
|
installText.setText(getString(R.string.update, getString(R.string.app_name)));
|
||||||
|
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
|
||||||
|
installText.setText(getString(R.string.update, getString(R.string.magisk)));
|
||||||
|
} else {
|
||||||
|
installText.setText(R.string.install);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|
||||||
|
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE)) {
|
||||||
|
install();
|
||||||
|
}
|
||||||
|
|
||||||
|
magiskUpdateIcon.setImageResource(image);
|
||||||
|
magiskUpdateIcon.setColorFilter(color);
|
||||||
|
magiskUpdateIcon.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
magiskUpdateProgress.setVisibility(View.GONE);
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSafetyNetUI(int response) {
|
||||||
|
safetyNetProgress.setVisibility(View.GONE);
|
||||||
|
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
|
||||||
|
if (response < 0) {
|
||||||
|
safetyNetStatusText.setText(R.string.safetyNet_api_error);
|
||||||
|
} else if ((response & 0x111) == 0) {
|
||||||
|
safetyNetStatusText.setText(R.string.safetyNet_check_success);
|
||||||
|
|
||||||
|
boolean b;
|
||||||
|
b = (response & CTS_PASS) != 0;
|
||||||
|
ctsStatusText.setText("ctsProfile: " + b);
|
||||||
|
ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||||
|
ctsStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||||
|
|
||||||
|
b = (response & BASIC_PASS) != 0;
|
||||||
|
basicStatusText.setText("basicIntegrity: " + b);
|
||||||
|
basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel);
|
||||||
|
basicStatusIcon.setColorFilter(b ? colorOK : colorBad);
|
||||||
|
|
||||||
|
expand();
|
||||||
|
} else {
|
||||||
|
@StringRes int resid;
|
||||||
|
switch (response) {
|
||||||
|
case CAUSE_SERVICE_DISCONNECTED:
|
||||||
|
resid = R.string.safetyNet_network_loss;
|
||||||
|
break;
|
||||||
|
case CAUSE_NETWORK_LOST:
|
||||||
|
resid = R.string.safetyNet_service_disconnected;
|
||||||
|
break;
|
||||||
|
case RESPONSE_ERR:
|
||||||
|
default:
|
||||||
|
resid = R.string.safetyNet_res_invalid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
safetyNetStatusText.setText(resid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -15,16 +12,14 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.SearchView;
|
import android.widget.SearchView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
||||||
import com.topjohnwu.magisk.asyncs.MagiskHide;
|
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.Unbinder;
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
public class MagiskHideFragment extends Fragment implements CallbackEvent.Listener<Void> {
|
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
private Unbinder unbinder;
|
private Unbinder unbinder;
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
@@ -46,13 +41,12 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
|
|||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||||
unbinder = ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
lastFilter = "";
|
||||||
PackageManager packageManager = getActivity().getPackageManager();
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> new MagiskHide(getActivity()).list());
|
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
|
||||||
|
|
||||||
appAdapter = new ApplicationAdapter(packageManager);
|
appAdapter = new ApplicationAdapter(getActivity());
|
||||||
recyclerView.setAdapter(appAdapter);
|
recyclerView.setAdapter(appAdapter);
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
searchListener = new SearchView.OnQueryTextListener() {
|
||||||
@@ -71,9 +65,7 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (getApplication().magiskHideDone.isTriggered) {
|
getActivity().setTitle(R.string.magiskhide);
|
||||||
onTrigger(getApplication().magiskHideDone);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -81,23 +73,10 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
inflater.inflate(R.menu.menu_magiskhide, menu);
|
||||||
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.app_search));
|
SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
||||||
search.setOnQueryTextListener(searchListener);
|
search.setOnQueryTextListener(searchListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
getActivity().setTitle(R.string.magiskhide);
|
|
||||||
getApplication().magiskHideDone.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
getApplication().magiskHideDone.unRegister(this);
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
@@ -105,12 +84,13 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
Logger.dev("MagiskHideFragment: UI refresh");
|
|
||||||
appAdapter.setLists(getApplication().appList, getApplication().magiskHideList);
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
if (!TextUtils.isEmpty(lastFilter)) {
|
appAdapter.filter(lastFilter);
|
||||||
appAdapter.filter(lastFilter);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getApplication().magiskHideDone };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,10 @@ package com.topjohnwu.magisk;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -24,7 +19,7 @@ import android.widget.ScrollView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.SerialTask;
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
@@ -34,7 +29,6 @@ import java.io.File;
|
|||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
@@ -45,25 +39,18 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
private static final String MAGISK_LOG = "/cache/magisk.log";
|
private static final String MAGISK_LOG = "/cache/magisk.log";
|
||||||
|
|
||||||
private Unbinder unbinder;
|
private Unbinder unbinder;
|
||||||
|
|
||||||
@BindView(R.id.txtLog) TextView txtLog;
|
@BindView(R.id.txtLog) TextView txtLog;
|
||||||
@BindView(R.id.svLog) ScrollView svLog;
|
@BindView(R.id.svLog) ScrollView svLog;
|
||||||
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
|
||||||
|
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||||
|
|
||||||
private MenuItem mClickedMenuItem;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||||
unbinder = ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
txtLog.setTextIsSelectable(true);
|
txtLog.setTextIsSelectable(true);
|
||||||
|
|
||||||
@@ -97,13 +84,14 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
mClickedMenuItem = item;
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_refresh:
|
case R.id.menu_refresh:
|
||||||
new LogManager().read();
|
new LogManager().read();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_save:
|
case R.id.menu_save:
|
||||||
new LogManager().save();
|
Utils.runWithPermission(getActivity(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
() -> new LogManager().save());
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_clear:
|
case R.id.menu_clear:
|
||||||
new LogManager().clear();
|
new LogManager().clear();
|
||||||
@@ -113,24 +101,14 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private class LogManager extends ParallelTask<Object, Void, Object> {
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
private int mode;
|
||||||
if (requestCode == 0) {
|
private File targetFile;
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (mClickedMenuItem != null) {
|
LogManager() {
|
||||||
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500);
|
super(MagiskLogFragment.this.getActivity());
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SnackbarMaker.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class LogManager extends SerialTask<Object, Void, Object> {
|
|
||||||
|
|
||||||
int mode;
|
|
||||||
File targetFile;
|
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
@Override
|
@Override
|
||||||
@@ -138,34 +116,16 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
mode = (int) params[0];
|
mode = (int) params[0];
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 0:
|
case 0:
|
||||||
List<String> logList = Utils.readFile(MAGISK_LOG);
|
StringBuildingList logList = new StringBuildingList();
|
||||||
|
getShell().su(logList, "cat " + MAGISK_LOG);
|
||||||
if (Utils.isValidShellResponse(logList)) {
|
return logList.toString();
|
||||||
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
|
|
||||||
for (String s : logList) {
|
|
||||||
llog.append(s).append("\n");
|
|
||||||
}
|
|
||||||
return llog.toString();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
Shell.su("echo > " + MAGISK_LOG);
|
getShell().su_raw("echo -n > " + MAGISK_LOG);
|
||||||
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
String filename = String.format(
|
String filename = String.format(
|
||||||
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
|
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
|
||||||
@@ -173,26 +133,21 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||||
|
|
||||||
targetFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MagiskManager/" + filename);
|
targetFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
||||||
|
|
||||||
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|
||||||
|| (targetFile.exists() && !targetFile.delete())) {
|
|| (targetFile.exists() && !targetFile.delete())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> in = Utils.readFile(MAGISK_LOG);
|
try (FileWriter out = new FileWriter(targetFile)) {
|
||||||
|
FileWritingList fileWritingList = new FileWritingList(out);
|
||||||
if (Utils.isValidShellResponse(in)) {
|
getShell().su(fileWritingList, "cat " + MAGISK_LOG);
|
||||||
try (FileWriter out = new FileWriter(targetFile)) {
|
} catch (IOException e) {
|
||||||
for (String line : in)
|
e.printStackTrace();
|
||||||
out.write(line + "\n");
|
return false;
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -200,12 +155,10 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Object o) {
|
protected void onPostExecute(Object o) {
|
||||||
if (o == null) return;
|
if (o == null) return;
|
||||||
boolean bool;
|
|
||||||
String llog;
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
llog = (String) o;
|
String llog = (String) o;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
if (TextUtils.isEmpty(llog))
|
if (TextUtils.isEmpty(llog))
|
||||||
txtLog.setText(R.string.log_is_empty);
|
txtLog.setText(R.string.log_is_empty);
|
||||||
@@ -215,27 +168,64 @@ public class MagiskLogFragment extends Fragment {
|
|||||||
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
|
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
bool = (boolean) o;
|
boolean bool = (boolean) o;
|
||||||
if (bool) {
|
if (bool) {
|
||||||
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
|
getMagiskManager().toast(targetFile.toString(), Toast.LENGTH_LONG);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
|
getMagiskManager().toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void read() {
|
void read() {
|
||||||
exec(0);
|
exec(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
void clear() {
|
||||||
exec(1);
|
exec(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() {
|
void save() {
|
||||||
exec(2);
|
exec(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class StringBuildingList extends Shell.AbstractList<String> {
|
||||||
|
|
||||||
|
StringBuilder builder;
|
||||||
|
|
||||||
|
StringBuildingList() {
|
||||||
|
builder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String s) {
|
||||||
|
builder.append(s).append("\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FileWritingList extends Shell.AbstractList<String> {
|
||||||
|
|
||||||
|
private FileWriter writer;
|
||||||
|
|
||||||
|
FileWritingList(FileWriter out) {
|
||||||
|
writer = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(String s) {
|
||||||
|
try {
|
||||||
|
writer.write(s + "\n");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,180 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.job.JobScheduler;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
import com.topjohnwu.magisk.asyncs.DownloadBusybox;
|
||||||
import com.topjohnwu.magisk.superuser.Policy;
|
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
|
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||||
|
import com.topjohnwu.magisk.container.Module;
|
||||||
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.services.UpdateCheckService;
|
||||||
|
import com.topjohnwu.magisk.superuser.SuReceiver;
|
||||||
|
import com.topjohnwu.magisk.superuser.SuRequestActivity;
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.magisk.utils.ValueSortedMap;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class MagiskManager extends Application {
|
public class MagiskManager extends Application {
|
||||||
|
|
||||||
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
||||||
public static final String MAGISK_HIDE_PATH = "/magisk/.core/magiskhide/";
|
public static final String MAGISK_HOST_FILE = "/magisk/.core/hosts";
|
||||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||||
public static final String MAGISK_PATH = "/magisk";
|
public static final String MAGISK_PATH = "/magisk";
|
||||||
public static final String INTENT_SECTION = "section";
|
public static final String INTENT_SECTION = "section";
|
||||||
|
public static final String INTENT_VERSION = "version";
|
||||||
|
public static final String INTENT_LINK = "link";
|
||||||
|
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
||||||
|
public static final String DISABLE_INDICATION_PROP = "ro.magisk.disable";
|
||||||
|
public static final String NOTIFICATION_CHANNEL = "magisk_update_notice";
|
||||||
|
public static final String BUSYBOXPATH = "/dev/magisk/bin";
|
||||||
|
public static final int UPDATE_SERVICE_ID = 1;
|
||||||
|
|
||||||
// Events
|
// Topics
|
||||||
public final CallbackEvent<Void> blockDetectionDone = new CallbackEvent<>();
|
public final Topic magiskHideDone = new Topic();
|
||||||
public final CallbackEvent<Void> magiskHideDone = new CallbackEvent<>();
|
public final Topic reloadActivity = new Topic();
|
||||||
public final CallbackEvent<Void> reloadMainActivity = new CallbackEvent<>();
|
public final Topic moduleLoadDone = new Topic();
|
||||||
public final CallbackEvent<Void> moduleLoadDone = new CallbackEvent<>();
|
public final Topic repoLoadDone = new Topic();
|
||||||
public final CallbackEvent<Void> repoLoadDone = new CallbackEvent<>();
|
public final Topic updateCheckDone = new Topic();
|
||||||
public final CallbackEvent<Void> updateCheckDone = new CallbackEvent<>();
|
public final Topic safetyNetDone = new Topic();
|
||||||
public final CallbackEvent<Void> safetyNetDone = new CallbackEvent<>();
|
public final Topic localeDone = new Topic();
|
||||||
public final SparseArray<CallbackEvent<Policy>> uidSuRequest = new SparseArray<>();
|
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
public double magiskVersion;
|
|
||||||
public String magiskVersionString;
|
public String magiskVersionString;
|
||||||
public double remoteMagiskVersion = -1;
|
public int magiskVersionCode = -1;
|
||||||
|
public String remoteMagiskVersionString;
|
||||||
|
public int remoteMagiskVersionCode = -1;
|
||||||
public String magiskLink;
|
public String magiskLink;
|
||||||
public String releaseNoteLink;
|
public String releaseNoteLink;
|
||||||
public int SNCheckResult = -1;
|
public String remoteManagerVersionString;
|
||||||
|
public int remoteManagerVersionCode = -1;
|
||||||
|
public String managerLink;
|
||||||
public String bootBlock = null;
|
public String bootBlock = null;
|
||||||
public boolean isSuClient = false;
|
public boolean isSuClient = false;
|
||||||
public String suVersion = null;
|
public String suVersion = null;
|
||||||
public boolean disabled;
|
public boolean disabled;
|
||||||
public boolean magiskHideStarted;
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
public ValueSortedMap<String, Repo> repoMap;
|
public Map<String, Module> moduleMap;
|
||||||
public ValueSortedMap<String, Module> moduleMap;
|
|
||||||
public List<String> blockList;
|
public List<String> blockList;
|
||||||
public List<ApplicationInfo> appList;
|
public List<Locale> locales;
|
||||||
public List<String> magiskHideList;
|
|
||||||
|
|
||||||
// Configurations
|
// Configurations
|
||||||
public static boolean shellLogging;
|
public static Locale locale;
|
||||||
public static boolean devLogging;
|
public static Locale defaultLocale;
|
||||||
|
|
||||||
public boolean magiskHide;
|
public boolean magiskHide;
|
||||||
public boolean isDarkTheme;
|
public boolean isDarkTheme;
|
||||||
public boolean updateNotification;
|
public boolean updateNotification;
|
||||||
public boolean busybox;
|
public boolean suReauth;
|
||||||
public int suRequestTimeout;
|
public int suRequestTimeout;
|
||||||
public int suLogTimeout = 14;
|
public int suLogTimeout = 14;
|
||||||
public int suAccessState;
|
public int suAccessState;
|
||||||
|
public int multiuserMode;
|
||||||
public int suResponseType;
|
public int suResponseType;
|
||||||
public int suNotificationType;
|
public int suNotificationType;
|
||||||
|
public int suNamespaceMode;
|
||||||
|
public String localeConfig;
|
||||||
|
public int updateChannel;
|
||||||
|
public String bootFormat;
|
||||||
|
public int snet_version;
|
||||||
|
|
||||||
|
// Global resources
|
||||||
public SharedPreferences prefs;
|
public SharedPreferences prefs;
|
||||||
|
public SuDatabaseHelper suDB;
|
||||||
|
public RepoDatabaseHelper repoDB;
|
||||||
|
public Shell shell;
|
||||||
|
|
||||||
private static Handler mHandler = new Handler();
|
private static Handler mHandler = new Handler();
|
||||||
|
private boolean started = false;
|
||||||
|
|
||||||
|
private static class LoadLocale extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
LoadLocale(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
getMagiskManager().locales = Utils.getAvailableLocale(getMagiskManager());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
getMagiskManager().localeDone.publish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
|
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists()) {
|
||||||
|
// Don't migrate yet, wait and check Magisk version
|
||||||
|
suDB = new SuDatabaseHelper(this);
|
||||||
|
} else {
|
||||||
|
suDB = new SuDatabaseHelper(Utils.getEncContext(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
repoDB = new RepoDatabaseHelper(this);
|
||||||
|
defaultLocale = Locale.getDefault();
|
||||||
|
setLocale();
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocale() {
|
||||||
|
localeConfig = prefs.getString("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() {
|
||||||
|
isDarkTheme = prefs.getBoolean("dark_theme", false);
|
||||||
|
|
||||||
|
// su
|
||||||
|
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
|
||||||
|
suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", SuRequestActivity.PROMPT);
|
||||||
|
suNotificationType = Utils.getPrefsInt(prefs, "su_notification", SuReceiver.TOAST);
|
||||||
|
suReauth = prefs.getBoolean("su_reauth", false);
|
||||||
|
suAccessState = suDB.getSettings(SuDatabaseHelper.ROOT_ACCESS, SuDatabaseHelper.ROOT_ACCESS_APPS_AND_ADB);
|
||||||
|
multiuserMode = suDB.getSettings(SuDatabaseHelper.MULTIUSER_MODE, SuDatabaseHelper.MULTIUSER_MODE_OWNER_ONLY);
|
||||||
|
suNamespaceMode = suDB.getSettings(SuDatabaseHelper.MNT_NS, SuDatabaseHelper.NAMESPACE_MODE_REQUESTER);
|
||||||
|
|
||||||
|
updateNotification = prefs.getBoolean("notification", true);
|
||||||
|
updateChannel = Utils.getPrefsInt(prefs, "update_channel", CheckUpdates.STABLE_CHANNEL);
|
||||||
|
bootFormat = prefs.getString("boot_format", ".img");
|
||||||
|
snet_version = prefs.getInt("snet_version", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toast(String msg, int duration) {
|
public void toast(String msg, int duration) {
|
||||||
@@ -89,91 +185,142 @@ public class MagiskManager extends Application {
|
|||||||
mHandler.post(() -> Toast.makeText(this, resId, duration).show());
|
mHandler.post(() -> Toast.makeText(this, resId, duration).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void startup() {
|
||||||
isDarkTheme = prefs.getBoolean("dark_theme", false);
|
if (started)
|
||||||
devLogging = prefs.getBoolean("developer_logging", false);
|
return;
|
||||||
shellLogging = prefs.getBoolean("shell_logging", false);
|
started = true;
|
||||||
magiskHide = prefs.getBoolean("magiskhide", false);
|
|
||||||
updateNotification = prefs.getBoolean("notification", true);
|
boolean hasNetwork = Utils.checkNetworkStatus(this);
|
||||||
// Always start a new root shell manually, just for safety
|
|
||||||
Shell.init();
|
getMagiskInfo();
|
||||||
updateMagiskInfo();
|
|
||||||
initSuAccess();
|
// Check if we need to migrate suDB
|
||||||
initSuConfigs();
|
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists() && Utils.useFDE(this)) {
|
||||||
// Initialize prefs
|
if (magiskVersionCode >= 1410) {
|
||||||
|
suDB.close();
|
||||||
|
Context de = createDeviceProtectedStorageContext();
|
||||||
|
de.moveDatabaseFrom(this, SuDatabaseHelper.DB_NAME);
|
||||||
|
suDB = new SuDatabaseHelper(de);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new LoadLocale(this).exec();
|
||||||
|
|
||||||
|
// Root actions
|
||||||
|
if (Shell.rootAccess()) {
|
||||||
|
if (hasNetwork && !Utils.itemExist(shell, BUSYBOXPATH + "/busybox")) {
|
||||||
|
try {
|
||||||
|
// Force synchronous, make sure we have busybox to use
|
||||||
|
new DownloadBusybox(this).exec().get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = getAssets().open(Utils.UTIL_FUNCTIONS)) {
|
||||||
|
shell.loadInputStream(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.su_raw(
|
||||||
|
"export PATH=" + BUSYBOXPATH + ":$PATH",
|
||||||
|
"mount_partitions",
|
||||||
|
"BOOTIMAGE=",
|
||||||
|
"find_boot_image",
|
||||||
|
"migrate_boot_backup"
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> res = shell.su("echo \"$BOOTIMAGE\"");
|
||||||
|
if (Utils.isValidShellResponse(res)) {
|
||||||
|
bootBlock = res.get(0);
|
||||||
|
} else {
|
||||||
|
blockList = shell.su("find /dev/block -type b | grep -vE 'dm|ram|loop'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back default values
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putBoolean("dark_theme", isDarkTheme)
|
.putBoolean("dark_theme", isDarkTheme)
|
||||||
.putBoolean("magiskhide", magiskHide)
|
.putBoolean("magiskhide", magiskHide)
|
||||||
.putBoolean("notification", updateNotification)
|
.putBoolean("notification", updateNotification)
|
||||||
.putBoolean("busybox", busybox)
|
.putBoolean("hosts", Utils.itemExist(shell, MAGISK_HOST_FILE))
|
||||||
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
|
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
|
||||||
.putBoolean("disable", Utils.itemExist(MAGISK_DISABLE_FILE))
|
.putBoolean("su_reauth", suReauth)
|
||||||
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
|
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
|
||||||
.putString("su_auto_response", String.valueOf(suResponseType))
|
.putString("su_auto_response", String.valueOf(suResponseType))
|
||||||
.putString("su_notification", String.valueOf(suNotificationType))
|
.putString("su_notification", String.valueOf(suNotificationType))
|
||||||
.putString("su_access", String.valueOf(suAccessState))
|
.putString("su_access", String.valueOf(suAccessState))
|
||||||
|
.putString("multiuser_mode", String.valueOf(multiuserMode))
|
||||||
|
.putString("mnt_ns", String.valueOf(suNamespaceMode))
|
||||||
|
.putString("update_channel", String.valueOf(updateChannel))
|
||||||
|
.putString("locale", localeConfig)
|
||||||
|
.putString("boot_format", bootFormat)
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
|
// Create notification channel on Android O
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL,
|
||||||
|
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadModules loadModuleTask = new LoadModules(this);
|
||||||
|
// Start update check job
|
||||||
|
if (hasNetwork) {
|
||||||
|
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||||
|
JobInfo jobInfo = new JobInfo.Builder(UPDATE_SERVICE_ID, service)
|
||||||
|
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||||
|
.setPersisted(true)
|
||||||
|
.setPeriodic(8 * 60 * 60 * 1000)
|
||||||
|
.build();
|
||||||
|
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo);
|
||||||
|
loadModuleTask.setCallBack(() -> new UpdateRepos(this, false).exec());
|
||||||
|
}
|
||||||
|
// Fire asynctasks
|
||||||
|
loadModuleTask.exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initSuConfigs() {
|
public void getMagiskInfo() {
|
||||||
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
|
List<String> ret;
|
||||||
suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0);
|
Shell.getShell(this);
|
||||||
suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1);
|
ret = shell.sh("su -v");
|
||||||
}
|
|
||||||
|
|
||||||
public void initSuAccess() {
|
|
||||||
List<String> ret = Shell.sh("su -v");
|
|
||||||
if (Utils.isValidShellResponse(ret)) {
|
if (Utils.isValidShellResponse(ret)) {
|
||||||
suVersion = ret.get(0);
|
suVersion = ret.get(0);
|
||||||
isSuClient = suVersion.toUpperCase().contains("MAGISK");
|
isSuClient = suVersion.toUpperCase().contains("MAGISK");
|
||||||
}
|
}
|
||||||
if (isSuClient) {
|
ret = shell.sh("magisk -v");
|
||||||
ret = Shell.sh("getprop persist.sys.root_access");
|
|
||||||
if (Utils.isValidShellResponse(ret)) {
|
|
||||||
suAccessState = Integer.parseInt(ret.get(0));
|
|
||||||
} else {
|
|
||||||
Shell.su(true, "setprop persist.sys.root_access 3");
|
|
||||||
suAccessState = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateMagiskInfo() {
|
|
||||||
List<String> ret = Shell.sh("getprop magisk.version");
|
|
||||||
if (!Utils.isValidShellResponse(ret)) {
|
if (!Utils.isValidShellResponse(ret)) {
|
||||||
magiskVersion = -1;
|
ret = shell.sh("getprop magisk.version");
|
||||||
} else {
|
if (Utils.isValidShellResponse(ret)) {
|
||||||
try {
|
try {
|
||||||
magiskVersionString = ret.get(0);
|
magiskVersionString = ret.get(0);
|
||||||
magiskVersion = Double.parseDouble(ret.get(0));
|
magiskVersionCode = (int) Double.parseDouble(ret.get(0)) * 10;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException ignored) {}
|
||||||
// Custom version don't need to receive updates
|
|
||||||
magiskVersion = Double.POSITIVE_INFINITY;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
magiskVersionString = ret.get(0).split(":")[0];
|
||||||
|
ret = shell.sh("magisk -V");
|
||||||
|
try {
|
||||||
|
magiskVersionCode = Integer.parseInt(ret.get(0));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
}
|
}
|
||||||
ret = Shell.sh("getprop persist.magisk.busybox");
|
ret = shell.sh("getprop " + DISABLE_INDICATION_PROP);
|
||||||
try {
|
|
||||||
busybox = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
busybox = false;
|
|
||||||
}
|
|
||||||
ret = Shell.sh("getprop ro.magisk.disable");
|
|
||||||
try {
|
try {
|
||||||
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
|
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
disabled = false;
|
disabled = false;
|
||||||
}
|
}
|
||||||
ret = Shell.sh("getprop persist.magisk.hide");
|
if (magiskVersionCode > 1435) {
|
||||||
try {
|
ret = shell.su("resetprop -p " + MAGISKHIDE_PROP);
|
||||||
magiskHideStarted = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
|
} else {
|
||||||
} catch (NumberFormatException e) {
|
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
|
||||||
magiskHideStarted = false;
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if (magiskHideStarted) {
|
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
magiskHide = true;
|
magiskHide = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.design.widget.NavigationView;
|
import android.support.design.widget.NavigationView;
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
@@ -20,14 +16,15 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.components.Activity;
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class MainActivity extends Activity
|
public class MainActivity extends Activity
|
||||||
implements NavigationView.OnNavigationItemSelectedListener, CallbackEvent.Listener<Void> {
|
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
|
||||||
|
|
||||||
private final Handler mDrawerHandler = new Handler();
|
private final Handler mDrawerHandler = new Handler();
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
@@ -41,24 +38,20 @@ public class MainActivity extends Activity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
getMagiskManager().startup();
|
||||||
|
|
||||||
prefs = getApplicationContext().prefs;
|
prefs = getMagiskManager().prefs;
|
||||||
|
|
||||||
if (getApplicationContext().isDarkTheme) {
|
if (getMagiskManager().isDarkTheme) {
|
||||||
setTheme(R.style.AppTheme_Dark);
|
setTheme(R.style.AppTheme_Dark);
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
|
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
|
||||||
@Override
|
@Override
|
||||||
public void onDrawerOpened(View drawerView) {
|
public void onDrawerOpened(View drawerView) {
|
||||||
super.onDrawerOpened(drawerView);
|
super.onDrawerOpened(drawerView);
|
||||||
@@ -80,8 +73,6 @@ public class MainActivity extends Activity
|
|||||||
navigate(getIntent().getStringExtra(MagiskManager.INTENT_SECTION));
|
navigate(getIntent().getStringExtra(MagiskManager.INTENT_SECTION));
|
||||||
|
|
||||||
navigationView.setNavigationItemSelectedListener(this);
|
navigationView.setNavigationItemSelectedListener(this);
|
||||||
getApplicationContext().reloadMainActivity.register(this);
|
|
||||||
getApplicationContext().updateCheckDone.register(this);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,31 +82,15 @@ public class MainActivity extends Activity
|
|||||||
checkHideSection();
|
checkHideSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
|
||||||
navigate(savedInstanceState.getInt(MagiskManager.INTENT_SECTION, R.id.status));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putInt(MagiskManager.INTENT_SECTION, mDrawerItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
getApplicationContext().reloadMainActivity.unRegister(this);
|
|
||||||
getApplicationContext().updateCheckDone.unRegister(this);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (drawer.isDrawerOpen(navigationView))
|
if (drawer.isDrawerOpen(navigationView)) {
|
||||||
drawer.closeDrawer(navigationView);
|
drawer.closeDrawer(navigationView);
|
||||||
else
|
} else if (mDrawerItem != R.id.magisk) {
|
||||||
|
navigate(R.id.magisk);
|
||||||
|
} else {
|
||||||
finish();
|
finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,38 +102,35 @@ public class MainActivity extends Activity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
if (event == getApplicationContext().reloadMainActivity) {
|
recreate();
|
||||||
recreate();
|
|
||||||
} else if (event == getApplicationContext().updateCheckDone) {
|
|
||||||
checkHideSection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkHideSection() {
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getMagiskManager().reloadActivity };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkHideSection() {
|
||||||
Menu menu = navigationView.getMenu();
|
Menu menu = navigationView.getMenu();
|
||||||
menu.findItem(R.id.magiskhide).setVisible(
|
menu.findItem(R.id.magiskhide).setVisible(
|
||||||
Shell.rootAccess() && getApplicationContext().magiskVersion >= 8
|
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 1300
|
||||||
&& prefs.getBoolean("magiskhide", false));
|
&& prefs.getBoolean("magiskhide", false));
|
||||||
menu.findItem(R.id.modules).setVisible(
|
menu.findItem(R.id.modules).setVisible(
|
||||||
Shell.rootAccess() && getApplicationContext().magiskVersion >= 4);
|
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
|
||||||
menu.findItem(R.id.downloads).setVisible(
|
menu.findItem(R.id.downloads).setVisible(Utils.checkNetworkStatus(this) &&
|
||||||
Shell.rootAccess() && getApplicationContext().magiskVersion >= 4);
|
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
|
||||||
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
|
||||||
menu.findItem(R.id.superuser).setVisible(
|
menu.findItem(R.id.superuser).setVisible(
|
||||||
Shell.rootAccess() && getApplicationContext().isSuClient);
|
Shell.rootAccess() && getMagiskManager().isSuClient);
|
||||||
menu.findItem(R.id.install).setVisible(getApplicationContext().remoteMagiskVersion > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void navigate(String item) {
|
public void navigate(String item) {
|
||||||
int itemId = R.id.status;
|
int itemId = R.id.magisk;
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
switch (item) {
|
switch (item) {
|
||||||
case "status":
|
case "magisk":
|
||||||
itemId = R.id.status;
|
itemId = R.id.magisk;
|
||||||
break;
|
|
||||||
case "install":
|
|
||||||
itemId = R.id.install;
|
|
||||||
break;
|
break;
|
||||||
case "superuser":
|
case "superuser":
|
||||||
itemId = R.id.superuser;
|
itemId = R.id.superuser;
|
||||||
@@ -191,11 +163,8 @@ public class MainActivity extends Activity
|
|||||||
mDrawerItem = itemId;
|
mDrawerItem = itemId;
|
||||||
navigationView.setCheckedItem(itemId);
|
navigationView.setCheckedItem(itemId);
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
case R.id.status:
|
case R.id.magisk:
|
||||||
displayFragment(new StatusFragment(), "status", true);
|
displayFragment(new MagiskFragment(), "magisk", true);
|
||||||
break;
|
|
||||||
case R.id.install:
|
|
||||||
displayFragment(new InstallFragment(), "install", true);
|
|
||||||
break;
|
break;
|
||||||
case R.id.superuser:
|
case R.id.superuser:
|
||||||
displayFragment(new SuperuserFragment(), "superuser", true);
|
displayFragment(new SuperuserFragment(), "superuser", true);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -14,21 +13,21 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
import com.topjohnwu.magisk.adapters.ModulesAdapter;
|
||||||
import com.topjohnwu.magisk.asyncs.FlashZip;
|
|
||||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
import com.topjohnwu.magisk.asyncs.LoadModules;
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import butterknife.Unbinder;
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
public class ModulesFragment extends Fragment implements CallbackEvent.Listener<Void> {
|
public class ModulesFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
private static final int FETCH_ZIP_CODE = 2;
|
private static final int FETCH_ZIP_CODE = 2;
|
||||||
|
|
||||||
@@ -36,7 +35,14 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
|
|||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
@BindView(R.id.fab) FloatingActionButton fabio;
|
@OnClick(R.id.fab)
|
||||||
|
public void selectFile() {
|
||||||
|
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("application/zip");
|
||||||
|
startActivityForResult(intent, FETCH_ZIP_CODE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private List<Module> listModules = new ArrayList<>();
|
private List<Module> listModules = new ArrayList<>();
|
||||||
|
|
||||||
@@ -46,12 +52,6 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
|
|||||||
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
View view = inflater.inflate(R.layout.fragment_modules, container, false);
|
||||||
unbinder = ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
|
||||||
fabio.setOnClickListener(v -> {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
||||||
intent.setType("application/zip");
|
|
||||||
startActivityForResult(intent, FETCH_ZIP_CODE);
|
|
||||||
});
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
recyclerView.setVisibility(View.GONE);
|
recyclerView.setVisibility(View.GONE);
|
||||||
new LoadModules(getActivity()).exec();
|
new LoadModules(getActivity()).exec();
|
||||||
@@ -69,40 +69,29 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getApplication().moduleLoadDone.isTriggered) {
|
getActivity().setTitle(R.string.modules);
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
Logger.dev("ModulesFragment: UI refresh triggered");
|
|
||||||
updateUI();
|
updateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { getApplication().moduleLoadDone };
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
|
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
// Get the URI of the selected file
|
// Get the URI of the selected file
|
||||||
final Uri uri = data.getData();
|
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||||
new FlashZip(getActivity(), uri).exec();
|
intent.setData(data.getData()).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
|
||||||
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
getApplication().moduleLoadDone.register(this);
|
|
||||||
getActivity().setTitle(R.string.modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
getApplication().moduleLoadDone.unRegister(this);
|
|
||||||
super.onStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -14,39 +13,22 @@ import android.widget.SearchView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||||
import com.topjohnwu.magisk.adapters.SimpleSectionedRecyclerViewAdapter;
|
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||||
import com.topjohnwu.magisk.asyncs.LoadRepos;
|
|
||||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.Unbinder;
|
import butterknife.Unbinder;
|
||||||
|
|
||||||
public class ReposFragment extends Fragment implements CallbackEvent.Listener<Void> {
|
public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||||
|
|
||||||
private Unbinder unbinder;
|
private Unbinder unbinder;
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
|
||||||
private List<Repo> mUpdateRepos = new ArrayList<>();
|
public static ReposAdapter adapter;
|
||||||
private List<Repo> mInstalledRepos = new ArrayList<>();
|
|
||||||
private List<Repo> mOthersRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fUpdateRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fInstalledRepos = new ArrayList<>();
|
|
||||||
private List<Repo> fOthersRepos = new ArrayList<>();
|
|
||||||
|
|
||||||
private SimpleSectionedRecyclerViewAdapter mSectionedAdapter;
|
|
||||||
|
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -60,24 +42,49 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
|
|||||||
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
View view = inflater.inflate(R.layout.fragment_repos, container, false);
|
||||||
unbinder = ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
|
||||||
mSectionedAdapter = new SimpleSectionedRecyclerViewAdapter(R.layout.section,
|
|
||||||
R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos));
|
|
||||||
|
|
||||||
recyclerView.setAdapter(mSectionedAdapter);
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
recyclerView.setVisibility(View.GONE);
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
new LoadRepos(getActivity()).exec();
|
emptyRv.setVisibility(View.GONE);
|
||||||
|
new UpdateRepos(getActivity(), true).exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getApplication().repoLoadDone.isTriggered) {
|
getActivity().setTitle(R.string.downloads);
|
||||||
reloadRepos();
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
adapter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
|
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[] { getApplication().repoLoadDone };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_repo, menu);
|
||||||
|
SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
|
||||||
|
search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
return false;
|
return false;
|
||||||
@@ -85,39 +92,10 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText) {
|
public boolean onQueryTextChange(String newText) {
|
||||||
new FilterApps().exec(newText);
|
adapter.filter(newText);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
|
||||||
Logger.dev("ReposFragment: UI refresh triggered");
|
|
||||||
reloadRepos();
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_repo, menu);
|
|
||||||
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.repo_search));
|
|
||||||
search.setOnQueryTextListener(searchListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
getApplication().repoLoadDone.register(this);
|
|
||||||
getActivity().setTitle(R.string.downloads);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
getApplication().repoLoadDone.unRegister(this);
|
|
||||||
super.onStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,92 +103,4 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
unbinder.unbind();
|
unbinder.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadRepos() {
|
|
||||||
mUpdateRepos.clear();
|
|
||||||
mInstalledRepos.clear();
|
|
||||||
mOthersRepos.clear();
|
|
||||||
for (Repo repo : getApplication().repoMap.values()) {
|
|
||||||
Module module = getApplication().moduleMap.get(repo.getId());
|
|
||||||
if (module != null) {
|
|
||||||
if (repo.getVersionCode() > module.getVersionCode()) {
|
|
||||||
mUpdateRepos.add(repo);
|
|
||||||
} else {
|
|
||||||
mInstalledRepos.add(repo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mOthersRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fUpdateRepos.clear();
|
|
||||||
fInstalledRepos.clear();
|
|
||||||
fOthersRepos.clear();
|
|
||||||
fUpdateRepos.addAll(mUpdateRepos);
|
|
||||||
fInstalledRepos.addAll(mInstalledRepos);
|
|
||||||
fOthersRepos.addAll(mOthersRepos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
|
|
||||||
emptyRv.setVisibility(View.VISIBLE);
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
|
|
||||||
if (!fUpdateRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0, getString(R.string.update_available)));
|
|
||||||
}
|
|
||||||
if (!fInstalledRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size(), getString(R.string.installed)));
|
|
||||||
}
|
|
||||||
if (!fOthersRepos.isEmpty()) {
|
|
||||||
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size() + fInstalledRepos.size(), getString(R.string.not_installed)));
|
|
||||||
}
|
|
||||||
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
|
|
||||||
mSectionedAdapter.setSections(array);
|
|
||||||
emptyRv.setVisibility(View.GONE);
|
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FilterApps extends ParallelTask<String, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... strings) {
|
|
||||||
String newText = strings[0];
|
|
||||||
fUpdateRepos.clear();
|
|
||||||
fInstalledRepos.clear();
|
|
||||||
fOthersRepos.clear();
|
|
||||||
for (Repo repo: mUpdateRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fUpdateRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Repo repo: mInstalledRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fInstalledRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Repo repo: mOthersRepos) {
|
|
||||||
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
|
|
||||||
) {
|
|
||||||
fOthersRepos.add(repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,45 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.MagiskHide;
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
import com.topjohnwu.magisk.asyncs.SerialTask;
|
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||||
import com.topjohnwu.magisk.components.Activity;
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class SettingsActivity extends Activity {
|
public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (getApplicationContext().isDarkTheme) {
|
if (getMagiskManager().isDarkTheme) {
|
||||||
setTheme(R.style.AppTheme_Dark);
|
setTheme(R.style.AppTheme_Transparent_Dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_container);
|
setContentView(R.layout.activity_settings);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
@@ -55,31 +60,27 @@ public class SettingsActivity extends Activity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFloating() {
|
@Override
|
||||||
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
public void onTopicPublished(Topic topic, Object result) {
|
||||||
if (isTablet) {
|
recreate();
|
||||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
}
|
||||||
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
|
||||||
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
@Override
|
||||||
params.alpha = 1.0f;
|
public Topic[] getSubscription() {
|
||||||
params.dimAmount = 0.6f;
|
return new Topic[] { getMagiskManager().reloadActivity };
|
||||||
params.flags |= 2;
|
|
||||||
getWindow().setAttributes(params);
|
|
||||||
setFinishOnTouchOutside(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragment
|
public static class SettingsFragment extends PreferenceFragment
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
implements SharedPreferences.OnSharedPreferenceChangeListener,
|
||||||
|
Topic.Subscriber {
|
||||||
|
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private PreferenceScreen prefScreen;
|
private PreferenceScreen prefScreen;
|
||||||
|
|
||||||
private ListPreference suAccess, autoRes, suNotification, requestTimeout;
|
private ListPreference updateChannel, suAccess, autoRes, suNotification,
|
||||||
|
requestTimeout, multiuserMode, namespaceMode;
|
||||||
private MagiskManager getApplication() {
|
private MagiskManager mm;
|
||||||
return Utils.getMagiskManager(getActivity());
|
private PreferenceCategory generalCatagory;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -87,162 +88,190 @@ public class SettingsActivity extends Activity {
|
|||||||
addPreferencesFromResource(R.xml.app_settings);
|
addPreferencesFromResource(R.xml.app_settings);
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
prefScreen = getPreferenceScreen();
|
prefScreen = getPreferenceScreen();
|
||||||
|
mm = Utils.getMagiskManager(getActivity());
|
||||||
|
|
||||||
|
generalCatagory = (PreferenceCategory) findPreference("general");
|
||||||
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
|
||||||
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
|
||||||
|
|
||||||
|
updateChannel = (ListPreference) findPreference("update_channel");
|
||||||
suAccess = (ListPreference) findPreference("su_access");
|
suAccess = (ListPreference) findPreference("su_access");
|
||||||
autoRes = (ListPreference) findPreference("su_auto_response");
|
autoRes = (ListPreference) findPreference("su_auto_response");
|
||||||
requestTimeout = (ListPreference) findPreference("su_request_timeout");
|
requestTimeout = (ListPreference) findPreference("su_request_timeout");
|
||||||
suNotification = (ListPreference) findPreference("su_notification");
|
suNotification = (ListPreference) findPreference("su_notification");
|
||||||
|
multiuserMode = (ListPreference) findPreference("multiuser_mode");
|
||||||
|
namespaceMode = (ListPreference) findPreference("mnt_ns");
|
||||||
|
SwitchPreference reauth = (SwitchPreference) findPreference("su_reauth");
|
||||||
|
Preference hideManager = findPreference("hide");
|
||||||
|
|
||||||
setSummary();
|
setSummary();
|
||||||
|
|
||||||
|
// Disable dangerous settings in user mode if selected owner manage
|
||||||
|
if (getActivity().getApplicationInfo().uid > 99999) {
|
||||||
|
suCategory.removePreference(multiuserMode);
|
||||||
|
generalCatagory.removePreference(hideManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove re-authentication option on Android O, it will not work
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
suCategory.removePreference(reauth);
|
||||||
|
}
|
||||||
|
|
||||||
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
findPreference("clear").setOnPreferenceClickListener((pref) -> {
|
||||||
Utils.clearRepoCache(getActivity());
|
Utils.clearRepoCache(getActivity());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||||
|
Utils.runWithPermission(getActivity(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
() -> new HideManager(getActivity()).exec());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (!Shell.rootAccess()) {
|
if (!Shell.rootAccess()) {
|
||||||
prefScreen.removePreference(magiskCategory);
|
prefScreen.removePreference(magiskCategory);
|
||||||
prefScreen.removePreference(suCategory);
|
prefScreen.removePreference(suCategory);
|
||||||
|
generalCatagory.removePreference(hideManager);
|
||||||
} else {
|
} else {
|
||||||
if (!getApplication().isSuClient) {
|
if (!mm.isSuClient) {
|
||||||
prefScreen.removePreference(suCategory);
|
prefScreen.removePreference(suCategory);
|
||||||
}
|
}
|
||||||
if (getApplication().magiskVersion < 11) {
|
if (mm.magiskVersionCode < 1300) {
|
||||||
prefScreen.removePreference(magiskCategory);
|
prefScreen.removePreference(magiskCategory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLocalePreference(ListPreference lp) {
|
||||||
|
boolean isNew = lp == null;
|
||||||
|
if (isNew) {
|
||||||
|
lp = new ListPreference(getActivity());
|
||||||
|
}
|
||||||
|
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
|
||||||
|
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
|
||||||
|
entries[0] = getString(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.setTitle(R.string.language);
|
||||||
|
lp.setKey("locale");
|
||||||
|
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
|
||||||
|
if (isNew) {
|
||||||
|
generalCatagory.addPreference(lp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
subscribeTopics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
|
||||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
unsubscribeTopics();
|
||||||
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||||
Logger.dev("Settings: Prefs change " + key);
|
|
||||||
boolean enabled;
|
boolean enabled;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "dark_theme":
|
case "dark_theme":
|
||||||
enabled = prefs.getBoolean("dark_theme", false);
|
enabled = prefs.getBoolean("dark_theme", false);
|
||||||
if (getApplication().isDarkTheme != enabled) {
|
if (mm.isDarkTheme != enabled) {
|
||||||
getApplication().isDarkTheme = enabled;
|
mm.reloadActivity.publish(false);
|
||||||
getApplication().reloadMainActivity.trigger();
|
|
||||||
getActivity().recreate();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "disable":
|
case "disable":
|
||||||
enabled = prefs.getBoolean("disable", false);
|
enabled = prefs.getBoolean("disable", false);
|
||||||
new SerialTask<Void, Void, Void>() {
|
if (enabled) {
|
||||||
private boolean enable = enabled;
|
Utils.createFile(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
|
||||||
@Override
|
} else {
|
||||||
protected Void doInBackground(Void... voids) {
|
Utils.removeItem(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
|
||||||
if (enable) {
|
}
|
||||||
Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE);
|
|
||||||
} else {
|
|
||||||
Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
|
||||||
break;
|
break;
|
||||||
case "busybox":
|
|
||||||
enabled = prefs.getBoolean("busybox", false);
|
|
||||||
new SerialTask<Void, Void, Void>() {
|
|
||||||
private boolean enable = enabled;
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
if (enable) {
|
|
||||||
Shell.su(
|
|
||||||
"setprop persist.magisk.busybox 1",
|
|
||||||
"sh /sbin/magic_mask.sh mount_busybox");
|
|
||||||
} else {
|
|
||||||
Shell.su(
|
|
||||||
"setprop persist.magisk.busybox 0",
|
|
||||||
"umount /system/xbin");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
break;
|
|
||||||
case "magiskhide":
|
case "magiskhide":
|
||||||
enabled = prefs.getBoolean("magiskhide", false);
|
enabled = prefs.getBoolean("magiskhide", false);
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!getApplication().isSuClient) {
|
Utils.enableMagiskHide(getShell());
|
||||||
new AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.no_magisksu_title)
|
|
||||||
.setMessage(R.string.no_magisksu_msg)
|
|
||||||
.setPositiveButton(R.string.understand, (dialog, which) -> new MagiskHide().enable())
|
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
new MagiskHide().enable();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
new MagiskHide().disable();
|
Utils.disableMagiskHide(getShell());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "hosts":
|
case "hosts":
|
||||||
enabled = prefs.getBoolean("hosts", false);
|
enabled = prefs.getBoolean("hosts", false);
|
||||||
new SerialTask<Void, Void, Void>() {
|
if (enabled) {
|
||||||
private boolean enable = enabled;
|
getShell().su_raw(
|
||||||
@Override
|
"cp -af /system/etc/hosts " + MagiskManager.MAGISK_HOST_FILE,
|
||||||
protected Void doInBackground(Void... voids) {
|
"mount -o bind " + MagiskManager.MAGISK_HOST_FILE + " /system/etc/hosts");
|
||||||
if (enable) {
|
} else {
|
||||||
Shell.su("cp -af /system/etc/hosts /magisk/.core/hosts",
|
getShell().su_raw(
|
||||||
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
|
"umount -l /system/etc/hosts",
|
||||||
} else {
|
"rm -f " + MagiskManager.MAGISK_HOST_FILE);
|
||||||
Shell.su("umount -l /system/etc/hosts",
|
}
|
||||||
"rm -f /magisk/.core/hosts");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
break;
|
break;
|
||||||
case "su_access":
|
case "su_access":
|
||||||
getApplication().suAccessState = Utils.getPrefsInt(prefs, "su_access", 0);
|
mm.suDB.setSettings(SuDatabaseHelper.ROOT_ACCESS, Utils.getPrefsInt(prefs, "su_access"));
|
||||||
Shell.su("setprop persist.sys.root_access " + getApplication().suAccessState);
|
|
||||||
break;
|
break;
|
||||||
case "su_request_timeout":
|
case "multiuser_mode":
|
||||||
getApplication().suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
|
mm.suDB.setSettings(SuDatabaseHelper.MULTIUSER_MODE, Utils.getPrefsInt(prefs, "multiuser_mode"));
|
||||||
break;
|
break;
|
||||||
case "su_auto_response":
|
case "mnt_ns":
|
||||||
getApplication().suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0);
|
mm.suDB.setSettings(SuDatabaseHelper.MNT_NS, Utils.getPrefsInt(prefs, "mnt_ns"));
|
||||||
break;
|
break;
|
||||||
case "su_notification":
|
case "locale":
|
||||||
getApplication().suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1);
|
mm.setLocale();
|
||||||
|
mm.reloadActivity.publish(false);
|
||||||
break;
|
break;
|
||||||
case "developer_logging":
|
case "update_channel":
|
||||||
MagiskManager.devLogging = prefs.getBoolean("developer_logging", false);
|
mm.updateChannel = Utils.getPrefsInt(prefs, "update_channel");
|
||||||
break;
|
new CheckUpdates(mm, true).exec();
|
||||||
case "shell_logging":
|
|
||||||
MagiskManager.shellLogging = prefs.getBoolean("shell_logging", false);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
mm.loadConfig();
|
||||||
setSummary();
|
setSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Shell getShell() {
|
||||||
|
return Shell.getShell(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
private void setSummary() {
|
private void setSummary() {
|
||||||
|
updateChannel.setSummary(getResources()
|
||||||
|
.getStringArray(R.array.update_channel)[mm.updateChannel]);
|
||||||
suAccess.setSummary(getResources()
|
suAccess.setSummary(getResources()
|
||||||
.getStringArray(R.array.su_access)[getApplication().suAccessState]);
|
.getStringArray(R.array.su_access)[mm.suAccessState]);
|
||||||
autoRes.setSummary(getResources()
|
autoRes.setSummary(getResources()
|
||||||
.getStringArray(R.array.auto_response)[getApplication().suResponseType]);
|
.getStringArray(R.array.auto_response)[mm.suResponseType]);
|
||||||
suNotification.setSummary(getResources()
|
suNotification.setSummary(getResources()
|
||||||
.getStringArray(R.array.su_notification)[getApplication().suNotificationType]);
|
.getStringArray(R.array.su_notification)[mm.suNotificationType]);
|
||||||
requestTimeout.setSummary(
|
requestTimeout.setSummary(
|
||||||
getString(R.string.request_timeout_summary, prefs.getString("su_request_timeout", "10")));
|
getString(R.string.request_timeout_summary, prefs.getString("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, Object result) {
|
||||||
|
setLocalePreference((ListPreference) findPreference("locale"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Topic[] getSubscription() {
|
||||||
|
return new Topic[] { mm.localeDone };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +1,24 @@
|
|||||||
package com.topjohnwu.magisk;
|
package com.topjohnwu.magisk;
|
||||||
|
|
||||||
import android.app.job.JobInfo;
|
|
||||||
import android.app.job.JobScheduler;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
|
||||||
import com.topjohnwu.magisk.asyncs.GetBootBlocks;
|
|
||||||
import com.topjohnwu.magisk.asyncs.LoadApps;
|
|
||||||
import com.topjohnwu.magisk.asyncs.LoadModules;
|
|
||||||
import com.topjohnwu.magisk.asyncs.LoadRepos;
|
|
||||||
import com.topjohnwu.magisk.asyncs.MagiskHide;
|
|
||||||
import com.topjohnwu.magisk.components.Activity;
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
import com.topjohnwu.magisk.services.UpdateCheckService;
|
|
||||||
|
|
||||||
public class SplashActivity extends Activity{
|
public class SplashActivity extends Activity {
|
||||||
|
|
||||||
private static final int UPDATE_SERVICE_ID = 1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
MagiskManager magiskManager = getApplicationContext();
|
getMagiskManager().startup();
|
||||||
|
|
||||||
// Init the info and configs and root shell
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
magiskManager.init();
|
String section = getIntent().getStringExtra(MagiskManager.INTENT_SECTION);
|
||||||
|
if (section != null) {
|
||||||
// Initialize the update check service, notify every 3 hours
|
intent.putExtra(MagiskManager.INTENT_SECTION, section);
|
||||||
if (!"install".equals(getIntent().getStringExtra(MagiskManager.INTENT_SECTION))) {
|
|
||||||
ComponentName service = new ComponentName(magiskManager, UpdateCheckService.class);
|
|
||||||
JobInfo jobInfo = new JobInfo.Builder(UPDATE_SERVICE_ID, service)
|
|
||||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
|
||||||
.setPersisted(true)
|
|
||||||
.setPeriodic(3 * 60 * 60 * 1000)
|
|
||||||
.build();
|
|
||||||
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
|
||||||
scheduler.schedule(jobInfo);
|
|
||||||
}
|
}
|
||||||
|
startActivity(intent);
|
||||||
// Now fire all async tasks
|
finish();
|
||||||
new GetBootBlocks(this).exec();
|
|
||||||
if (magiskManager.magiskHide && magiskManager.magiskVersion > 11 &&
|
|
||||||
!magiskManager.magiskHideStarted) {
|
|
||||||
new MagiskHide().enable();
|
|
||||||
}
|
|
||||||
new LoadModules(this) {
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
super.onPostExecute(v);
|
|
||||||
new LoadRepos(activity).exec();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
new LoadApps(this).exec();
|
|
||||||
new CheckUpdates(this, false){
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
super.onPostExecute(v);
|
|
||||||
String section = getIntent().getStringExtra(MagiskManager.INTENT_SECTION);
|
|
||||||
Intent intent = new Intent(magiskManager, MainActivity.class);
|
|
||||||
if (section != null) {
|
|
||||||
intent.putExtra(MagiskManager.INTENT_SECTION, section);
|
|
||||||
}
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
package com.topjohnwu.magisk;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import butterknife.BindColor;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import butterknife.Unbinder;
|
|
||||||
|
|
||||||
public class StatusFragment extends Fragment implements CallbackEvent.Listener<Void> {
|
|
||||||
|
|
||||||
private static boolean noDialog = false;
|
|
||||||
|
|
||||||
private Unbinder unbinder;
|
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
|
||||||
|
|
||||||
@BindView(R.id.magisk_status_container) View magiskStatusContainer;
|
|
||||||
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
|
|
||||||
@BindView(R.id.magisk_version) TextView magiskVersionText;
|
|
||||||
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
|
|
||||||
@BindView(R.id.magisk_check_updates_progress) ProgressBar magiskCheckUpdatesProgress;
|
|
||||||
|
|
||||||
@BindView(R.id.root_status_container) View rootStatusContainer;
|
|
||||||
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
|
|
||||||
@BindView(R.id.root_status) TextView rootStatusText;
|
|
||||||
@BindView(R.id.root_info) TextView rootInfoText;
|
|
||||||
|
|
||||||
@BindView(R.id.safetyNet_container) View safetyNetContainer;
|
|
||||||
@BindView(R.id.safetyNet_icon) ImageView safetyNetIcon;
|
|
||||||
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
|
|
||||||
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
|
|
||||||
|
|
||||||
@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.blue500) int colorInfo;
|
|
||||||
@BindColor(android.R.color.transparent) int trans;
|
|
||||||
|
|
||||||
@OnClick(R.id.safetyNet_container)
|
|
||||||
public void safetyNet() {
|
|
||||||
safetyNetProgress.setVisibility(View.VISIBLE);
|
|
||||||
safetyNetContainer.setBackgroundColor(trans);
|
|
||||||
safetyNetIcon.setImageResource(0);
|
|
||||||
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
|
|
||||||
Utils.checkSafetyNet(getApplication());
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.magisk_status_container)
|
|
||||||
public void gotoInstall() {
|
|
||||||
if (getApplication().remoteMagiskVersion > 0) {
|
|
||||||
((MainActivity) getActivity()).navigate(R.id.install);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int defaultColor;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.fragment_status, container, false);
|
|
||||||
unbinder = ButterKnife.bind(this, v);
|
|
||||||
|
|
||||||
defaultColor = magiskUpdateText.getCurrentTextColor();
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(() -> {
|
|
||||||
magiskStatusContainer.setBackgroundColor(trans);
|
|
||||||
magiskStatusIcon.setImageResource(0);
|
|
||||||
magiskUpdateText.setText(R.string.checking_for_updates);
|
|
||||||
magiskCheckUpdatesProgress.setVisibility(View.VISIBLE);
|
|
||||||
magiskUpdateText.setTextColor(defaultColor);
|
|
||||||
|
|
||||||
safetyNetProgress.setVisibility(View.GONE);
|
|
||||||
safetyNetContainer.setBackgroundColor(colorNeutral);
|
|
||||||
safetyNetIcon.setImageResource(R.drawable.ic_safetynet);
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_check_text);
|
|
||||||
safetyNetStatusText.setTextColor(defaultColor);
|
|
||||||
|
|
||||||
getApplication().safetyNetDone.isTriggered = false;
|
|
||||||
noDialog = false;
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
new CheckUpdates(getActivity()).exec();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (getApplication().magiskVersion < 0 && Shell.rootAccess() && !noDialog) {
|
|
||||||
noDialog = true;
|
|
||||||
new AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.no_magisk_title)
|
|
||||||
.setMessage(R.string.no_magisk_msg)
|
|
||||||
.setCancelable(true)
|
|
||||||
.setPositiveButton(R.string.goto_install, (d, i) -> gotoInstall())
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
|
|
||||||
if (getApplication().updateCheckDone.isTriggered)
|
|
||||||
updateCheckUI();
|
|
||||||
if (getApplication().safetyNetDone.isTriggered)
|
|
||||||
updateSafetyNetUI();
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrigger(CallbackEvent<Void> event) {
|
|
||||||
if (event == getApplication().updateCheckDone) {
|
|
||||||
Logger.dev("StatusFragment: Update Check UI refresh triggered");
|
|
||||||
updateCheckUI();
|
|
||||||
} else if (event == getApplication().safetyNetDone) {
|
|
||||||
Logger.dev("StatusFragment: SafetyNet UI refresh triggered");
|
|
||||||
updateSafetyNetUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
getApplication().updateCheckDone.register(this);
|
|
||||||
getApplication().safetyNetDone.register(this);
|
|
||||||
getActivity().setTitle(R.string.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
getApplication().updateCheckDone.unRegister(this);
|
|
||||||
getApplication().safetyNetDone.unRegister(this);
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
unbinder.unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
int image, color;
|
|
||||||
|
|
||||||
getApplication().updateMagiskInfo();
|
|
||||||
|
|
||||||
if (getApplication().magiskVersion < 0) {
|
|
||||||
magiskVersionText.setText(R.string.magisk_version_error);
|
|
||||||
} else if (getApplication().disabled) {
|
|
||||||
magiskVersionText.setText(getString(R.string.magisk_version_core_only, getApplication().magiskVersionString));
|
|
||||||
} else {
|
|
||||||
magiskVersionText.setText(getString(R.string.magisk_version, getApplication().magiskVersionString));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (Shell.rootStatus) {
|
|
||||||
case 0:
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
rootStatusText.setText(R.string.not_rooted);
|
|
||||||
rootInfoText.setText(R.string.root_info_warning);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (getApplication().suVersion != null) {
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
rootStatusText.setText(R.string.proper_root);
|
|
||||||
rootInfoText.setText(getApplication().suVersion);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case -1:
|
|
||||||
default:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
rootStatusText.setText(R.string.root_error);
|
|
||||||
rootInfoText.setText(R.string.root_info_warning);
|
|
||||||
}
|
|
||||||
rootStatusContainer.setBackgroundColor(color);
|
|
||||||
rootStatusText.setTextColor(color);
|
|
||||||
rootInfoText.setTextColor(color);
|
|
||||||
rootStatusIcon.setImageResource(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCheckUI() {
|
|
||||||
int image, color;
|
|
||||||
|
|
||||||
if (getApplication().remoteMagiskVersion < 0) {
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
magiskUpdateText.setText(R.string.cannot_check_updates);
|
|
||||||
} else if (getApplication().remoteMagiskVersion > getApplication().magiskVersion) {
|
|
||||||
color = colorInfo;
|
|
||||||
image = R.drawable.ic_update;
|
|
||||||
magiskUpdateText.setText(getString(R.string.magisk_update_available, getApplication().remoteMagiskVersion));
|
|
||||||
} else {
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
magiskUpdateText.setText(getString(R.string.up_to_date, getString(R.string.magisk)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getApplication().magiskVersion < 0) {
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
}
|
|
||||||
|
|
||||||
magiskStatusContainer.setBackgroundColor(color);
|
|
||||||
magiskVersionText.setTextColor(color);
|
|
||||||
magiskUpdateText.setTextColor(color);
|
|
||||||
magiskStatusIcon.setImageResource(image);
|
|
||||||
|
|
||||||
magiskCheckUpdatesProgress.setVisibility(View.GONE);
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSafetyNetUI() {
|
|
||||||
int image, color;
|
|
||||||
safetyNetProgress.setVisibility(View.GONE);
|
|
||||||
switch (getApplication().SNCheckResult) {
|
|
||||||
case -3:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_connection_suspended);
|
|
||||||
break;
|
|
||||||
case -2:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_connection_failed);
|
|
||||||
break;
|
|
||||||
case -1:
|
|
||||||
color = colorNeutral;
|
|
||||||
image = R.drawable.ic_help;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_error);
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
color = colorBad;
|
|
||||||
image = R.drawable.ic_cancel;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_fail);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
color = colorOK;
|
|
||||||
image = R.drawable.ic_check_circle;
|
|
||||||
safetyNetStatusText.setText(R.string.safetyNet_pass);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
safetyNetContainer.setBackgroundColor(color);
|
|
||||||
safetyNetStatusText.setTextColor(color);
|
|
||||||
safetyNetIcon.setImageResource(image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -13,10 +13,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
import com.topjohnwu.magisk.adapters.SuLogAdapter;
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.database.SuLogDatabaseHelper;
|
|
||||||
import com.topjohnwu.magisk.superuser.SuLogEntry;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
@@ -28,7 +24,8 @@ public class SuLogFragment extends Fragment {
|
|||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
|
||||||
private Unbinder unbinder;
|
private Unbinder unbinder;
|
||||||
private SuLogDatabaseHelper dbHelper;
|
private MagiskManager mm;
|
||||||
|
private SuLogAdapter adapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -48,8 +45,9 @@ public class SuLogFragment extends Fragment {
|
|||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||||
unbinder = ButterKnife.bind(this, v);
|
unbinder = ButterKnife.bind(this, v);
|
||||||
|
mm = getApplication();
|
||||||
dbHelper = new SuLogDatabaseHelper(getActivity());
|
adapter = new SuLogAdapter(mm.suDB);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
updateList();
|
updateList();
|
||||||
|
|
||||||
@@ -57,13 +55,12 @@ public class SuLogFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateList() {
|
private void updateList() {
|
||||||
List<SuLogEntry> logs = dbHelper.getLogList();
|
adapter.notifyDBChanged();
|
||||||
|
|
||||||
if (logs.size() == 0) {
|
if (adapter.getSectionCount() == 0) {
|
||||||
emptyRv.setVisibility(View.VISIBLE);
|
emptyRv.setVisibility(View.VISIBLE);
|
||||||
recyclerView.setVisibility(View.GONE);
|
recyclerView.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
recyclerView.setAdapter(new SuLogAdapter(logs).getAdapter());
|
|
||||||
emptyRv.setVisibility(View.GONE);
|
emptyRv.setVisibility(View.GONE);
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
@@ -76,7 +73,7 @@ public class SuLogFragment extends Fragment {
|
|||||||
updateList();
|
updateList();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_clear:
|
case R.id.menu_clear:
|
||||||
dbHelper.clearLogs();
|
mm.suDB.clearLogs();
|
||||||
updateList();
|
updateList();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
import com.topjohnwu.magisk.adapters.PolicyAdapter;
|
||||||
import com.topjohnwu.magisk.components.Fragment;
|
import com.topjohnwu.magisk.components.Fragment;
|
||||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.superuser.Policy;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,15 +32,15 @@ public class SuperuserFragment extends Fragment {
|
|||||||
unbinder = ButterKnife.bind(this, view);
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
|
||||||
PackageManager pm = getActivity().getPackageManager();
|
PackageManager pm = getActivity().getPackageManager();
|
||||||
|
MagiskManager mm = getApplication();
|
||||||
|
|
||||||
SuDatabaseHelper dbHelper = new SuDatabaseHelper(getActivity());
|
List<Policy> policyList = mm.suDB.getPolicyList(pm);
|
||||||
List<Policy> policyList = dbHelper.getPolicyList(pm);
|
|
||||||
|
|
||||||
if (policyList.size() == 0) {
|
if (policyList.size() == 0) {
|
||||||
emptyRv.setVisibility(View.VISIBLE);
|
emptyRv.setVisibility(View.VISIBLE);
|
||||||
recyclerView.setVisibility(View.GONE);
|
recyclerView.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
recyclerView.setAdapter(new PolicyAdapter(policyList, dbHelper, pm));
|
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.suDB, pm));
|
||||||
emptyRv.setVisibility(View.GONE);
|
emptyRv.setVisibility(View.GONE);
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
@@ -13,13 +14,16 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.asyncs.MagiskHide;
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
@@ -40,20 +44,19 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
|
|
||||||
private List<ApplicationInfo> mOriginalList, mList;
|
private List<ApplicationInfo> mOriginalList, mList;
|
||||||
private List<String> mHideList;
|
private List<String> mHideList;
|
||||||
private PackageManager packageManager;
|
private PackageManager pm;
|
||||||
private ApplicationFilter filter;
|
private ApplicationFilter filter;
|
||||||
|
private Topic magiskHideDone;
|
||||||
|
private Shell shell;
|
||||||
|
|
||||||
public ApplicationAdapter(PackageManager packageManager) {
|
public ApplicationAdapter(Context context) {
|
||||||
mOriginalList = mList = Collections.emptyList();
|
mOriginalList = mList = Collections.emptyList();
|
||||||
mHideList = Collections.emptyList();
|
mHideList = Collections.emptyList();
|
||||||
this.packageManager = packageManager;
|
|
||||||
filter = new ApplicationFilter();
|
filter = new ApplicationFilter();
|
||||||
}
|
pm = context.getPackageManager();
|
||||||
|
magiskHideDone = Utils.getMagiskManager(context).magiskHideDone;
|
||||||
public void setLists(List<ApplicationInfo> listApps, List<String> hideList) {
|
shell = Shell.getShell(context);
|
||||||
mOriginalList = mList = listApps;
|
new LoadApps().exec();
|
||||||
mHideList = hideList;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -66,8 +69,8 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||||
ApplicationInfo info = mList.get(position);
|
ApplicationInfo info = mList.get(position);
|
||||||
|
|
||||||
holder.appIcon.setImageDrawable(info.loadIcon(packageManager));
|
holder.appIcon.setImageDrawable(info.loadIcon(pm));
|
||||||
holder.appName.setText(info.loadLabel(packageManager));
|
holder.appName.setText(info.loadLabel(pm));
|
||||||
holder.appPackage.setText(info.packageName);
|
holder.appPackage.setText(info.packageName);
|
||||||
|
|
||||||
// Remove all listeners
|
// Remove all listeners
|
||||||
@@ -86,10 +89,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
holder.checkBox.setChecked(mHideList.contains(info.packageName));
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
new MagiskHide().add(info.packageName);
|
Utils.addMagiskHide(shell, info.packageName);
|
||||||
mHideList.add(info.packageName);
|
mHideList.add(info.packageName);
|
||||||
} else {
|
} else {
|
||||||
new MagiskHide().rm(info.packageName);
|
Utils.rmMagiskHide(shell, info.packageName);
|
||||||
mHideList.remove(info.packageName);
|
mHideList.remove(info.packageName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -105,6 +108,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
filter.filter(constraint);
|
filter.filter(constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
new LoadApps().exec();
|
||||||
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
@BindView(R.id.app_icon) ImageView appIcon;
|
@BindView(R.id.app_icon) ImageView appIcon;
|
||||||
@@ -122,31 +129,47 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected FilterResults performFiltering(CharSequence constraint) {
|
protected FilterResults performFiltering(CharSequence constraint) {
|
||||||
List<ApplicationInfo> filteredApps;
|
|
||||||
if (constraint == null || constraint.length() == 0) {
|
if (constraint == null || constraint.length() == 0) {
|
||||||
filteredApps = mOriginalList;
|
mList = mOriginalList;
|
||||||
} else {
|
} else {
|
||||||
filteredApps = new ArrayList<>();
|
mList = new ArrayList<>();
|
||||||
String filter = constraint.toString().toLowerCase();
|
String filter = constraint.toString().toLowerCase();
|
||||||
for (ApplicationInfo info : mOriginalList) {
|
for (ApplicationInfo info : mOriginalList) {
|
||||||
if (Utils.lowercaseContains(info.loadLabel(packageManager), filter)
|
if (Utils.lowercaseContains(info.loadLabel(pm), filter)
|
||||||
|| Utils.lowercaseContains(info.packageName, filter)) {
|
|| Utils.lowercaseContains(info.packageName, filter)) {
|
||||||
filteredApps.add(info);
|
mList.add(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
FilterResults results = new FilterResults();
|
|
||||||
results.values = filteredApps;
|
|
||||||
results.count = filteredApps.size();
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
mList = (List<ApplicationInfo>) results.values;
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LoadApps extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
mOriginalList = pm.getInstalledApplications(0);
|
||||||
|
for (Iterator<ApplicationInfo> i = mOriginalList.iterator(); i.hasNext(); ) {
|
||||||
|
ApplicationInfo info = i.next();
|
||||||
|
if (ApplicationAdapter.BLACKLIST.contains(info.packageName) || !info.enabled) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(mOriginalList, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
||||||
|
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
||||||
|
mHideList = Utils.listMagiskHide(shell);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void v) {
|
||||||
|
magiskHideDone.publish(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.asyncs.SerialTask;
|
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||||
Context context = holder.itemView.getContext();
|
Context context = holder.itemView.getContext();
|
||||||
|
Shell rootShell = Shell.getShell(context);
|
||||||
final Module module = mList.get(position);
|
final Module module = mList.get(position);
|
||||||
|
|
||||||
String version = module.getVersion();
|
String version = module.getVersion();
|
||||||
@@ -53,44 +53,31 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
|
|||||||
|
|
||||||
holder.checkBox.setOnCheckedChangeListener(null);
|
holder.checkBox.setOnCheckedChangeListener(null);
|
||||||
holder.checkBox.setChecked(module.isEnabled());
|
holder.checkBox.setChecked(module.isEnabled());
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new SerialTask<Void, Void, Void>() {
|
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
@Override
|
int snack;
|
||||||
protected Void doInBackground(Void... voids) {
|
if (isChecked) {
|
||||||
if (isChecked) {
|
module.removeDisableFile(rootShell);
|
||||||
module.removeDisableFile();
|
snack = R.string.disable_file_removed;
|
||||||
} else {
|
} else {
|
||||||
module.createDisableFile();
|
module.createDisableFile(rootShell);
|
||||||
}
|
snack = R.string.disable_file_created;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
|
||||||
@Override
|
holder.delete.setOnClickListener(v -> {
|
||||||
protected void onPostExecute(Void v) {
|
boolean removed = module.willBeRemoved();
|
||||||
int snack = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
|
int snack;
|
||||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
if (removed) {
|
||||||
|
module.deleteRemoveFile(rootShell);
|
||||||
|
snack = R.string.remove_file_deleted;
|
||||||
|
} else {
|
||||||
|
module.createRemoveFile(rootShell);
|
||||||
|
snack = R.string.remove_file_created;
|
||||||
}
|
}
|
||||||
}.exec());
|
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||||
|
updateDeleteButton(holder, module);
|
||||||
holder.delete.setOnClickListener(v -> new SerialTask<Void, Void, Void>() {
|
});
|
||||||
private final boolean removed = module.willBeRemoved();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
if (removed) {
|
|
||||||
module.deleteRemoveFile();
|
|
||||||
} else {
|
|
||||||
module.createRemoveFile();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
int snack = removed ? R.string.remove_file_deleted : R.string.remove_file_created;
|
|
||||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
|
||||||
updateDeleteButton(holder, module);
|
|
||||||
}
|
|
||||||
}.exec());
|
|
||||||
|
|
||||||
if (module.isUpdated()) {
|
if (module.isUpdated()) {
|
||||||
holder.notice.setVisibility(View.VISIBLE);
|
holder.notice.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.app.Activity;
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Switch;
|
import android.widget.Switch;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
import com.topjohnwu.magisk.superuser.Policy;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -49,78 +47,72 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
Policy policy = policyList.get(position);
|
Policy policy = policyList.get(position);
|
||||||
try {
|
|
||||||
holder.setExpanded(expandList.contains(policy));
|
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(view -> {
|
holder.setExpanded(expandList.contains(policy));
|
||||||
if (holder.mExpanded) {
|
|
||||||
holder.collapse();
|
|
||||||
expandList.remove(policy);
|
|
||||||
} else {
|
|
||||||
holder.expand();
|
|
||||||
expandList.add(policy);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
holder.appName.setText(policy.appName);
|
holder.itemView.setOnClickListener(view -> {
|
||||||
holder.packageName.setText(policy.packageName);
|
if (holder.isExpanded()) {
|
||||||
holder.appIcon.setImageDrawable(pm.getPackageInfo(policy.packageName, 0).applicationInfo.loadIcon(pm));
|
holder.collapse();
|
||||||
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
expandList.remove(policy);
|
||||||
if ((isChecked && policy.policy == Policy.DENY) ||
|
} else {
|
||||||
(!isChecked && policy.policy == Policy.ALLOW)) {
|
holder.expand();
|
||||||
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
expandList.add(policy);
|
||||||
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.addPolicy(policy);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
|
||||||
if ((isChecked && !policy.notification) ||
|
|
||||||
(!isChecked && policy.notification)) {
|
|
||||||
policy.notification = isChecked;
|
|
||||||
String message = v.getContext().getString(
|
|
||||||
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
|
|
||||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
|
||||||
dbHelper.addPolicy(policy);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
|
||||||
if ((isChecked && !policy.logging) ||
|
|
||||||
(!isChecked && policy.logging)) {
|
|
||||||
policy.logging = isChecked;
|
|
||||||
String message = v.getContext().getString(
|
|
||||||
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
|
|
||||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
|
||||||
dbHelper.addPolicy(policy);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.delete.setOnClickListener(v -> new AlertDialogBuilder(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) -> {
|
|
||||||
policyList.remove(position);
|
|
||||||
notifyItemRemoved(position);
|
|
||||||
notifyItemRangeChanged(position, policyList.size());
|
|
||||||
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
|
|
||||||
Snackbar.LENGTH_SHORT).show();
|
|
||||||
dbHelper.deletePolicy(policy.uid);
|
|
||||||
})
|
|
||||||
.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.appName.setText(policy.appName);
|
||||||
holder.moreInfo.setVisibility(View.GONE);
|
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((v, isChecked) -> {
|
||||||
|
if ((isChecked && !policy.notification) ||
|
||||||
|
(!isChecked && policy.notification)) {
|
||||||
|
policy.notification = isChecked;
|
||||||
|
String message = v.getContext().getString(
|
||||||
|
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
|
||||||
|
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if ((isChecked && !policy.logging) ||
|
||||||
|
(!isChecked && policy.logging)) {
|
||||||
|
policy.logging = isChecked;
|
||||||
|
String message = v.getContext().getString(
|
||||||
|
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
|
||||||
|
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
holder.delete.setOnClickListener(v -> new AlertDialogBuilder((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) -> {
|
||||||
|
policyList.remove(position);
|
||||||
|
notifyItemRemoved(position);
|
||||||
|
notifyItemRangeChanged(position, policyList.size());
|
||||||
|
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
|
||||||
|
Snackbar.LENGTH_SHORT).show();
|
||||||
|
dbHelper.deletePolicy(policy);
|
||||||
|
})
|
||||||
|
.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);
|
||||||
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
// Hide for now
|
||||||
policyList.remove(position);
|
holder.moreInfo.setVisibility(View.GONE);
|
||||||
dbHelper.deletePolicy(policy.uid);
|
|
||||||
onBindViewHolder(holder, position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -128,96 +120,31 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
|
|||||||
return policyList.size();
|
return policyList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||||
|
|
||||||
@BindView(R.id.app_name) TextView appName;
|
@BindView(R.id.app_name) TextView appName;
|
||||||
@BindView(R.id.package_name) TextView packageName;
|
@BindView(R.id.package_name) TextView packageName;
|
||||||
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
|
||||||
@BindView(R.id.app_icon) ImageView appIcon;
|
@BindView(R.id.app_icon) ImageView appIcon;
|
||||||
@BindView(R.id.master_switch) Switch masterSwitch;
|
@BindView(R.id.master_switch) Switch masterSwitch;
|
||||||
@BindView(R.id.notification_switch) Switch notificationSwitch;
|
@BindView(R.id.notification_switch) Switch notificationSwitch;
|
||||||
@BindView(R.id.logging_switch) Switch loggingSwitch;
|
@BindView(R.id.logging_switch) Switch loggingSwitch;
|
||||||
|
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||||
|
|
||||||
@BindView(R.id.delete) ImageView delete;
|
@BindView(R.id.delete) ImageView delete;
|
||||||
@BindView(R.id.more_info) ImageView moreInfo;
|
@BindView(R.id.more_info) ImageView moreInfo;
|
||||||
|
|
||||||
private ValueAnimator mAnimator;
|
private Container container = new Container();
|
||||||
private boolean mExpanded = false;
|
|
||||||
private static int expandHeight = 0;
|
|
||||||
|
|
||||||
ViewHolder(View itemView) {
|
public ViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
container.expandLayout = expandLayout;
|
||||||
new ViewTreeObserver.OnPreDrawListener() {
|
setupExpandable();
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw() {
|
|
||||||
if (expandHeight == 0) {
|
|
||||||
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
expandLayout.measure(widthSpec, heightSpec);
|
|
||||||
expandHeight = expandLayout.getMeasuredHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
mAnimator = slideAnimator(0, expandHeight);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setExpanded(boolean expanded) {
|
@Override
|
||||||
mExpanded = expanded;
|
public Container getContainer() {
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
return container;
|
||||||
layoutParams.height = expanded ? expandHeight : 0;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expand() {
|
|
||||||
expandLayout.setVisibility(View.VISIBLE);
|
|
||||||
mAnimator.start();
|
|
||||||
mExpanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collapse() {
|
|
||||||
if (!mExpanded) return;
|
|
||||||
int finalHeight = expandLayout.getHeight();
|
|
||||||
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
|
|
||||||
mAnimator.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animator) {
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animator) {}
|
|
||||||
});
|
|
||||||
mAnimator.start();
|
|
||||||
mExpanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator slideAnimator(int start, int end) {
|
|
||||||
|
|
||||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
|
||||||
|
|
||||||
animator.addUpdateListener(valueAnimator -> {
|
|
||||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = value;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
});
|
|
||||||
return animator;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.database.Cursor;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -12,99 +14,165 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
|
||||||
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
import com.topjohnwu.magisk.components.MarkDownWindow;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
import com.topjohnwu.magisk.container.Repo;
|
||||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> {
|
public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> {
|
||||||
|
|
||||||
private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos;
|
private static final int UPDATES = 0;
|
||||||
private Context mContext;
|
private static final int INSTALLED = 1;
|
||||||
|
private static final int OTHERS = 2;
|
||||||
|
|
||||||
public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) {
|
private Cursor repoCursor = null;
|
||||||
mUpdateRepos = update;
|
private Map<String, Module> moduleMap;
|
||||||
mInstalledRepos = installed;
|
private RepoDatabaseHelper repoDB;
|
||||||
mOthersRepos = others;
|
private List<Pair<Integer, List<Repo>>> repoPairs;
|
||||||
|
|
||||||
|
public ReposAdapter(RepoDatabaseHelper db, Map<String, Module> map) {
|
||||||
|
repoDB = db;
|
||||||
|
moduleMap = map;
|
||||||
|
repoPairs = new ArrayList<>();
|
||||||
|
notifyDBChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return repoPairs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public int getItemCount(int section) {
|
||||||
mContext = parent.getContext();
|
return repoPairs.get(section).second.size();
|
||||||
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_repo, parent, false);
|
|
||||||
return new ViewHolder(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||||
Repo repo = getItem(position);
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
|
||||||
|
return new SectionHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
|
||||||
|
return new RepoHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||||
|
switch (repoPairs.get(section).first) {
|
||||||
|
case UPDATES:
|
||||||
|
holder.sectionText.setText(R.string.update_available);
|
||||||
|
break;
|
||||||
|
case INSTALLED:
|
||||||
|
holder.sectionText.setText(R.string.installed);
|
||||||
|
break;
|
||||||
|
case OTHERS:
|
||||||
|
holder.sectionText.setText(R.string.not_installed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
|
||||||
|
Repo repo = repoPairs.get(section).second.get(position);
|
||||||
|
Context context = holder.itemView.getContext();
|
||||||
|
|
||||||
holder.title.setText(repo.getName());
|
holder.title.setText(repo.getName());
|
||||||
holder.versionName.setText(repo.getVersion());
|
holder.versionName.setText(repo.getVersion());
|
||||||
String author = repo.getAuthor();
|
String author = repo.getAuthor();
|
||||||
holder.author.setText(TextUtils.isEmpty(author) ? null : mContext.getString(R.string.author, author));
|
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||||
holder.description.setText(repo.getDescription());
|
holder.description.setText(repo.getDescription());
|
||||||
|
|
||||||
holder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), mContext));
|
holder.infoLayout.setOnClickListener(v ->
|
||||||
|
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||||
|
|
||||||
holder.downloadImage.setOnClickListener(v -> {
|
holder.downloadImage.setOnClickListener(v -> {
|
||||||
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
|
||||||
new AlertDialogBuilder(mContext)
|
new AlertDialogBuilder((Activity) context)
|
||||||
.setTitle(mContext.getString(R.string.repo_install_title, repo.getName()))
|
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||||
.setMessage(mContext.getString(R.string.repo_install_msg, filename))
|
.setMessage(context.getString(R.string.repo_install_msg, filename))
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.install, (d, i) -> Utils.dlAndReceive(
|
.setPositiveButton(R.string.install, (d, i) ->
|
||||||
mContext,
|
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||||
new DownloadReceiver() {
|
Utils.getLegalFilename(filename), true).exec()
|
||||||
@Override
|
)
|
||||||
public void onDownloadDone(Uri uri) {
|
.setNeutralButton(R.string.download, (d, i) ->
|
||||||
new ProcessRepoZip(activity, uri, true).exec();
|
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
|
||||||
}
|
Utils.getLegalFilename(filename), false).exec())
|
||||||
},
|
|
||||||
repo.getZipUrl(),
|
|
||||||
Utils.getLegalFilename(filename)))
|
|
||||||
.setNeutralButton(R.string.download, (d, i) -> Utils.dlAndReceive(
|
|
||||||
mContext,
|
|
||||||
new DownloadReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onDownloadDone(Uri uri) {
|
|
||||||
new ProcessRepoZip(activity, uri, false).exec();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
repo.getZipUrl(),
|
|
||||||
Utils.getLegalFilename(filename)))
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void notifyDBChanged() {
|
||||||
public int getItemCount() {
|
if (repoCursor != null)
|
||||||
return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size();
|
repoCursor.close();
|
||||||
|
repoCursor = repoDB.getRepoCursor();
|
||||||
|
filter("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Repo getItem(int position) {
|
public void filter(String s) {
|
||||||
if (position >= mUpdateRepos.size()) {
|
List<Repo> updates = new ArrayList<>();
|
||||||
position -= mUpdateRepos.size();
|
List<Repo> installed = new ArrayList<>();
|
||||||
if (position >= mInstalledRepos.size()) {
|
List<Repo> others = new ArrayList<>();
|
||||||
position -= mInstalledRepos.size();
|
|
||||||
return mOthersRepos.get(position);
|
repoPairs.clear();
|
||||||
} else {
|
while (repoCursor.moveToNext()) {
|
||||||
return mInstalledRepos.get(position);
|
Repo repo = new Repo(repoCursor);
|
||||||
|
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|
||||||
|
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|
||||||
|
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
|
||||||
|
) {
|
||||||
|
// Passed the repoFilter
|
||||||
|
Module module = moduleMap.get(repo.getId());
|
||||||
|
if (module != null) {
|
||||||
|
if (repo.getVersionCode() > module.getVersionCode()) {
|
||||||
|
// Updates
|
||||||
|
updates.add(repo);
|
||||||
|
} else {
|
||||||
|
installed.add(repo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
others.add(repo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return mUpdateRepos.get(position);
|
repoCursor.moveToFirst();
|
||||||
|
|
||||||
|
if (!updates.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(UPDATES, updates));
|
||||||
|
if (!installed.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(INSTALLED, installed));
|
||||||
|
if (!others.isEmpty())
|
||||||
|
repoPairs.add(new Pair<>(OTHERS, others));
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.section_text) TextView sectionText;
|
||||||
|
|
||||||
|
SectionHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
static class RepoHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
@BindView(R.id.title) TextView title;
|
@BindView(R.id.title) TextView title;
|
||||||
@BindView(R.id.version_name) TextView versionName;
|
@BindView(R.id.version_name) TextView versionName;
|
||||||
@@ -113,7 +181,7 @@ public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder>
|
|||||||
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
||||||
@BindView(R.id.download) ImageView downloadImage;
|
@BindView(R.id.download) ImageView downloadImage;
|
||||||
|
|
||||||
ViewHolder(View itemView) {
|
RepoHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
|
||||||
|
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private static final int SECTION_TYPE = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
if (viewType == SECTION_TYPE)
|
||||||
|
return onCreateSectionViewHolder(parent);
|
||||||
|
return onCreateItemViewHolder(parent, viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
PositionInfo info = getPositionInfo(position);
|
||||||
|
if (info.position == -1)
|
||||||
|
onBindSectionViewHolder((S) holder, info.section);
|
||||||
|
else
|
||||||
|
onBindItemViewHolder((C) holder, info.section, info.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public int getItemCount() {
|
||||||
|
int size, sec;
|
||||||
|
size = sec = getSectionCount();
|
||||||
|
for (int i = 0; i < sec; ++i){
|
||||||
|
size += getItemCount(i);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final public int getItemViewType(int position) {
|
||||||
|
PositionInfo info = getPositionInfo(position);
|
||||||
|
if (info.position == -1)
|
||||||
|
return SECTION_TYPE;
|
||||||
|
else
|
||||||
|
return getItemViewType(info.section, info.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemViewType(int section, int position) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getSectionPosition(int section) {
|
||||||
|
return getItemPosition(section, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getItemPosition(int section, int position) {
|
||||||
|
int realPosition = 0;
|
||||||
|
// Previous sections
|
||||||
|
for (int i = 0; i < section; ++i) {
|
||||||
|
realPosition += getItemCount(i) + 1;
|
||||||
|
}
|
||||||
|
// Current section
|
||||||
|
realPosition += position + 1;
|
||||||
|
return realPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PositionInfo getPositionInfo(int position) {
|
||||||
|
int section = 0;
|
||||||
|
while (true) {
|
||||||
|
if (position == 0)
|
||||||
|
return new PositionInfo(section, -1);
|
||||||
|
position -= 1;
|
||||||
|
if (position < getItemCount(section))
|
||||||
|
return new PositionInfo(section, position);
|
||||||
|
position -= getItemCount(section++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PositionInfo {
|
||||||
|
int section;
|
||||||
|
int position;
|
||||||
|
PositionInfo(int section, int position) {
|
||||||
|
this.section = section;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getSectionCount();
|
||||||
|
public abstract int getItemCount(int section);
|
||||||
|
public abstract S onCreateSectionViewHolder(ViewGroup parent);
|
||||||
|
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
|
||||||
|
public abstract void onBindSectionViewHolder(S holder, int section);
|
||||||
|
public abstract void onBindItemViewHolder(C holder, int section, int position);
|
||||||
|
}
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|
||||||
|
|
||||||
private static final int SECTION_TYPE = 0;
|
|
||||||
|
|
||||||
private boolean mValid = true;
|
|
||||||
private int mSectionResourceId;
|
|
||||||
private int mTextResourceId;
|
|
||||||
private RecyclerView.Adapter mBaseAdapter;
|
|
||||||
private SparseArray<Section> mSections = new SparseArray<Section>();
|
|
||||||
|
|
||||||
|
|
||||||
public SimpleSectionedRecyclerViewAdapter(int sectionResourceId, int textResourceId,
|
|
||||||
RecyclerView.Adapter baseAdapter) {
|
|
||||||
|
|
||||||
mSectionResourceId = sectionResourceId;
|
|
||||||
mTextResourceId = textResourceId;
|
|
||||||
mBaseAdapter = baseAdapter;
|
|
||||||
|
|
||||||
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
|
||||||
@Override
|
|
||||||
public void onChanged() {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeChanged(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeInserted(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
|
||||||
mValid = mBaseAdapter.getItemCount()>0;
|
|
||||||
notifyItemRangeRemoved(positionStart, itemCount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class SectionViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
public TextView title;
|
|
||||||
|
|
||||||
public SectionViewHolder(View view, int mTextResourceid) {
|
|
||||||
super(view);
|
|
||||||
title = (TextView) view.findViewById(mTextResourceid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
|
|
||||||
if (typeView == SECTION_TYPE) {
|
|
||||||
View view = LayoutInflater.from(parent.getContext()).inflate(mSectionResourceId, parent, false);
|
|
||||||
return new SectionViewHolder(view,mTextResourceId);
|
|
||||||
}else{
|
|
||||||
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) {
|
|
||||||
if (isSectionHeaderPosition(position)) {
|
|
||||||
((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title);
|
|
||||||
}else{
|
|
||||||
mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
return isSectionHeaderPosition(position)
|
|
||||||
? SECTION_TYPE
|
|
||||||
: mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Section {
|
|
||||||
int firstPosition;
|
|
||||||
int sectionedPosition;
|
|
||||||
CharSequence title;
|
|
||||||
|
|
||||||
public Section(int firstPosition, CharSequence title) {
|
|
||||||
this.firstPosition = firstPosition;
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharSequence getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setSections(Section[] sections) {
|
|
||||||
mSections.clear();
|
|
||||||
|
|
||||||
Arrays.sort(sections, new Comparator<Section>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Section o, Section o1) {
|
|
||||||
return (o.firstPosition == o1.firstPosition)
|
|
||||||
? 0
|
|
||||||
: ((o.firstPosition < o1.firstPosition) ? -1 : 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int offset = 0; // offset positions for the headers we're adding
|
|
||||||
for (Section section : sections) {
|
|
||||||
section.sectionedPosition = section.firstPosition + offset;
|
|
||||||
mSections.append(section.sectionedPosition, section);
|
|
||||||
++offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int positionToSectionedPosition(int position) {
|
|
||||||
int offset = 0;
|
|
||||||
for (int i = 0; i < mSections.size(); i++) {
|
|
||||||
if (mSections.valueAt(i).firstPosition > position) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++offset;
|
|
||||||
}
|
|
||||||
return position + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int sectionedPositionToPosition(int sectionedPosition) {
|
|
||||||
if (isSectionHeaderPosition(sectionedPosition)) {
|
|
||||||
return RecyclerView.NO_POSITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
for (int i = 0; i < mSections.size(); i++) {
|
|
||||||
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
--offset;
|
|
||||||
}
|
|
||||||
return sectionedPosition + offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSectionHeaderPosition(int position) {
|
|
||||||
return mSections.get(position) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int position) {
|
|
||||||
return isSectionHeaderPosition(position)
|
|
||||||
? Integer.MAX_VALUE - mSections.indexOfKey(position)
|
|
||||||
: mBaseAdapter.getItemId(sectionedPositionToPosition(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,156 +1,134 @@
|
|||||||
package com.topjohnwu.magisk.adapters;
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.database.Cursor;
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.RotateAnimation;
|
import android.view.animation.RotateAnimation;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter;
|
|
||||||
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
|
|
||||||
import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder;
|
|
||||||
import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;
|
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.superuser.SuLogEntry;
|
import com.topjohnwu.magisk.components.ExpandableView;
|
||||||
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
|
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class SuLogAdapter {
|
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||||
|
|
||||||
private ExpandableAdapter adapter;
|
private List<List<Integer>> logEntryList;
|
||||||
private Set<SuLogEntry> expandList = new HashSet<>();
|
private Set<Integer> itemExpanded, sectionExpanded;
|
||||||
|
private SuDatabaseHelper suDB;
|
||||||
|
private Cursor suLogCursor = null;
|
||||||
|
|
||||||
public SuLogAdapter(List<SuLogEntry> list) {
|
public SuLogAdapter(SuDatabaseHelper db) {
|
||||||
|
suDB = db;
|
||||||
|
logEntryList = Collections.emptyList();
|
||||||
|
sectionExpanded = new HashSet<>();
|
||||||
|
itemExpanded = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
// Separate the logs with date
|
@Override
|
||||||
Map<String, List<SuLogEntry>> logEntryMap = new LinkedHashMap<>();
|
public int getSectionCount() {
|
||||||
List<SuLogEntry> group;
|
return logEntryList.size();
|
||||||
for (SuLogEntry log : list) {
|
}
|
||||||
String date = log.getDateString();
|
|
||||||
group = logEntryMap.get(date);
|
@Override
|
||||||
if (group == null) {
|
public int getItemCount(int section) {
|
||||||
group = new ArrayList<>();
|
return sectionExpanded.contains(section) ? logEntryList.get(section).size() : 0;
|
||||||
logEntryMap.put(date, group);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
|
||||||
|
return new SectionHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
|
||||||
|
return new LogViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||||
|
suLogCursor.moveToPosition(logEntryList.get(section).get(0));
|
||||||
|
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||||
|
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
RotateAnimation rotate;
|
||||||
|
if (sectionExpanded.contains(section)) {
|
||||||
|
holder.arrow.setRotation(0);
|
||||||
|
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
sectionExpanded.remove(section);
|
||||||
|
notifyItemRangeRemoved(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||||
|
} else {
|
||||||
|
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
sectionExpanded.add(section);
|
||||||
|
notifyItemRangeInserted(getItemPosition(section, 0), logEntryList.get(section).size());
|
||||||
}
|
}
|
||||||
group.add(log);
|
rotate.setDuration(300);
|
||||||
}
|
rotate.setFillAfter(true);
|
||||||
|
holder.arrow.setAnimation(rotate);
|
||||||
// Then format them into expandable groups
|
});
|
||||||
List<LogGroup> logEntryGroups = new ArrayList<>();
|
holder.date.setText(entry.getDateString());
|
||||||
for (Map.Entry<String, List<SuLogEntry>> entry : logEntryMap.entrySet()) {
|
|
||||||
logEntryGroups.add(new LogGroup(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
|
||||||
adapter = new ExpandableAdapter(logEntryGroups);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecyclerView.Adapter getAdapter() {
|
@Override
|
||||||
return adapter;
|
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||||
}
|
int sqlPosition = logEntryList.get(section).get(position);
|
||||||
|
suLogCursor.moveToPosition(sqlPosition);
|
||||||
private class ExpandableAdapter
|
SuLogEntry entry = new SuLogEntry(suLogCursor);
|
||||||
extends ExpandableRecyclerViewAdapter<LogGroupViewHolder, LogViewHolder> {
|
holder.setExpanded(itemExpanded.contains(sqlPosition));
|
||||||
|
holder.itemView.setOnClickListener(view -> {
|
||||||
ExpandableAdapter(List<? extends ExpandableGroup> groups) {
|
if (holder.isExpanded()) {
|
||||||
super(groups);
|
holder.collapse();
|
||||||
expandableList.expandedGroupIndexes[0] = true;
|
itemExpanded.remove(sqlPosition);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogGroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
|
|
||||||
return new LogGroupViewHolder(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LogViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
|
|
||||||
return new LogViewHolder(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindChildViewHolder(LogViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
|
|
||||||
Context context = holder.itemView.getContext();
|
|
||||||
SuLogEntry logEntry = (SuLogEntry) group.getItems().get(childIndex);
|
|
||||||
holder.setExpanded(expandList.contains(logEntry));
|
|
||||||
holder.itemView.setOnClickListener(view -> {
|
|
||||||
if (holder.mExpanded) {
|
|
||||||
holder.collapse();
|
|
||||||
expandList.remove(logEntry);
|
|
||||||
} else {
|
|
||||||
holder.expand();
|
|
||||||
expandList.add(logEntry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.appName.setText(logEntry.appName);
|
|
||||||
holder.action.setText(context.getString(logEntry.action ? R.string.grant : R.string.deny));
|
|
||||||
holder.command.setText(logEntry.command);
|
|
||||||
holder.fromPid.setText(String.valueOf(logEntry.fromPid));
|
|
||||||
holder.toUid.setText(String.valueOf(logEntry.toUid));
|
|
||||||
holder.time.setText(logEntry.getTimeString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindGroupViewHolder(LogGroupViewHolder holder, int flatPosition, ExpandableGroup group) {
|
|
||||||
holder.date.setText(group.getTitle());
|
|
||||||
if (isGroupExpanded(flatPosition)) {
|
|
||||||
holder.expand();
|
holder.expand();
|
||||||
|
itemExpanded.add(sqlPosition);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
holder.appName.setText(entry.appName);
|
||||||
|
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||||
|
holder.command.setText(entry.command);
|
||||||
|
holder.fromPid.setText(String.valueOf(entry.fromPid));
|
||||||
|
holder.toUid.setText(String.valueOf(entry.toUid));
|
||||||
|
holder.time.setText(entry.getTimeString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LogGroup extends ExpandableGroup<SuLogEntry> {
|
public void notifyDBChanged() {
|
||||||
LogGroup(String title, List<SuLogEntry> items) {
|
if (suLogCursor != null)
|
||||||
super(title, items);
|
suLogCursor.close();
|
||||||
}
|
suLogCursor = suDB.getLogCursor();
|
||||||
|
logEntryList = suDB.getLogStructure();
|
||||||
|
itemExpanded.clear();
|
||||||
|
sectionExpanded.clear();
|
||||||
|
sectionExpanded.add(0);
|
||||||
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class LogGroupViewHolder extends GroupViewHolder {
|
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
@BindView(R.id.date) TextView date;
|
@BindView(R.id.date) TextView date;
|
||||||
@BindView(R.id.arrow) ImageView arrow;
|
@BindView(R.id.arrow) ImageView arrow;
|
||||||
|
|
||||||
public LogGroupViewHolder(View itemView) {
|
SectionHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expand() {
|
|
||||||
RotateAnimation rotate =
|
|
||||||
new RotateAnimation(360, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
|
||||||
rotate.setDuration(300);
|
|
||||||
rotate.setFillAfter(true);
|
|
||||||
arrow.setAnimation(rotate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void collapse() {
|
|
||||||
RotateAnimation rotate =
|
|
||||||
new RotateAnimation(180, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
|
||||||
rotate.setDuration(300);
|
|
||||||
rotate.setFillAfter(true);
|
|
||||||
arrow.setAnimation(rotate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class LogViewHolder extends ChildViewHolder {
|
static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
|
||||||
|
|
||||||
@BindView(R.id.app_name) TextView appName;
|
@BindView(R.id.app_name) TextView appName;
|
||||||
@BindView(R.id.action) TextView action;
|
@BindView(R.id.action) TextView action;
|
||||||
@@ -158,85 +136,20 @@ public class SuLogAdapter {
|
|||||||
@BindView(R.id.fromPid) TextView fromPid;
|
@BindView(R.id.fromPid) TextView fromPid;
|
||||||
@BindView(R.id.toUid) TextView toUid;
|
@BindView(R.id.toUid) TextView toUid;
|
||||||
@BindView(R.id.command) TextView command;
|
@BindView(R.id.command) TextView command;
|
||||||
@BindView(R.id.expand_layout) LinearLayout expandLayout;
|
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||||
|
|
||||||
private ValueAnimator mAnimator;
|
private Container container = new Container();
|
||||||
private boolean mExpanded = false;
|
|
||||||
private static int expandHeight = 0;
|
|
||||||
|
|
||||||
public LogViewHolder(View itemView) {
|
LogViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
container.expandLayout = expandLayout;
|
||||||
new ViewTreeObserver.OnPreDrawListener() {
|
setupExpandable();
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw() {
|
|
||||||
if (expandHeight == 0) {
|
|
||||||
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
expandLayout.measure(widthSpec, heightSpec);
|
|
||||||
expandHeight = expandLayout.getMeasuredHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
mAnimator = slideAnimator(0, expandHeight);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setExpanded(boolean expanded) {
|
@Override
|
||||||
mExpanded = expanded;
|
public Container getContainer() {
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
return container;
|
||||||
layoutParams.height = expanded ? expandHeight : 0;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expand() {
|
|
||||||
expandLayout.setVisibility(View.VISIBLE);
|
|
||||||
mAnimator.start();
|
|
||||||
mExpanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collapse() {
|
|
||||||
if (!mExpanded) return;
|
|
||||||
int finalHeight = expandLayout.getHeight();
|
|
||||||
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
|
|
||||||
mAnimator.addListener(new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animator) {
|
|
||||||
expandLayout.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animator) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animator) {}
|
|
||||||
});
|
|
||||||
mAnimator.start();
|
|
||||||
mExpanded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator slideAnimator(int start, int end) {
|
|
||||||
|
|
||||||
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
|
||||||
|
|
||||||
animator.addUpdateListener(valueAnimator -> {
|
|
||||||
int value = (Integer) valueAnimator.getAnimatedValue();
|
|
||||||
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
|
|
||||||
layoutParams.height = value;
|
|
||||||
expandLayout.setLayoutParams(layoutParams);
|
|
||||||
});
|
|
||||||
return animator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
|
||||||
|
import com.topjohnwu.jarsigner.ByteArrayStream;
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
import dalvik.system.DexClassLoader;
|
||||||
|
|
||||||
|
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||||
|
|
||||||
|
public static final int SNET_VER = 2;
|
||||||
|
|
||||||
|
private static final String SNET_URL = "https://github.com/topjohnwu/MagiskManager/releases/download/v5.4.0/snet.apk";
|
||||||
|
private static final String PKG = "com.topjohnwu.snet";
|
||||||
|
|
||||||
|
private File dexPath;
|
||||||
|
private DexClassLoader loader;
|
||||||
|
|
||||||
|
public CheckSafetyNet(FragmentActivity activity) {
|
||||||
|
super(activity);
|
||||||
|
dexPath = new File(activity.getCacheDir().getParent() + "/snet", "snet.apk");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm.snet_version != CheckSafetyNet.SNET_VER) {
|
||||||
|
getShell().sh("rm -rf " + dexPath.getParent());
|
||||||
|
}
|
||||||
|
mm.snet_version = CheckSafetyNet.SNET_VER;
|
||||||
|
mm.prefs.edit().putInt("snet_version", CheckSafetyNet.SNET_VER).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Exception doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
if (!dexPath.exists()) {
|
||||||
|
HttpURLConnection conn = WebService.request(SNET_URL, null);
|
||||||
|
ByteArrayStream bas = new ByteArrayStream();
|
||||||
|
bas.readFrom(conn.getInputStream());
|
||||||
|
conn.disconnect();
|
||||||
|
dexPath.getParentFile().mkdir();
|
||||||
|
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath))) {
|
||||||
|
bas.writeTo(out);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(),
|
||||||
|
null, ClassLoader.getSystemClassLoader());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Exception err) {
|
||||||
|
try {
|
||||||
|
if (err != null) throw err;
|
||||||
|
Class<?> helperClazz = loader.loadClass(PKG + ".SafetyNetHelper");
|
||||||
|
Class<?> callbackClazz = loader.loadClass(PKG + ".SafetyNetCallback");
|
||||||
|
Object helper = helperClazz.getConstructors()[0].newInstance(
|
||||||
|
getActivity(), Proxy.newProxyInstance(
|
||||||
|
loader, new Class[] { callbackClazz }, (proxy, method, args) -> {
|
||||||
|
getMagiskManager().safetyNetDone.publish(false, args[0]);
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
helperClazz.getMethod("attest").invoke(helper);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
getMagiskManager().safetyNetDone.publish(false, -1);
|
||||||
|
}
|
||||||
|
super.onPostExecute(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
|
||||||
import android.support.v7.app.NotificationCompat;
|
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.BuildConfig;
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.SplashActivity;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.magisk.utils.WebService;
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
@@ -18,55 +12,65 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
private static final String UPDATE_JSON = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/updates/magisk_update.json";
|
public static final int STABLE_CHANNEL = 0;
|
||||||
private static final int NOTIFICATION_ID = 1;
|
public static final int BETA_CHANNEL = 1;
|
||||||
|
|
||||||
|
private static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
|
||||||
|
private static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
|
||||||
|
|
||||||
private boolean showNotification = false;
|
private boolean showNotification = false;
|
||||||
|
|
||||||
public CheckUpdates(Context context, boolean b) {
|
public CheckUpdates(Context context) {
|
||||||
this(context);
|
super(context);
|
||||||
showNotification = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckUpdates(Context context) {
|
public CheckUpdates(Context context, boolean b) {
|
||||||
magiskManager = Utils.getMagiskManager(context);
|
super(context);
|
||||||
|
showNotification = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null) return null;
|
||||||
|
String jsonStr;
|
||||||
|
switch (mm.updateChannel) {
|
||||||
|
case STABLE_CHANNEL:
|
||||||
|
jsonStr = WebService.getString(STABLE_URL);
|
||||||
|
break;
|
||||||
|
case BETA_CHANNEL:
|
||||||
|
jsonStr = WebService.getString(BETA_URL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
jsonStr = null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
JSONObject json = new JSONObject(jsonStr);
|
JSONObject json = new JSONObject(jsonStr);
|
||||||
JSONObject magisk = json.getJSONObject("magisk");
|
JSONObject magisk = json.getJSONObject("magisk");
|
||||||
magiskManager.remoteMagiskVersion = magisk.getDouble("versionCode");
|
mm.remoteMagiskVersionString = magisk.getString("version");
|
||||||
magiskManager.magiskLink = magisk.getString("link");
|
mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
|
||||||
magiskManager.releaseNoteLink = magisk.getString("note");
|
mm.magiskLink = magisk.getString("link");
|
||||||
} catch (JSONException ignored) {
|
mm.releaseNoteLink = magisk.getString("note");
|
||||||
}
|
JSONObject manager = json.getJSONObject("app");
|
||||||
|
mm.remoteManagerVersionString = manager.getString("version");
|
||||||
|
mm.remoteManagerVersionCode = manager.getInt("versionCode");
|
||||||
|
mm.managerLink = manager.getString("link");
|
||||||
|
} catch (JSONException ignored) {}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void v) {
|
protected void onPostExecute(Void v) {
|
||||||
if (magiskManager.magiskVersion < magiskManager.remoteMagiskVersion
|
MagiskManager mm = getMagiskManager();
|
||||||
&& showNotification && magiskManager.updateNotification) {
|
if (mm == null) return;
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(magiskManager);
|
if (showNotification && mm.updateNotification) {
|
||||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||||
.setContentTitle(magiskManager.getString(R.string.magisk_update_title))
|
Utils.showManagerUpdateNotification(mm);
|
||||||
.setContentText(magiskManager.getString(R.string.magisk_update_available, magiskManager.remoteMagiskVersion))
|
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||||
.setVibrate(new long[]{0, 100, 100, 100})
|
Utils.showMagiskUpdateNotification(mm);
|
||||||
.setAutoCancel(true);
|
}
|
||||||
Intent intent = new Intent(magiskManager, SplashActivity.class);
|
|
||||||
intent.putExtra(MagiskManager.INTENT_SECTION, "install");
|
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(magiskManager);
|
|
||||||
stackBuilder.addParentStack(SplashActivity.class);
|
|
||||||
stackBuilder.addNextIntent(intent);
|
|
||||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(NOTIFICATION_ID, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
builder.setContentIntent(pendingIntent);
|
|
||||||
NotificationManager notificationManager =
|
|
||||||
(NotificationManager) magiskManager.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
|
||||||
}
|
}
|
||||||
magiskManager.updateCheckDone.trigger();
|
mm.updateCheckDone.publish();
|
||||||
|
super.onPostExecute(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
public class DownloadBusybox extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private static final String BUSYBOX_ARM = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-arm";
|
||||||
|
private static final String BUSYBOX_X86 = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-x86";
|
||||||
|
|
||||||
|
private File busybox;
|
||||||
|
|
||||||
|
public DownloadBusybox(Context context) {
|
||||||
|
super(context);
|
||||||
|
busybox = new File(context.getCacheDir(), "busybox");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
Context context = getMagiskManager();
|
||||||
|
Utils.removeItem(getShell(), context.getApplicationInfo().dataDir + "/busybox");
|
||||||
|
try {
|
||||||
|
FileOutputStream out = new FileOutputStream(busybox);
|
||||||
|
HttpURLConnection conn = WebService.request(
|
||||||
|
Build.SUPPORTED_32_BIT_ABIS[0].contains("x86") ?
|
||||||
|
BUSYBOX_X86 :
|
||||||
|
BUSYBOX_ARM,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
if (conn == null) throw new IOException();
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int len;
|
||||||
|
while ((len = bis.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
conn.disconnect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (busybox.exists()) {
|
||||||
|
getShell().su(
|
||||||
|
"rm -rf " + MagiskManager.BUSYBOXPATH,
|
||||||
|
"mkdir -p " + MagiskManager.BUSYBOXPATH,
|
||||||
|
"cp " + busybox + " " + MagiskManager.BUSYBOXPATH,
|
||||||
|
"chmod -R 755 " + MagiskManager.BUSYBOXPATH,
|
||||||
|
MagiskManager.BUSYBOXPATH + "/busybox --install -s " + MagiskManager.BUSYBOXPATH
|
||||||
|
);
|
||||||
|
busybox.delete();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.widget.Toast;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
import com.topjohnwu.magisk.container.AdaptiveList;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -21,134 +20,98 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FlashZip extends SerialTask<Void, String, Integer> {
|
public class FlashZip extends ParallelTask<Void, Void, Integer> {
|
||||||
|
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
private File mCachedFile, mScriptFile, mCheckFile;
|
private File mCachedFile;
|
||||||
|
private AdaptiveList<String> mList;
|
||||||
|
|
||||||
private String mFilename;
|
public FlashZip(Activity context, Uri uri, AdaptiveList<String> list) {
|
||||||
private ProgressDialog progress;
|
|
||||||
|
|
||||||
public FlashZip(Activity context, Uri uri) {
|
|
||||||
super(context);
|
super(context);
|
||||||
mUri = uri;
|
mUri = uri;
|
||||||
|
mList = list;
|
||||||
mCachedFile = new File(magiskManager.getCacheDir(), "install.zip");
|
mCachedFile = new File(context.getCacheDir(), "install.zip");
|
||||||
mScriptFile = new File(magiskManager.getCacheDir(), "/META-INF/com/google/android/update-binary");
|
|
||||||
mCheckFile = new File(mScriptFile.getParent(), "updater-script");
|
|
||||||
|
|
||||||
// Try to get the filename ourselves
|
|
||||||
mFilename = Utils.getNameFromUri(magiskManager, mUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyToCache() throws Throwable {
|
private boolean unzipAndCheck() throws Exception {
|
||||||
publishProgress(magiskManager.getString(R.string.copying_msg));
|
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
|
||||||
|
List<String> ret = Utils.readFile(getShell(), new File(mCachedFile.getParentFile(), "updater-script").getPath());
|
||||||
if (mCachedFile.exists() && !mCachedFile.delete()) {
|
|
||||||
Logger.error("FlashZip: Error while deleting already existing file");
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
try (
|
|
||||||
InputStream in = magiskManager.getContentResolver().openInputStream(mUri);
|
|
||||||
OutputStream outputStream = new FileOutputStream(mCachedFile)
|
|
||||||
) {
|
|
||||||
byte buffer[] = new byte[1024];
|
|
||||||
int length;
|
|
||||||
if (in == null) throw new FileNotFoundException();
|
|
||||||
while ((length = in.read(buffer)) > 0)
|
|
||||||
outputStream.write(buffer, 0, length);
|
|
||||||
|
|
||||||
Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath());
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Logger.error("FlashZip: Invalid Uri");
|
|
||||||
throw e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Logger.error("FlashZip: Error in creating file");
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean unzipAndCheck() throws Exception {
|
|
||||||
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
|
|
||||||
List<String> ret;
|
|
||||||
ret = Utils.readFile(mCheckFile.getPath());
|
|
||||||
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int cleanup(int ret) {
|
|
||||||
Shell.su(
|
|
||||||
"rm -rf " + mCachedFile.getParent() + "/*",
|
|
||||||
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
|
|
||||||
);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
progress = new ProgressDialog(activity);
|
// UI updates must run in the UI thread
|
||||||
progress.setTitle(R.string.zip_install_progress_title);
|
mList.setCallback(this::publishProgress);
|
||||||
progress.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onProgressUpdate(String... values) {
|
protected void onProgressUpdate(Void... values) {
|
||||||
progress.setMessage(values[0]);
|
mList.updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(Void... voids) {
|
protected Integer doInBackground(Void... voids) {
|
||||||
Logger.dev("FlashZip Running... " + mFilename);
|
MagiskManager mm = getMagiskManager();
|
||||||
List<String> ret;
|
if (mm == null) return -1;
|
||||||
try {
|
try {
|
||||||
copyToCache();
|
mList.add("- Copying zip to temp directory");
|
||||||
if (!unzipAndCheck()) return cleanup(0);
|
|
||||||
publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
|
|
||||||
ret = Shell.su(
|
|
||||||
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile,
|
|
||||||
"if [ $? -eq 0 ]; then echo true; else echo false; fi"
|
|
||||||
);
|
|
||||||
if (!Utils.isValidShellResponse(ret)) return -1;
|
|
||||||
Logger.dev("FlashZip: Console log:");
|
|
||||||
for (String line : ret) {
|
|
||||||
Logger.dev(line);
|
|
||||||
}
|
|
||||||
if (Boolean.parseBoolean(ret.get(ret.size() - 1)))
|
|
||||||
return cleanup(1);
|
|
||||||
|
|
||||||
} catch (Throwable e) {
|
mCachedFile.delete();
|
||||||
|
try (
|
||||||
|
InputStream in = mm.getContentResolver().openInputStream(mUri);
|
||||||
|
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
|
||||||
|
) {
|
||||||
|
if (in == null) throw new FileNotFoundException();
|
||||||
|
InputStream buf= new BufferedInputStream(in);
|
||||||
|
byte buffer[] = new byte[4096];
|
||||||
|
int length;
|
||||||
|
while ((length = buf.read(buffer)) > 0)
|
||||||
|
out.write(buffer, 0, length);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
mList.add("! Invalid Uri");
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
mList.add("! Cannot copy to cache");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (!unzipAndCheck()) return 0;
|
||||||
|
mList.add("- Installing " + Utils.getNameFromUri(mm, mUri));
|
||||||
|
getShell().su(mList,
|
||||||
|
"cd " + mCachedFile.getParent(),
|
||||||
|
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile +
|
||||||
|
" && echo 'Success!' || echo 'Failed!'"
|
||||||
|
);
|
||||||
|
if (TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
|
||||||
|
return 1;
|
||||||
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return cleanup(-1);
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
// -1 = error, manual install; 0 = invalid zip; 1 = success
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Integer result) {
|
protected void onPostExecute(Integer result) {
|
||||||
super.onPostExecute(result);
|
MagiskManager mm = getMagiskManager();
|
||||||
progress.dismiss();
|
if (mm == null) return;
|
||||||
|
getShell().su_raw(
|
||||||
|
"rm -rf " + mCachedFile.getParent(),
|
||||||
|
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
|
||||||
|
);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case -1:
|
case -1:
|
||||||
Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show();
|
mList.add(mm.getString(R.string.install_error));
|
||||||
Utils.showUriSnack(activity, mUri);
|
Utils.showUriSnack(getActivity(), mUri);
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
|
mList.add(mm.getString(R.string.invalid_zip));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
onSuccess();
|
// Success
|
||||||
|
new LoadModules(mm).exec();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
super.onPostExecute(result);
|
||||||
|
|
||||||
protected void onSuccess() {
|
|
||||||
magiskManager.updateCheckDone.trigger();
|
|
||||||
new LoadModules(activity).exec();
|
|
||||||
|
|
||||||
new AlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.reboot_title)
|
|
||||||
.setMessage(R.string.reboot_msg)
|
|
||||||
.setPositiveButton(R.string.reboot, (dialogInterface, i) -> Shell.su(true, "reboot"))
|
|
||||||
.setNegativeButton(R.string.no_thanks, null)
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
public class GetBootBlocks extends SerialTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
public GetBootBlocks(Activity context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
magiskManager.blockList = Shell.su("ls /dev/block | grep mmc");
|
|
||||||
if (magiskManager.bootBlock == null) {
|
|
||||||
magiskManager.bootBlock = Utils.detectBootImage();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
magiskManager.blockDetectionDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
115
app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java
Normal file
115
app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.jarsigner.JarMap;
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
|
||||||
|
public class HideManager extends ParallelTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
|
private static final String UNHIDE_APK = "unhide.apk";
|
||||||
|
private static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||||
|
private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes();
|
||||||
|
|
||||||
|
public HideManager(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
getMagiskManager().toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Generate a new unhide app with random package name
|
||||||
|
File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk");
|
||||||
|
unhideAPK.getParentFile().mkdirs();
|
||||||
|
String pkg;
|
||||||
|
|
||||||
|
try {
|
||||||
|
JarMap asset = new JarMap(mm.getAssets().open(UNHIDE_APK));
|
||||||
|
JarEntry je = new JarEntry(ANDROID_MANIFEST);
|
||||||
|
byte xml[] = asset.getRawData(je);
|
||||||
|
int offset = -1;
|
||||||
|
|
||||||
|
// Linear search pattern offset
|
||||||
|
for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) {
|
||||||
|
boolean match = true;
|
||||||
|
for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) {
|
||||||
|
if (xml[i + j] != UNHIDE_PKG_NAME[j]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
offset = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offset < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Patch binary XML with new package name
|
||||||
|
pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1);
|
||||||
|
System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length());
|
||||||
|
asset.getOutputStream(je).write(xml);
|
||||||
|
|
||||||
|
// Sign the APK
|
||||||
|
ZipUtils.signZip(mm, asset, unhideAPK, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the application
|
||||||
|
List<String> ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false");
|
||||||
|
unhideAPK.delete();
|
||||||
|
if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Allow the application to gain root by default
|
||||||
|
PackageManager pm = mm.getPackageManager();
|
||||||
|
int uid = pm.getApplicationInfo(pkg, 0).uid;
|
||||||
|
Policy policy = new Policy(uid, pm);
|
||||||
|
policy.policy = Policy.ALLOW;
|
||||||
|
policy.notification = false;
|
||||||
|
policy.logging = false;
|
||||||
|
mm.suDB.addPolicy(policy);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide myself!
|
||||||
|
getShell().su_raw("pm hide " + mm.getPackageName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean b) {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null)
|
||||||
|
return;
|
||||||
|
if (!b) {
|
||||||
|
mm.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
super.onPostExecute(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
230
app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
230
app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.container.AdaptiveList;
|
||||||
|
import com.topjohnwu.magisk.container.TarEntry;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||||
|
|
||||||
|
import org.kamranzafar.jtar.TarInputStream;
|
||||||
|
import org.kamranzafar.jtar.TarOutputStream;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
|
private static final int PATCH_MODE = 0;
|
||||||
|
private static final int DIRECT_MODE = 1;
|
||||||
|
|
||||||
|
private Uri mBootImg, mZip;
|
||||||
|
private AdaptiveList<String> mList;
|
||||||
|
private String mBootLocation;
|
||||||
|
private boolean mKeepEnc, mKeepVerity;
|
||||||
|
private int mode;
|
||||||
|
|
||||||
|
private InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity) {
|
||||||
|
super(context);
|
||||||
|
mList = list;
|
||||||
|
mZip = zip;
|
||||||
|
mKeepEnc = enc;
|
||||||
|
mKeepVerity = verity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, Uri boot) {
|
||||||
|
this(context, list, zip, enc, verity);
|
||||||
|
mBootImg = boot;
|
||||||
|
mode = PATCH_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, String boot) {
|
||||||
|
this(context, list, zip, enc, verity);
|
||||||
|
mBootLocation = boot;
|
||||||
|
mode = DIRECT_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
// UI updates must run in the UI thread
|
||||||
|
mList.setCallback(this::publishProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Void... values) {
|
||||||
|
mList.updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null) return false;
|
||||||
|
|
||||||
|
File install = new File(Utils.getEncContext(mm).getFilesDir().getParent(), "install");
|
||||||
|
getShell().sh_raw("rm -rf " + install);
|
||||||
|
|
||||||
|
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||||
|
String arch;
|
||||||
|
if (abis.contains("x86_64")) arch = "x64";
|
||||||
|
else if (abis.contains("arm64-v8a")) arch = "arm64";
|
||||||
|
else if (abis.contains("x86")) arch = "x86";
|
||||||
|
else arch = "arm";
|
||||||
|
mList.add("- Device platform: " + arch);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Unzip files
|
||||||
|
mList.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, install, arch + "/", true);
|
||||||
|
buf.reset();
|
||||||
|
ZipUtils.unzip(buf, install, "common/", true);
|
||||||
|
buf.reset();
|
||||||
|
ZipUtils.unzip(buf, install, "chromeos/", false);
|
||||||
|
buf.reset();
|
||||||
|
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
mList.add("! Invalid Uri");
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
mList.add("! Cannot unzip zip");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
File boot;
|
||||||
|
switch (mode) {
|
||||||
|
case PATCH_MODE:
|
||||||
|
boot = new File(install, "boot.img");
|
||||||
|
// Copy boot image to local
|
||||||
|
try (
|
||||||
|
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
|
||||||
|
OutputStream out = new FileOutputStream(boot)
|
||||||
|
) {
|
||||||
|
InputStream source;
|
||||||
|
if (in == null) throw new FileNotFoundException();
|
||||||
|
|
||||||
|
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
|
||||||
|
// Extract boot.img from tar
|
||||||
|
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
|
||||||
|
org.kamranzafar.jtar.TarEntry entry;
|
||||||
|
while ((entry = tar.getNextEntry()) != null) {
|
||||||
|
if (entry.getName().equals("boot.img"))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
source = tar;
|
||||||
|
} else {
|
||||||
|
// Direct copy raw image
|
||||||
|
source = new BufferedInputStream(in);
|
||||||
|
}
|
||||||
|
byte buffer[] = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = source.read(buffer)) > 0)
|
||||||
|
out.write(buffer, 0, length);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
mList.add("! Invalid Uri");
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
mList.add("! Copy failed");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DIRECT_MODE:
|
||||||
|
boot = new File(mBootLocation);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mList.add("- Use boot image: " + boot);
|
||||||
|
|
||||||
|
Shell shell;
|
||||||
|
if (mode == PATCH_MODE && Shell.rootAccess()) {
|
||||||
|
// Force non-root shell
|
||||||
|
shell = Shell.getShell("sh");
|
||||||
|
} else {
|
||||||
|
shell = getShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch boot image
|
||||||
|
shell.sh(mList,
|
||||||
|
"cd " + install,
|
||||||
|
"KEEPFORCEENCRYPT=" + mKeepEnc + " KEEPVERITY=" + mKeepVerity + " sh " +
|
||||||
|
"update-binary indep boot_patch.sh " + boot +
|
||||||
|
" && echo 'Success!' || echo 'Failed!'"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
File patched_boot = new File(install, "new-boot.img");
|
||||||
|
mList.add("");
|
||||||
|
switch (mode) {
|
||||||
|
case PATCH_MODE:
|
||||||
|
File dest = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/patched_boot" + mm.bootFormat);
|
||||||
|
dest.getParentFile().mkdirs();
|
||||||
|
switch (mm.bootFormat) {
|
||||||
|
case ".img":
|
||||||
|
getShell().sh_raw("cp -f " + patched_boot + " " + dest);
|
||||||
|
break;
|
||||||
|
case ".img.tar":
|
||||||
|
TarOutputStream tar = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
|
||||||
|
tar.putNextEntry(new TarEntry(patched_boot, "boot.img"));
|
||||||
|
byte buffer[] = new byte[4096];
|
||||||
|
BufferedInputStream in = new BufferedInputStream(new FileInputStream(patched_boot));
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buffer)) != -1) {
|
||||||
|
tar.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
tar.flush();
|
||||||
|
tar.close();
|
||||||
|
in.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mList.add("*********************************");
|
||||||
|
mList.add(" Patched Boot Image is placed in ");
|
||||||
|
mList.add(" " + dest + " ");
|
||||||
|
mList.add("*********************************");
|
||||||
|
break;
|
||||||
|
case DIRECT_MODE:
|
||||||
|
// Direct flash boot image
|
||||||
|
getShell().su(mList, "flash_boot_image " + patched_boot + " " + mBootLocation);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finals
|
||||||
|
getShell().sh_raw(
|
||||||
|
"cd " + install,
|
||||||
|
"mv bin/busybox busybox",
|
||||||
|
"rm -rf bin *.img update-binary",
|
||||||
|
"cd /");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class LoadApps extends ParallelTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
public LoadApps(Activity context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
PackageManager pm = magiskManager.getPackageManager();
|
|
||||||
List<ApplicationInfo> list = pm.getInstalledApplications(0);
|
|
||||||
for (Iterator<ApplicationInfo> i = list.iterator(); i.hasNext(); ) {
|
|
||||||
ApplicationInfo info = i.next();
|
|
||||||
if (ApplicationAdapter.BLACKLIST.contains(info.packageName) || !info.enabled) {
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(list, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
|
|
||||||
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
|
|
||||||
magiskManager.appList = Collections.unmodifiableList(list);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
new MagiskHide(activity).list();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +1,37 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.module.BaseModule;
|
import com.topjohnwu.magisk.container.Module;
|
||||||
import com.topjohnwu.magisk.module.Module;
|
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
import com.topjohnwu.magisk.utils.ValueSortedMap;
|
|
||||||
|
|
||||||
public class LoadModules extends SerialTask<Void, Void, Void> {
|
public class LoadModules extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
public LoadModules(Activity context) {
|
public LoadModules(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
Logger.dev("LoadModules: Loading modules");
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null) return null;
|
||||||
|
mm.moduleMap = new ValueSortedMap<>();
|
||||||
|
|
||||||
magiskManager.moduleMap = new ValueSortedMap<>();
|
for (String path : Utils.getModList(getShell(), MagiskManager.MAGISK_PATH)) {
|
||||||
|
Module module = new Module(getShell(), path);
|
||||||
for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) {
|
mm.moduleMap.put(module.getId(), module);
|
||||||
Logger.dev("LoadModules: Adding modules from " + path);
|
|
||||||
Module module;
|
|
||||||
try {
|
|
||||||
module = new Module(path);
|
|
||||||
magiskManager.moduleMap.put(module.getId(), module);
|
|
||||||
} catch (BaseModule.CacheModException ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.dev("LoadModules: Data load done");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void v) {
|
protected void onPostExecute(Void v) {
|
||||||
magiskManager.moduleLoadDone.trigger();
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null) return;
|
||||||
|
mm.moduleLoadDone.publish();
|
||||||
|
super.onPostExecute(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
|
||||||
import com.topjohnwu.magisk.module.BaseModule;
|
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.ValueSortedMap;
|
|
||||||
import com.topjohnwu.magisk.utils.WebService;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class LoadRepos extends ParallelTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
public static final String ETAG_KEY = "ETag";
|
|
||||||
|
|
||||||
private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
|
||||||
private static final String IF_NONE_MATCH = "If-None-Match";
|
|
||||||
private static final String LINK_KEY = "Link";
|
|
||||||
|
|
||||||
private static final int CHECK_ETAG = 0;
|
|
||||||
private static final int LOAD_NEXT = 1;
|
|
||||||
private static final int LOAD_PREV = 2;
|
|
||||||
|
|
||||||
private List<String> etags;
|
|
||||||
private ValueSortedMap<String, Repo> cached, fetched;
|
|
||||||
private RepoDatabaseHelper repoDB;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
|
|
||||||
public LoadRepos(Activity context) {
|
|
||||||
super(context);
|
|
||||||
prefs = magiskManager.prefs;
|
|
||||||
String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
|
|
||||||
repoDB = new RepoDatabaseHelper(magiskManager);
|
|
||||||
// Legacy data cleanup
|
|
||||||
File old = new File(prefsPath, "RepoMap.xml");
|
|
||||||
if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) {
|
|
||||||
old.delete();
|
|
||||||
prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply();
|
|
||||||
repoDB.clearRepo();
|
|
||||||
}
|
|
||||||
etags = new ArrayList<>(
|
|
||||||
Arrays.asList(magiskManager.prefs.getString(ETAG_KEY, "").split(",")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadJSON(String jsonString) throws Exception {
|
|
||||||
JSONArray jsonArray = new JSONArray(jsonString);
|
|
||||||
|
|
||||||
for (int i = 0; i < jsonArray.length(); i++) {
|
|
||||||
JSONObject jsonobject = jsonArray.getJSONObject(i);
|
|
||||||
String id = jsonobject.getString("description");
|
|
||||||
String name = jsonobject.getString("name");
|
|
||||||
String lastUpdate = jsonobject.getString("pushed_at");
|
|
||||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
|
||||||
Date updatedDate = format.parse(lastUpdate);
|
|
||||||
Repo repo = cached.get(id);
|
|
||||||
try {
|
|
||||||
if (repo == null) {
|
|
||||||
Logger.dev("LoadRepos: Create new repo " + id);
|
|
||||||
repo = new Repo(name, updatedDate);
|
|
||||||
} else {
|
|
||||||
// Popout from cached
|
|
||||||
cached.remove(id);
|
|
||||||
repo.update(updatedDate);
|
|
||||||
}
|
|
||||||
if (repo.getId() != null) {
|
|
||||||
fetched.put(id, repo);
|
|
||||||
}
|
|
||||||
} catch (BaseModule.CacheModException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean loadPage(int page, String url, int mode) {
|
|
||||||
Logger.dev("LoadRepos: Loading page: " + (page + 1));
|
|
||||||
Map<String, String> header = new HashMap<>();
|
|
||||||
if (mode == CHECK_ETAG && page < etags.size() && !TextUtils.isEmpty(etags.get(page))) {
|
|
||||||
Logger.dev("ETAG: " + etags.get(page));
|
|
||||||
header.put(IF_NONE_MATCH, etags.get(page));
|
|
||||||
}
|
|
||||||
if (url == null) {
|
|
||||||
url = String.format(Locale.US, REPO_URL, page + 1);
|
|
||||||
}
|
|
||||||
String jsonString = WebService.request(url, WebService.GET, header, true);
|
|
||||||
if (TextUtils.isEmpty(jsonString)) {
|
|
||||||
// At least check the pages we know
|
|
||||||
return page + 1 < etags.size() && loadPage(page + 1, null, CHECK_ETAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The request succeed, parse the new stuffs
|
|
||||||
try {
|
|
||||||
loadJSON(jsonString);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the ETAG
|
|
||||||
String newEtag = header.get(ETAG_KEY);
|
|
||||||
newEtag = newEtag.substring(newEtag.indexOf('\"'), newEtag.lastIndexOf('\"') + 1);
|
|
||||||
Logger.dev("New ETAG: " + newEtag);
|
|
||||||
if (page < etags.size()) {
|
|
||||||
etags.set(page, newEtag);
|
|
||||||
} else {
|
|
||||||
etags.add(newEtag);
|
|
||||||
}
|
|
||||||
|
|
||||||
String links = header.get(LINK_KEY);
|
|
||||||
if (links != null) {
|
|
||||||
if (mode == CHECK_ETAG || mode == LOAD_NEXT) {
|
|
||||||
// Try to check next page URL
|
|
||||||
url = null;
|
|
||||||
for (String s : links.split(", ")) {
|
|
||||||
if (s.contains("next")) {
|
|
||||||
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (url != null) {
|
|
||||||
loadPage(page + 1, url, LOAD_NEXT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == CHECK_ETAG || mode == LOAD_PREV) {
|
|
||||||
// Try to check prev page URL
|
|
||||||
url = null;
|
|
||||||
for (String s : links.split(", ")) {
|
|
||||||
if (s.contains("prev")) {
|
|
||||||
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (url != null) {
|
|
||||||
loadPage(page - 1, url, LOAD_PREV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
Logger.dev("LoadRepos: Loading repos");
|
|
||||||
|
|
||||||
cached = repoDB.getRepoMap(false);
|
|
||||||
fetched = new ValueSortedMap<>();
|
|
||||||
|
|
||||||
if (!loadPage(0, null, CHECK_ETAG)) {
|
|
||||||
magiskManager.repoMap = repoDB.getRepoMap();
|
|
||||||
Logger.dev("LoadRepos: No updates, use DB");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
repoDB.addRepoMap(fetched);
|
|
||||||
repoDB.removeRepo(cached);
|
|
||||||
|
|
||||||
// Update ETag
|
|
||||||
StringBuilder etagBuilder = new StringBuilder();
|
|
||||||
for (int i = 0; i < etags.size(); ++i) {
|
|
||||||
if (i != 0) etagBuilder.append(",");
|
|
||||||
etagBuilder.append(etags.get(i));
|
|
||||||
}
|
|
||||||
prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply();
|
|
||||||
|
|
||||||
magiskManager.repoMap = repoDB.getRepoMap();
|
|
||||||
Logger.dev("LoadRepos: Done");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
magiskManager.repoLoadDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MagiskHide extends SerialTask<Object, Void, Void> {
|
|
||||||
|
|
||||||
private boolean isList = false;
|
|
||||||
|
|
||||||
public MagiskHide() {}
|
|
||||||
|
|
||||||
public MagiskHide(Activity context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Object... params) {
|
|
||||||
String command = (String) params[0];
|
|
||||||
List<String> ret = Shell.su(MagiskManager.MAGISK_HIDE_PATH + command);
|
|
||||||
if (isList) {
|
|
||||||
magiskManager.magiskHideList = ret;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
if (isList) {
|
|
||||||
magiskManager.magiskHideDone.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(CharSequence packageName) {
|
|
||||||
exec("add " + packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void rm(CharSequence packageName) {
|
|
||||||
exec("rm " + packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enable() {
|
|
||||||
exec("enable; setprop persist.magisk.hide 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disable() {
|
|
||||||
exec("disable; setprop persist.magisk.hide 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void list() {
|
|
||||||
isList = true;
|
|
||||||
if (magiskManager == null) return;
|
|
||||||
exec("list");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
|
||||||
|
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
|
||||||
|
|
||||||
|
private String mTitle, mUrl;
|
||||||
|
|
||||||
|
public MarkDownWindow(Activity context, String title, String url) {
|
||||||
|
super(context);
|
||||||
|
mTitle = title;
|
||||||
|
mUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... voids) {
|
||||||
|
String md = WebService.getString(mUrl);
|
||||||
|
Parser parser = Parser.builder().build();
|
||||||
|
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||||
|
Node doc = parser.parse(md);
|
||||||
|
return String.format(
|
||||||
|
"<link rel='stylesheet' type='text/css' href='file:///android_asset/%s.css'/> %s",
|
||||||
|
getMagiskManager().isDarkTheme ? "dark" : "light", renderer.render(doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String html) {
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||||
|
alert.setTitle(mTitle);
|
||||||
|
|
||||||
|
WebView wv = new WebView(getActivity());
|
||||||
|
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
|
||||||
|
|
||||||
|
alert.setView(wv);
|
||||||
|
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,59 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
||||||
protected Activity activity;
|
|
||||||
protected MagiskManager magiskManager;
|
private WeakReference<Activity> weakActivity;
|
||||||
|
private WeakReference<MagiskManager> weakMagiskManager;
|
||||||
|
|
||||||
|
private Runnable callback = null;
|
||||||
|
|
||||||
public ParallelTask() {}
|
public ParallelTask() {}
|
||||||
|
|
||||||
public ParallelTask(Activity context) {
|
public ParallelTask(Context context) {
|
||||||
activity = context;
|
weakMagiskManager = new WeakReference<>(Utils.getMagiskManager(context));
|
||||||
magiskManager = Utils.getMagiskManager(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
public ParallelTask(Activity context) {
|
||||||
public final void exec(Params... params) {
|
this((Context) context);
|
||||||
|
weakActivity = new WeakReference<>(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Activity getActivity() {
|
||||||
|
return weakActivity.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MagiskManager getMagiskManager() {
|
||||||
|
return weakMagiskManager.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Shell getShell() {
|
||||||
|
MagiskManager magiskManager = getMagiskManager();
|
||||||
|
return magiskManager == null ? null : Shell.getShell(magiskManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ParallelTask<Params, Progress, Result> exec(Params... params) {
|
||||||
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class ProcessMagiskZip extends ParallelTask<Void, Void, Boolean> {
|
|
||||||
|
|
||||||
private Uri mUri;
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
private String mBoot;
|
|
||||||
private boolean mEnc, mVerity;
|
|
||||||
|
|
||||||
public ProcessMagiskZip(Activity context, Uri uri, String boot, boolean enc, boolean verity) {
|
|
||||||
super(context);
|
|
||||||
mUri = uri;
|
|
||||||
mBoot = boot;
|
|
||||||
mEnc = enc;
|
|
||||||
mVerity = verity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
progressDialog = ProgressDialog.show(activity,
|
|
||||||
activity.getString(R.string.zip_process_title),
|
|
||||||
activity.getString(R.string.zip_unzip_msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(Void... params) {
|
|
||||||
if (Shell.rootAccess()) {
|
|
||||||
try {
|
|
||||||
// We might not have busybox yet, unzip with Java
|
|
||||||
// We shall have complete busybox after Magisk installation
|
|
||||||
File tempdir = new File(magiskManager.getCacheDir(), "magisk");
|
|
||||||
ZipUtils.unzip(magiskManager.getContentResolver().openInputStream(mUri), tempdir);
|
|
||||||
// Running in parallel mode, open new shell
|
|
||||||
Shell.su(true,
|
|
||||||
"rm -f /dev/.magisk",
|
|
||||||
(mBoot != null) ? "echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" >> /dev/.magisk" : "",
|
|
||||||
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
|
|
||||||
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk",
|
|
||||||
"mkdir -p " + MagiskManager.TMP_FOLDER_PATH,
|
|
||||||
"cp -af " + tempdir + "/. " + MagiskManager.TMP_FOLDER_PATH + "/magisk",
|
|
||||||
"mv -f " + tempdir + "/META-INF " + magiskManager.getCacheDir() + "/META-INF",
|
|
||||||
"rm -rf " + tempdir
|
|
||||||
);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.error("ProcessMagiskZip: Error!");
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean result) {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
if (result) {
|
|
||||||
new FlashZip(activity, mUri) {
|
|
||||||
@Override
|
|
||||||
protected boolean unzipAndCheck() throws Exception {
|
|
||||||
// Don't need to check, as it is downloaded in correct form
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSuccess() {
|
|
||||||
new SerialTask<Void, Void, Void>(activity) {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
Shell.su("setprop magisk.version "
|
|
||||||
+ String.valueOf(magiskManager.remoteMagiskVersion));
|
|
||||||
magiskManager.updateCheckDone.trigger();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
super.onSuccess();
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
} else {
|
|
||||||
Utils.showUriSnack(activity, mUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +1,160 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
import com.topjohnwu.magisk.container.InputStreamWrapper;
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
|
||||||
public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
|
public class ProcessRepoZip extends ParallelTask<Void, Object, Boolean> {
|
||||||
|
|
||||||
private Uri mUri;
|
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private boolean mInstall;
|
private boolean mInstall;
|
||||||
|
private String mLink;
|
||||||
|
private File mFile;
|
||||||
|
private int progress = 0, total = -1;
|
||||||
|
|
||||||
public ProcessRepoZip(Activity context, Uri uri, boolean install) {
|
private static final int UPDATE_DL_PROG = 0;
|
||||||
|
private static final int SHOW_PROCESSING = 1;
|
||||||
|
|
||||||
|
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
|
||||||
super(context);
|
super(context);
|
||||||
mUri = uri;
|
mLink = link;
|
||||||
|
mFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", filename);
|
||||||
mInstall = install;
|
mInstall = install;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeTopFolder(InputStream in, File output) throws IOException {
|
||||||
|
JarInputStream source = new JarInputStream(in);
|
||||||
|
JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output)));
|
||||||
|
JarEntry entry;
|
||||||
|
String path;
|
||||||
|
int size;
|
||||||
|
byte buffer[] = new byte[4096];
|
||||||
|
while ((entry = source.getNextJarEntry()) != null) {
|
||||||
|
// Remove the top directory from the path
|
||||||
|
path = entry.getName().substring(entry.getName().indexOf("/") + 1);
|
||||||
|
// If it's the top folder, ignore it
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Don't include placeholder
|
||||||
|
if (path.equals("system/placeholder")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dest.putNextEntry(new JarEntry(path));
|
||||||
|
while((size = source.read(buffer)) != -1) {
|
||||||
|
dest.write(buffer, 0, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
source.close();
|
||||||
|
dest.close();
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
progressDialog = ProgressDialog.show(activity,
|
Activity activity = getActivity();
|
||||||
activity.getString(R.string.zip_process_title),
|
mFile.getParentFile().mkdirs();
|
||||||
activity.getString(R.string.zip_process_msg));
|
progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Object... values) {
|
||||||
|
int mode = (int) values[0];
|
||||||
|
switch (mode) {
|
||||||
|
case UPDATE_DL_PROG:
|
||||||
|
int add = (int) values[1];
|
||||||
|
progress += add;
|
||||||
|
progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, 100 * progress / total));
|
||||||
|
break;
|
||||||
|
case SHOW_PROCESSING:
|
||||||
|
progressDialog.setTitle(R.string.zip_process_title);
|
||||||
|
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
Activity activity = getActivity();
|
||||||
FileInputStream in;
|
if (activity == null) return null;
|
||||||
FileOutputStream out;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Create temp file
|
// Request zip from Internet
|
||||||
File temp1 = new File(magiskManager.getCacheDir(), "1.zip");
|
HttpURLConnection conn;
|
||||||
File temp2 = new File(magiskManager.getCacheDir(), "2.zip");
|
do {
|
||||||
if (magiskManager.getCacheDir().mkdirs()) {
|
conn = WebService.request(mLink, null);
|
||||||
temp1.createNewFile();
|
if (conn == null) return null;
|
||||||
temp2.createNewFile();
|
total = conn.getContentLength();
|
||||||
}
|
if (total < 0)
|
||||||
|
conn.disconnect();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
} while (true);
|
||||||
|
|
||||||
out = new FileOutputStream(temp1);
|
InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream()));
|
||||||
|
|
||||||
// First remove top folder in Github source zip, Uri -> temp1
|
// Temp files
|
||||||
ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), out);
|
File temp1 = new File(activity.getCacheDir(), "1.zip");
|
||||||
out.flush();
|
File temp2 = new File(temp1.getParentFile(), "2.zip");
|
||||||
out.close();
|
temp1.getParentFile().mkdir();
|
||||||
|
|
||||||
out = new FileOutputStream(temp2);
|
// First remove top folder in Github source zip, Web -> temp1
|
||||||
|
removeTopFolder(in, temp1);
|
||||||
|
|
||||||
|
conn.disconnect();
|
||||||
|
publishProgress(SHOW_PROCESSING);
|
||||||
|
|
||||||
// Then sign the zip for the first time, temp1 -> temp2
|
// Then sign the zip for the first time, temp1 -> temp2
|
||||||
ZipUtils.signZip(activity, temp1, out, false);
|
ZipUtils.signZip(activity, temp1, temp2, false);
|
||||||
out.flush();
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
// Adjust the zip to prevent unzip issues, temp2 -> temp2
|
// Adjust the zip to prevent unzip issues, temp2 -> temp1
|
||||||
ZipUtils.adjustZip(temp2);
|
ZipUtils.zipAdjust(temp2.getPath(), temp1.getPath());
|
||||||
|
|
||||||
out = new FileOutputStream(temp1);
|
// Finally, sign the whole zip file again, temp1 -> temp2
|
||||||
|
ZipUtils.signZip(activity, temp1, temp2, true);
|
||||||
|
|
||||||
// Finally, sign the whole zip file again, temp2 -> temp1
|
// Write it to the target zip, temp2 -> file
|
||||||
ZipUtils.signZip(activity, temp2, out, true);
|
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(mFile));
|
||||||
out.flush();
|
InputStream source = new BufferedInputStream(new FileInputStream(temp2))
|
||||||
out.close();
|
) {
|
||||||
|
|
||||||
in = new FileInputStream(temp1);
|
|
||||||
|
|
||||||
// Write it back to the downloaded zip, temp1 -> Uri
|
|
||||||
try (OutputStream target = activity.getContentResolver().openOutputStream(mUri)) {
|
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
int length;
|
int length;
|
||||||
if (target == null) throw new FileNotFoundException();
|
while ((length = source.read(buffer)) > 0)
|
||||||
while ((length = in.read(buffer)) > 0)
|
out.write(buffer, 0, length);
|
||||||
target.write(buffer, 0, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the temp file
|
// Delete temp files
|
||||||
temp1.delete();
|
temp1.delete();
|
||||||
temp2.delete();
|
temp2.delete();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.error("ProcessRepoZip: Error!");
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -101,16 +162,53 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) return;
|
||||||
progressDialog.dismiss();
|
progressDialog.dismiss();
|
||||||
|
Uri uri = Uri.fromFile(mFile);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (Shell.rootAccess() && mInstall) {
|
if (Shell.rootAccess() && mInstall) {
|
||||||
new FlashZip(activity, mUri).exec();
|
Intent intent = new Intent(getActivity(), FlashActivity.class);
|
||||||
|
intent.setData(uri).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
|
||||||
|
activity.startActivity(intent);
|
||||||
} else {
|
} else {
|
||||||
Utils.showUriSnack(activity, mUri);
|
Utils.showUriSnack(activity, uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show();
|
Utils.getMagiskManager(activity).toast(R.string.process_error, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
super.onPostExecute(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParallelTask<Void, Object, Boolean> exec(Void... voids) {
|
||||||
|
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
() -> super.exec(voids));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProgressInputStream extends InputStreamWrapper {
|
||||||
|
|
||||||
|
ProgressInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int read() throws IOException {
|
||||||
|
publishProgress(UPDATE_DL_PROG, 1);
|
||||||
|
return super.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(@NonNull byte[] b) throws IOException {
|
||||||
|
return read(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||||
|
int read = super.read(b, off, len);
|
||||||
|
publishProgress(UPDATE_DL_PROG, read);
|
||||||
|
return read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RestoreStockBoot extends ParallelTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
|
private String mBoot;
|
||||||
|
|
||||||
|
public RestoreStockBoot(Context context, String boot) {
|
||||||
|
super(context);
|
||||||
|
mBoot = boot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
List<String> ret = getShell().su("cat /init.magisk.rc | grep STOCKSHA1");
|
||||||
|
if (!Utils.isValidShellResponse(ret))
|
||||||
|
return false;
|
||||||
|
String stock_boot = "/data/stock_boot_" + ret.get(0).substring(ret.get(0).indexOf('=') + 1) + ".img.gz";
|
||||||
|
if (!Utils.itemExist(getShell(), stock_boot))
|
||||||
|
return false;
|
||||||
|
getShell().su_raw("flash_boot_image " + stock_boot + " " + mBoot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
if (result) {
|
||||||
|
getMagiskManager().toast(R.string.restore_done, Toast.LENGTH_SHORT);
|
||||||
|
} else {
|
||||||
|
getMagiskManager().toast(R.string.restore_fail, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.asyncs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is only used for running root commands
|
|
||||||
**/
|
|
||||||
|
|
||||||
public abstract class SerialTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
|
|
||||||
protected Activity activity;
|
|
||||||
protected MagiskManager magiskManager;
|
|
||||||
|
|
||||||
public SerialTask() {}
|
|
||||||
|
|
||||||
public SerialTask(Activity context) {
|
|
||||||
activity = context;
|
|
||||||
magiskManager = Utils.getMagiskManager(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final void exec(Params... params) {
|
|
||||||
if (!Shell.rootAccess()) return;
|
|
||||||
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
219
app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
219
app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package com.topjohnwu.magisk.asyncs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.ReposFragment;
|
||||||
|
import com.topjohnwu.magisk.container.Repo;
|
||||||
|
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||||
|
import com.topjohnwu.magisk.utils.Logger;
|
||||||
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
||||||
|
|
||||||
|
public static final String ETAG_KEY = "ETag";
|
||||||
|
private static final String IF_NONE_MATCH = "If-None-Match";
|
||||||
|
private static final String LINK_KEY = "Link";
|
||||||
|
|
||||||
|
private static final int CHECK_ETAG = 0;
|
||||||
|
private static final int LOAD_NEXT = 1;
|
||||||
|
private static final int LOAD_PREV = 2;
|
||||||
|
|
||||||
|
private List<String> cached, etags, newEtags = new ArrayList<>();
|
||||||
|
private RepoDatabaseHelper repoDB;
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private boolean forceUpdate;
|
||||||
|
|
||||||
|
private int tasks = 0;
|
||||||
|
|
||||||
|
public UpdateRepos(Context context, boolean force) {
|
||||||
|
super(context);
|
||||||
|
prefs = getMagiskManager().prefs;
|
||||||
|
repoDB = getMagiskManager().repoDB;
|
||||||
|
getMagiskManager().repoLoadDone.hasPublished = false;
|
||||||
|
// Legacy data cleanup
|
||||||
|
File old = new File(context.getApplicationInfo().dataDir + "/shared_prefs", "RepoMap.xml");
|
||||||
|
if (old.exists() || prefs.getString("repomap", null) != null) {
|
||||||
|
old.delete();
|
||||||
|
prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply();
|
||||||
|
repoDB.clearRepo();
|
||||||
|
}
|
||||||
|
forceUpdate = force;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadJSON(String jsonString) throws Exception {
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonString);
|
||||||
|
|
||||||
|
// Empty page, throw error
|
||||||
|
if (jsonArray.length() == 0) throw new Exception();
|
||||||
|
|
||||||
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
JSONObject jsonobject = jsonArray.getJSONObject(i);
|
||||||
|
String id = jsonobject.getString("description");
|
||||||
|
String name = jsonobject.getString("name");
|
||||||
|
String lastUpdate = jsonobject.getString("pushed_at");
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||||
|
Date updatedDate = format.parse(lastUpdate);
|
||||||
|
++tasks;
|
||||||
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||||
|
Repo repo = repoDB.getRepo(id);
|
||||||
|
Boolean updated;
|
||||||
|
try {
|
||||||
|
if (repo == null) {
|
||||||
|
repo = new Repo(name, updatedDate);
|
||||||
|
updated = true;
|
||||||
|
} else {
|
||||||
|
// Popout from cached
|
||||||
|
cached.remove(id);
|
||||||
|
if (forceUpdate) {
|
||||||
|
repo.update();
|
||||||
|
updated = true;
|
||||||
|
} else {
|
||||||
|
updated = repo.update(updatedDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated) {
|
||||||
|
repoDB.addRepo(repo);
|
||||||
|
publishProgress();
|
||||||
|
}
|
||||||
|
if (!id.equals(repo.getId())) {
|
||||||
|
Logger.error("Repo [" + name + "] id=[" + repo.getId() + "] has illegal repo id");
|
||||||
|
}
|
||||||
|
} catch (Repo.IllegalRepoException e) {
|
||||||
|
Logger.error(e.getMessage());
|
||||||
|
repoDB.removeRepo(id);
|
||||||
|
}
|
||||||
|
--tasks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean loadPage(int page, int mode) {
|
||||||
|
Map<String, String> header = new HashMap<>();
|
||||||
|
String etag = "";
|
||||||
|
if (mode == CHECK_ETAG && page < etags.size()) {
|
||||||
|
etag = etags.get(page);
|
||||||
|
}
|
||||||
|
header.put(IF_NONE_MATCH, etag);
|
||||||
|
String url = String.format(Locale.US, REPO_URL, page + 1);
|
||||||
|
HttpURLConnection conn = WebService.request(url, header);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (conn == null) throw new Exception();
|
||||||
|
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||||
|
newEtags.add(etag);
|
||||||
|
return page + 1 < etags.size() && loadPage(page + 1, CHECK_ETAG);
|
||||||
|
}
|
||||||
|
loadJSON(WebService.getString(conn));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// Don't continue
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ETAG
|
||||||
|
etag = header.get(ETAG_KEY);
|
||||||
|
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||||
|
newEtags.add(etag);
|
||||||
|
|
||||||
|
String links = header.get(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);
|
||||||
|
} else if (mode != LOAD_NEXT && s.contains("prev")) {
|
||||||
|
// Back propagation
|
||||||
|
loadPage(page - 1, LOAD_PREV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Void waitTasks() {
|
||||||
|
while (tasks > 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(5);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Void... values) {
|
||||||
|
if (ReposFragment.adapter != null)
|
||||||
|
ReposFragment.adapter.notifyDBChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
etags = new ArrayList<>(Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
|
||||||
|
cached = repoDB.getRepoIDList();
|
||||||
|
|
||||||
|
if (!loadPage(0, CHECK_ETAG)) {
|
||||||
|
// Nothing changed online
|
||||||
|
if (forceUpdate) {
|
||||||
|
for (String id : cached) {
|
||||||
|
if (id == null) continue;
|
||||||
|
++tasks;
|
||||||
|
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||||
|
Repo repo = repoDB.getRepo(id);
|
||||||
|
try {
|
||||||
|
repo.update();
|
||||||
|
repoDB.addRepo(repo);
|
||||||
|
} catch (Repo.IllegalRepoException e) {
|
||||||
|
Logger.error(e.getMessage());
|
||||||
|
repoDB.removeRepo(repo);
|
||||||
|
}
|
||||||
|
--tasks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return waitTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait till all tasks are done
|
||||||
|
waitTasks();
|
||||||
|
|
||||||
|
// The leftover cached means they are removed from online repo
|
||||||
|
repoDB.removeRepo(cached);
|
||||||
|
|
||||||
|
// Update ETag
|
||||||
|
StringBuilder etagBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < newEtags.size(); ++i) {
|
||||||
|
if (i != 0) etagBuilder.append(",");
|
||||||
|
etagBuilder.append(newEtags.get(i));
|
||||||
|
}
|
||||||
|
prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void v) {
|
||||||
|
MagiskManager mm = getMagiskManager();
|
||||||
|
if (mm == null) return;
|
||||||
|
mm.repoLoadDone.publish();
|
||||||
|
super.onPostExecute(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,79 @@
|
|||||||
package com.topjohnwu.magisk.components;
|
package com.topjohnwu.magisk.components;
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
|
||||||
public class Activity extends AppCompatActivity {
|
public class Activity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private Runnable permissionGrantCallback;
|
||||||
|
|
||||||
|
public Activity() {
|
||||||
|
super();
|
||||||
|
Configuration configuration = new Configuration();
|
||||||
|
configuration.setLocale(MagiskManager.locale);
|
||||||
|
applyOverrideConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MagiskManager getApplicationContext() {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (this instanceof Topic.Subscriber) {
|
||||||
|
((Topic.Subscriber) this).subscribeTopics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
if (this instanceof Topic.Subscriber) {
|
||||||
|
((Topic.Subscriber) this).unsubscribeTopics();
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (permissionGrantCallback != null) {
|
||||||
|
permissionGrantCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
permissionGrantCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissionGrantCallback(Runnable callback) {
|
||||||
|
permissionGrantCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MagiskManager getMagiskManager() {
|
||||||
return (MagiskManager) super.getApplicationContext();
|
return (MagiskManager) super.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Shell getShell() {
|
||||||
|
return Shell.getShell(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setFloating() {
|
||||||
|
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
|
||||||
|
if (isTablet) {
|
||||||
|
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||||
|
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
|
||||||
|
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
|
||||||
|
params.alpha = 1.0f;
|
||||||
|
params.dimAmount = 0.6f;
|
||||||
|
params.flags |= 2;
|
||||||
|
getWindow().setAttributes(params);
|
||||||
|
setFinishOnTouchOutside(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.components;
|
package com.topjohnwu.magisk.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@@ -37,12 +37,12 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
|
|||||||
|
|
||||||
private AlertDialog dialog;
|
private AlertDialog dialog;
|
||||||
|
|
||||||
public AlertDialogBuilder(@NonNull Context context) {
|
public AlertDialogBuilder(@NonNull Activity context) {
|
||||||
super(context);
|
super(context);
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlertDialogBuilder(@NonNull Context context, @StyleRes int themeResId) {
|
public AlertDialogBuilder(@NonNull Activity context, @StyleRes int themeResId) {
|
||||||
super(context, themeResId);
|
super(context, themeResId);
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.topjohnwu.magisk.components;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
|
public interface ExpandableView {
|
||||||
|
|
||||||
|
class Container {
|
||||||
|
public ViewGroup expandLayout;
|
||||||
|
ValueAnimator expandAnimator, collapseAnimator;
|
||||||
|
boolean mExpanded = false;
|
||||||
|
int expandHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide state info
|
||||||
|
Container getContainer();
|
||||||
|
|
||||||
|
default void setupExpandable() {
|
||||||
|
Container container = getContainer();
|
||||||
|
container.expandLayout.getViewTreeObserver().addOnPreDrawListener(
|
||||||
|
new ViewTreeObserver.OnPreDrawListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
if (container.expandHeight == 0) {
|
||||||
|
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||||
|
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||||
|
container.expandLayout.measure(widthSpec, heightSpec);
|
||||||
|
container.expandHeight = container.expandLayout.getMeasuredHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
container.expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
container.expandLayout.setVisibility(View.GONE);
|
||||||
|
container.expandAnimator = slideAnimator(0, container.expandHeight);
|
||||||
|
container.collapseAnimator = slideAnimator(container.expandHeight, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isExpanded() {
|
||||||
|
return getContainer().mExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setExpanded(boolean expanded) {
|
||||||
|
Container container = getContainer();
|
||||||
|
container.mExpanded = expanded;
|
||||||
|
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||||
|
layoutParams.height = expanded ? container.expandHeight : 0;
|
||||||
|
container.expandLayout.setLayoutParams(layoutParams);
|
||||||
|
container.expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void expand() {
|
||||||
|
Container container = getContainer();
|
||||||
|
if (container.mExpanded) return;
|
||||||
|
container.expandLayout.setVisibility(View.VISIBLE);
|
||||||
|
container.expandAnimator.start();
|
||||||
|
container.mExpanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void collapse() {
|
||||||
|
Container container = getContainer();
|
||||||
|
if (!container.mExpanded) return;
|
||||||
|
container.collapseAnimator.start();
|
||||||
|
container.mExpanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default ValueAnimator slideAnimator(int start, int end) {
|
||||||
|
Container container = getContainer();
|
||||||
|
ValueAnimator animator = ValueAnimator.ofInt(start, end);
|
||||||
|
|
||||||
|
animator.addUpdateListener(valueAnimator -> {
|
||||||
|
int value = (Integer) valueAnimator.getAnimatedValue();
|
||||||
|
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
|
||||||
|
layoutParams.height = value;
|
||||||
|
container.expandLayout.setLayoutParams(layoutParams);
|
||||||
|
});
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,53 @@
|
|||||||
package com.topjohnwu.magisk.components;
|
package com.topjohnwu.magisk.components;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
public class Fragment extends android.support.v4.app.Fragment {
|
public class Fragment extends android.support.v4.app.Fragment {
|
||||||
|
|
||||||
|
private ActivityResultListener activityResultListener;
|
||||||
|
|
||||||
public MagiskManager getApplication() {
|
public MagiskManager getApplication() {
|
||||||
return Utils.getMagiskManager(getActivity());
|
return Utils.getMagiskManager(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Shell getShell() {
|
||||||
|
return Shell.getShell(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 onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (activityResultListener != null)
|
||||||
|
activityResultListener.onActivityResult(requestCode, resultCode, data);
|
||||||
|
activityResultListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
|
||||||
|
activityResultListener = listener;
|
||||||
|
super.startActivityForResult(intent, requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ActivityResultListener {
|
||||||
|
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.components;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
import us.feras.mdv.MarkdownView;
|
|
||||||
|
|
||||||
public class MarkDownWindow {
|
|
||||||
|
|
||||||
public MarkDownWindow(String title, String url, Context context) {
|
|
||||||
MagiskManager magiskManager = Utils.getMagiskManager(context);
|
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
|
||||||
alert.setTitle(title);
|
|
||||||
|
|
||||||
Logger.dev("WebView: URL = " + url);
|
|
||||||
|
|
||||||
MarkdownView md = new MarkdownView(context);
|
|
||||||
md.loadMarkdownFile(url, "file:///android_asset/" +
|
|
||||||
(magiskManager.isDarkTheme ? "dark" : "light") + ".css");
|
|
||||||
|
|
||||||
alert.setView(md);
|
|
||||||
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,6 @@ import android.support.design.widget.Snackbar;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
|
|
||||||
public class SnackbarMaker {
|
public class SnackbarMaker {
|
||||||
|
|
||||||
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
public static Snackbar make(Activity activity, CharSequence text, int duration) {
|
||||||
@@ -32,7 +30,7 @@ public class SnackbarMaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setup(Snackbar snack) {
|
private static void setup(Snackbar snack) {
|
||||||
TextView text = ButterKnife.findById(snack.getView(), android.support.design.R.id.snackbar_text);
|
TextView text = snack.getView().findViewById(android.support.design.R.id.snackbar_text);
|
||||||
text.setMaxLines(Integer.MAX_VALUE);
|
text.setMaxLines(Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class AdaptiveList<E> extends ArrayList<E> {
|
||||||
|
|
||||||
|
private Runnable callback;
|
||||||
|
private RecyclerView mView;
|
||||||
|
|
||||||
|
public AdaptiveList(RecyclerView v) {
|
||||||
|
mView = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateView() {
|
||||||
|
mView.getAdapter().notifyDataSetChanged();
|
||||||
|
mView.scrollToPosition(mView.getAdapter().getItemCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(Runnable cb) {
|
||||||
|
callback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean add(E e) {
|
||||||
|
boolean ret = super.add(e);
|
||||||
|
if (ret) {
|
||||||
|
if (callback == null) {
|
||||||
|
updateView();
|
||||||
|
} else {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
package com.topjohnwu.magisk.module;
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
public abstract class BaseModule implements Comparable<BaseModule> {
|
||||||
|
|
||||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
private String mId = null, mName, mVersion, mAuthor, mDescription;
|
||||||
private int mVersionCode = 0, templateVersion = 0;
|
private int mVersionCode = -1, templateVersion = -1;
|
||||||
|
|
||||||
protected BaseModule() {}
|
protected BaseModule() {}
|
||||||
|
|
||||||
@@ -25,9 +24,21 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
templateVersion = c.getInt(c.getColumnIndex("template"));
|
templateVersion = c.getInt(c.getColumnIndex("template"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseProps(List<String> props) throws CacheModException { parseProps(props.toArray(new String[props.size()])); }
|
public ContentValues getContentValues() {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put("id", mId);
|
||||||
|
values.put("name", mName);
|
||||||
|
values.put("version", mVersion);
|
||||||
|
values.put("versionCode", mVersionCode);
|
||||||
|
values.put("author", mAuthor);
|
||||||
|
values.put("description", mDescription);
|
||||||
|
values.put("template", templateVersion);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
protected void parseProps(String[] props) throws CacheModException {
|
protected void parseProps(List<String> props) { parseProps(props.toArray(new String[0])); }
|
||||||
|
|
||||||
|
protected void parseProps(String[] props) throws NumberFormatException {
|
||||||
for (String line : props) {
|
for (String line : props) {
|
||||||
String[] prop = line.split("=", 2);
|
String[] prop = line.split("=", 2);
|
||||||
if (prop.length != 2)
|
if (prop.length != 2)
|
||||||
@@ -48,9 +59,7 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
mVersion = prop[1];
|
mVersion = prop[1];
|
||||||
break;
|
break;
|
||||||
case "versionCode":
|
case "versionCode":
|
||||||
try {
|
mVersionCode = Integer.parseInt(prop[1]);
|
||||||
mVersionCode = Integer.parseInt(prop[1]);
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
break;
|
break;
|
||||||
case "author":
|
case "author":
|
||||||
mAuthor = prop[1];
|
mAuthor = prop[1];
|
||||||
@@ -59,12 +68,7 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
mDescription = prop[1];
|
mDescription = prop[1];
|
||||||
break;
|
break;
|
||||||
case "template":
|
case "template":
|
||||||
try {
|
templateVersion = Integer.parseInt(prop[1]);
|
||||||
templateVersion = Integer.parseInt(prop[1]);
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
case "cacheModule":
|
|
||||||
if (Boolean.parseBoolean(prop[1]))
|
|
||||||
throw new CacheModException(mId);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -108,12 +112,6 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
|||||||
return templateVersion;
|
return templateVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CacheModException extends Exception {
|
|
||||||
public CacheModException(String id) {
|
|
||||||
Logger.error("Cache mods are no longer supported! id: " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NonNull BaseModule module) {
|
public int compareTo(@NonNull BaseModule module) {
|
||||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class InputStreamWrapper extends InputStream {
|
||||||
|
private InputStream in;
|
||||||
|
|
||||||
|
public InputStreamWrapper(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return in.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int readlimit) {
|
||||||
|
in.mark(readlimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return in.markSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int read() throws IOException {
|
||||||
|
return in.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(@NonNull byte[] b) throws IOException {
|
||||||
|
return read(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||||
|
return in.read(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
in.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
return in.skip(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
app/src/main/java/com/topjohnwu/magisk/container/Module.java
Normal file
67
app/src/main/java/com/topjohnwu/magisk/container/Module.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.utils.Shell;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
public class Module extends BaseModule {
|
||||||
|
|
||||||
|
private String mRemoveFile, mDisableFile, mUpdateFile;
|
||||||
|
private boolean mEnable, mRemove, mUpdated;
|
||||||
|
|
||||||
|
public Module(Shell shell, String path) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
parseProps(Utils.readFile(shell, path + "/module.prop"));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
|
mRemoveFile = path + "/remove";
|
||||||
|
mDisableFile = path + "/disable";
|
||||||
|
mUpdateFile = path + "/update";
|
||||||
|
|
||||||
|
if (getId() == null) {
|
||||||
|
int sep = path.lastIndexOf('/');
|
||||||
|
setId(path.substring(sep + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getName() == null) {
|
||||||
|
setName(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
mEnable = !Utils.itemExist(shell, mDisableFile);
|
||||||
|
mRemove = Utils.itemExist(shell, mRemoveFile);
|
||||||
|
mUpdated = Utils.itemExist(shell, mUpdateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createDisableFile(Shell shell) {
|
||||||
|
mEnable = false;
|
||||||
|
Utils.createFile(shell, mDisableFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDisableFile(Shell shell) {
|
||||||
|
mEnable = true;
|
||||||
|
Utils.removeItem(shell, mDisableFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return mEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createRemoveFile(Shell shell) {
|
||||||
|
mRemove = true;
|
||||||
|
Utils.createFile(shell, mRemoveFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteRemoveFile(Shell shell) {
|
||||||
|
mRemove = false;
|
||||||
|
Utils.removeItem(shell, mRemoveFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean willBeRemoved() {
|
||||||
|
return mRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return mUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,51 +1,57 @@
|
|||||||
package com.topjohnwu.magisk.superuser;
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
|
||||||
public class Policy {
|
public class Policy implements Comparable<Policy>{
|
||||||
public static final int INTERACTIVE = 0;
|
public static final int INTERACTIVE = 0;
|
||||||
public static final int DENY = 1;
|
public static final int DENY = 1;
|
||||||
public static final int ALLOW = 2;
|
public static final int ALLOW = 2;
|
||||||
|
|
||||||
public int uid, policy;
|
public int uid, policy = INTERACTIVE;
|
||||||
public long until;
|
public long until;
|
||||||
public boolean logging = true, notification = true;
|
public boolean logging = true, notification = true;
|
||||||
public String packageName, appName;
|
public String packageName, appName;
|
||||||
public PackageInfo info;
|
public ApplicationInfo info;
|
||||||
|
|
||||||
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||||
String[] pkgs = pm.getPackagesForUid(uid);
|
String[] pkgs = pm.getPackagesForUid(uid);
|
||||||
if (pkgs != null && pkgs.length > 0) {
|
if (pkgs != null && pkgs.length > 0) {
|
||||||
info = pm.getPackageInfo(pkgs[0], 0);
|
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
packageName = pkgs[0];
|
packageName = pkgs[0];
|
||||||
appName = info.applicationInfo.loadLabel(pm).toString();
|
info = pm.getApplicationInfo(packageName, 0);
|
||||||
|
appName = info.loadLabel(pm).toString();
|
||||||
} else throw new PackageManager.NameNotFoundException();
|
} else throw new PackageManager.NameNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Policy(Cursor c) {
|
public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||||
uid = c.getInt(c.getColumnIndex("uid"));
|
uid = c.getInt(c.getColumnIndex("uid"));
|
||||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||||
appName = c.getString(c.getColumnIndex("app_name"));
|
|
||||||
policy = c.getInt(c.getColumnIndex("policy"));
|
policy = c.getInt(c.getColumnIndex("policy"));
|
||||||
until = c.getLong(c.getColumnIndex("until"));
|
until = c.getLong(c.getColumnIndex("until"));
|
||||||
logging = c.getInt(c.getColumnIndex("logging")) != 0;
|
logging = c.getInt(c.getColumnIndex("logging")) != 0;
|
||||||
notification = c.getInt(c.getColumnIndex("notification")) != 0;
|
notification = c.getInt(c.getColumnIndex("notification")) != 0;
|
||||||
|
info = pm.getApplicationInfo(packageName, 0);
|
||||||
|
appName = info.loadLabel(pm).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
public ContentValues getContentValues() {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put("uid", uid);
|
values.put("uid", uid);
|
||||||
values.put("package_name", packageName);
|
values.put("package_name", packageName);
|
||||||
values.put("app_name", appName);
|
|
||||||
values.put("policy", policy);
|
values.put("policy", policy);
|
||||||
values.put("until", until);
|
values.put("until", until);
|
||||||
values.put("logging", logging ? 1 : 0);
|
values.put("logging", logging ? 1 : 0);
|
||||||
values.put("notification", notification ? 1 : 0);
|
values.put("notification", notification ? 1 : 0);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NonNull Policy policy) {
|
||||||
|
return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
package com.topjohnwu.magisk.module;
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.WebService;
|
import com.topjohnwu.magisk.utils.WebService;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class Repo extends BaseModule {
|
public class Repo extends BaseModule {
|
||||||
|
|
||||||
|
public static final int MIN_TEMPLATE_VER = 4;
|
||||||
|
|
||||||
private static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
private static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||||
private static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
private static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||||
|
|
||||||
private String repoName;
|
private String repoName;
|
||||||
private Date mLastUpdate;
|
private Date mLastUpdate;
|
||||||
|
|
||||||
public Repo(String name, Date lastUpdate) throws CacheModException {
|
public Repo(String name, Date lastUpdate) throws IllegalRepoException {
|
||||||
mLastUpdate = lastUpdate;
|
mLastUpdate = lastUpdate;
|
||||||
repoName = name;
|
repoName = name;
|
||||||
update();
|
update();
|
||||||
@@ -28,34 +29,46 @@ public class Repo extends BaseModule {
|
|||||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() throws CacheModException {
|
public void update() throws IllegalRepoException {
|
||||||
String props = WebService.request(getManifestUrl(), WebService.GET);
|
String props = WebService.getString(getManifestUrl());
|
||||||
String lines[] = props.split("\\n");
|
String lines[] = props.split("\\n");
|
||||||
parseProps(lines);
|
try {
|
||||||
Logger.dev("Repo: Fetching prop: " + getId());
|
parseProps(lines);
|
||||||
}
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
public void update(Date lastUpdate) throws CacheModException {
|
if (getId() == null) {
|
||||||
if (lastUpdate.after(mLastUpdate)) {
|
throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
|
||||||
mLastUpdate = lastUpdate;
|
}
|
||||||
update();
|
if (getVersionCode() < 0) {
|
||||||
|
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||||
|
}
|
||||||
|
if (getTemplateVersion() < MIN_TEMPLATE_VER) {
|
||||||
|
throw new IllegalRepoException("Repo [" + repoName + "] is outdated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean update(Date lastUpdate) throws IllegalRepoException {
|
||||||
|
if (lastUpdate.after(mLastUpdate)) {
|
||||||
|
mLastUpdate = lastUpdate;
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
public ContentValues getContentValues() {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = super.getContentValues();
|
||||||
values.put("id", getId());
|
|
||||||
values.put("name", getName());
|
|
||||||
values.put("version", getVersion());
|
|
||||||
values.put("versionCode", getVersionCode());
|
|
||||||
values.put("author", getAuthor());
|
|
||||||
values.put("description", getDescription());
|
|
||||||
values.put("repo_name", repoName);
|
values.put("repo_name", repoName);
|
||||||
values.put("last_update", mLastUpdate.getTime());
|
values.put("last_update", mLastUpdate.getTime());
|
||||||
values.put("template", getTemplateVersion());
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRepoName() {
|
||||||
|
return repoName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getZipUrl() {
|
public String getZipUrl() {
|
||||||
return String.format(ZIP_URL, repoName);
|
return String.format(ZIP_URL, repoName);
|
||||||
}
|
}
|
||||||
@@ -71,4 +84,10 @@ public class Repo extends BaseModule {
|
|||||||
public Date getLastUpdate() {
|
public Date getLastUpdate() {
|
||||||
return mLastUpdate;
|
return mLastUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class IllegalRepoException extends Exception {
|
||||||
|
IllegalRepoException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class SuLogEntry {
|
||||||
|
|
||||||
|
public int fromUid, toUid, fromPid;
|
||||||
|
public String packageName, appName, command;
|
||||||
|
public boolean action;
|
||||||
|
public Date date;
|
||||||
|
|
||||||
|
public SuLogEntry(Policy policy) {
|
||||||
|
fromUid = policy.uid;
|
||||||
|
packageName = policy.packageName;
|
||||||
|
appName = policy.appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuLogEntry(Cursor c) {
|
||||||
|
fromUid = c.getInt(c.getColumnIndex("from_uid"));
|
||||||
|
fromPid = c.getInt(c.getColumnIndex("from_pid"));
|
||||||
|
toUid = c.getInt(c.getColumnIndex("to_uid"));
|
||||||
|
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||||
|
appName = c.getString(c.getColumnIndex("app_name"));
|
||||||
|
command = c.getString(c.getColumnIndex("command"));
|
||||||
|
action = c.getInt(c.getColumnIndex("action")) != 0;
|
||||||
|
date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentValues getContentValues() {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put("from_uid", fromUid);
|
||||||
|
values.put("package_name", packageName);
|
||||||
|
values.put("app_name", appName);
|
||||||
|
values.put("from_pid", fromPid);
|
||||||
|
values.put("command", command);
|
||||||
|
values.put("to_uid", toUid);
|
||||||
|
values.put("action", action ? 1 : 0);
|
||||||
|
values.put("time", date.getTime());
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDateString() {
|
||||||
|
return DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimeString() {
|
||||||
|
return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
|
import org.kamranzafar.jtar.TarHeader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class TarEntry extends org.kamranzafar.jtar.TarEntry {
|
||||||
|
|
||||||
|
public TarEntry(File file, String entryName) {
|
||||||
|
super(file, entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Workaround missing java.nio.file.attribute.PosixFilePermission
|
||||||
|
* Simply just assign a default permission to the file
|
||||||
|
* */
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extractTarHeader(String entryName) {
|
||||||
|
int permissions = file.isDirectory() ? 000755 : 000644;
|
||||||
|
header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions);
|
||||||
|
header.userName = new StringBuffer("");
|
||||||
|
header.groupName = header.userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rewrite the header to GNU format
|
||||||
|
* */
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeEntryHeader(byte[] outbuf) {
|
||||||
|
super.writeEntryHeader(outbuf);
|
||||||
|
|
||||||
|
System.arraycopy("ustar \0".getBytes(), 0, outbuf, 257, TarHeader.USTAR_MAGICLEN);
|
||||||
|
getOctalBytes(header.mode, outbuf, 100, TarHeader.MODELEN);
|
||||||
|
getOctalBytes(header.userId, outbuf, 108, TarHeader.UIDLEN);
|
||||||
|
getOctalBytes(header.groupId, outbuf, 116, TarHeader.GIDLEN);
|
||||||
|
getOctalBytes(header.size, outbuf, 124, TarHeader.SIZELEN);
|
||||||
|
getOctalBytes(header.modTime, outbuf, 136, TarHeader.MODTIMELEN);
|
||||||
|
Arrays.fill(outbuf, 148, 148 + TarHeader.CHKSUMLEN, (byte) ' ');
|
||||||
|
Arrays.fill(outbuf, 329, 329 + TarHeader.USTAR_DEVLEN, (byte) '\0');
|
||||||
|
Arrays.fill(outbuf, 337, 337 + TarHeader.USTAR_DEVLEN, (byte) '\0');
|
||||||
|
|
||||||
|
// Recalculate checksum
|
||||||
|
getOctalBytes(computeCheckSum(outbuf), outbuf, 148, TarHeader.CHKSUMLEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Proper octal to ASCII conversion
|
||||||
|
* */
|
||||||
|
|
||||||
|
private void getOctalBytes(long value, byte[] buf, int offset, int length) {
|
||||||
|
int idx = length - 1;
|
||||||
|
|
||||||
|
buf[offset + idx] = 0;
|
||||||
|
--idx;
|
||||||
|
|
||||||
|
for (long val = value; idx >= 0; --idx) {
|
||||||
|
buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
|
||||||
|
val = val >> 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
package com.topjohnwu.magisk.container;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
@@ -5,20 +5,29 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.module.Repo;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
import com.topjohnwu.magisk.container.Repo;
|
||||||
import com.topjohnwu.magisk.utils.ValueSortedMap;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final int DATABASE_VER = 2;
|
private static final int DATABASE_VER = 2;
|
||||||
private static final String TABLE_NAME = "repos";
|
private static final String TABLE_NAME = "repos";
|
||||||
private static final int MIN_TEMPLATE_VER = 3;
|
|
||||||
|
private SQLiteDatabase mDb;
|
||||||
|
private MagiskManager mm;
|
||||||
|
|
||||||
public RepoDatabaseHelper(Context context) {
|
public RepoDatabaseHelper(Context context) {
|
||||||
super(context, "repo.db", null, DATABASE_VER);
|
super(context, "repo.db", null, DATABASE_VER);
|
||||||
|
mDb = getWritableDatabase();
|
||||||
|
mm = Utils.getMagiskManager(context);
|
||||||
|
|
||||||
|
// Clear bad repos
|
||||||
|
mDb.delete(TABLE_NAME, "template<?",
|
||||||
|
new String[] { String.valueOf(Repo.MIN_TEMPLATE_VER) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,52 +51,52 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRepoMap(ValueSortedMap<String, Repo> map) {
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
Collection<Repo> list = map.values();
|
|
||||||
for (Repo repo : list) {
|
|
||||||
Logger.dev("Add to DB: " + repo.getId());
|
|
||||||
db.replace(TABLE_NAME, null, repo.getContentValues());
|
|
||||||
}
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearRepo() {
|
public void clearRepo() {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
mDb.delete(TABLE_NAME, null, null);
|
||||||
db.delete(TABLE_NAME, null, null);
|
|
||||||
db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRepo(ValueSortedMap<String, Repo> map) {
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
public void removeRepo(String id) {
|
||||||
Collection<Repo> list = map.values();
|
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||||
for (Repo repo : list) {
|
}
|
||||||
Logger.dev("Remove from DB: " + repo.getId());
|
|
||||||
db.delete(TABLE_NAME, "id=?", new String[] { repo.getId() });
|
public void removeRepo(Repo repo) {
|
||||||
|
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeRepo(List<String> list) {
|
||||||
|
for (String id : list) {
|
||||||
|
if (id == null) continue;
|
||||||
|
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||||
}
|
}
|
||||||
db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueSortedMap<String, Repo> getRepoMap() {
|
public void addRepo(Repo repo) {
|
||||||
return getRepoMap(true);
|
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueSortedMap<String, Repo> getRepoMap(boolean filtered) {
|
public Repo getRepo(String id) {
|
||||||
ValueSortedMap<String, Repo> ret = new ValueSortedMap<>();
|
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
if (c.moveToNext()) {
|
||||||
Repo repo;
|
return new Repo(c);
|
||||||
try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, null)) {
|
}
|
||||||
while (c.moveToNext()) {
|
}
|
||||||
repo = new Repo(c);
|
return null;
|
||||||
if (repo.getTemplateVersion() < MIN_TEMPLATE_VER && filtered) {
|
}
|
||||||
Logger.dev("Outdated repo: " + repo.getId());
|
|
||||||
} else {
|
public Cursor getRepoCursor() {
|
||||||
// Logger.dev("Load from DB: " + repo.getId());
|
return mDb.query(TABLE_NAME, null, "template<=?",
|
||||||
ret.put(repo.getId(), repo);
|
new String[] { String.valueOf(mm.magiskVersionCode) },
|
||||||
}
|
null, null, "name COLLATE NOCASE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRepoIDList() {
|
||||||
|
LinkedList<String> ret = new LinkedList<>();
|
||||||
|
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
ret.add(c.getString(c.getColumnIndex("id")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.close();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,60 @@
|
|||||||
package com.topjohnwu.magisk.database;
|
package com.topjohnwu.magisk.database;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.DatabaseUtils;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.superuser.Policy;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SuDatabaseHelper extends SQLiteOpenHelper {
|
public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final int DATABASE_VER = 1;
|
public static final String ROOT_ACCESS = "root_access";
|
||||||
private static final String TABLE_NAME = "policies";
|
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||||
|
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||||
|
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||||
|
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||||
|
|
||||||
|
public static final String MULTIUSER_MODE = "multiuser_mode";
|
||||||
|
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||||
|
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||||
|
public static final int MULTIUSER_MODE_USER = 2;
|
||||||
|
|
||||||
|
public static final String MNT_NS = "mnt_ns";
|
||||||
|
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||||
|
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||||
|
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||||
|
|
||||||
|
public static final String DB_NAME = "su.db";
|
||||||
|
private static final int DATABASE_VER = 3;
|
||||||
|
private static final String POLICY_TABLE = "policies";
|
||||||
|
private static final String LOG_TABLE = "logs";
|
||||||
|
private static final String SETTINGS_TABLE = "settings";
|
||||||
|
|
||||||
|
private MagiskManager mm;
|
||||||
|
private PackageManager pm;
|
||||||
|
private SQLiteDatabase mDb;
|
||||||
|
|
||||||
public SuDatabaseHelper(Context context) {
|
public SuDatabaseHelper(Context context) {
|
||||||
super(context, "su.db", null, DATABASE_VER);
|
super(context, DB_NAME, null, DATABASE_VER);
|
||||||
|
mm = Utils.getMagiskManager(context);
|
||||||
|
pm = context.getPackageManager();
|
||||||
|
mDb = getWritableDatabase();
|
||||||
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -28,58 +65,201 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
if (oldVersion == 0) {
|
if (oldVersion == 0) {
|
||||||
db.execSQL(
|
createTables(db);
|
||||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
oldVersion = 2;
|
||||||
"(uid INT, package_name TEXT, app_name TEXT, policy INT, " +
|
}
|
||||||
"until INT, logging INT, notification INT, " +
|
if (oldVersion == 1) {
|
||||||
"PRIMARY KEY(uid))");
|
// We're dropping column app_name, rename and re-construct table
|
||||||
|
db.execSQL("ALTER TABLE " + POLICY_TABLE + " RENAME TO " + POLICY_TABLE + "_old");
|
||||||
|
|
||||||
|
// Create the new tables
|
||||||
|
createTables(db);
|
||||||
|
|
||||||
|
// Migrate old data to new tables
|
||||||
|
db.execSQL(
|
||||||
|
"INSERT INTO " + POLICY_TABLE + " SELECT " +
|
||||||
|
"uid, package_name, policy, until, logging, notification " +
|
||||||
|
"FROM " + POLICY_TABLE + "_old");
|
||||||
|
db.execSQL("DROP TABLE " + POLICY_TABLE + "_old");
|
||||||
|
|
||||||
|
File oldDB = mm.getDatabasePath("sulog.db");
|
||||||
|
if (oldDB.exists()) {
|
||||||
|
migrateLegacyLogList(oldDB, db);
|
||||||
|
mm.deleteDatabase("sulog.db");
|
||||||
|
}
|
||||||
|
++oldVersion;
|
||||||
|
}
|
||||||
|
if (oldVersion == 2) {
|
||||||
|
db.execSQL("UPDATE " + LOG_TABLE + " SET time=time*1000");
|
||||||
|
++oldVersion;
|
||||||
}
|
}
|
||||||
// Currently new database, no upgrading
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deletePolicy(int uid) {
|
private void createTables(SQLiteDatabase db) {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
// Policies
|
||||||
db.delete(TABLE_NAME, "uid=?", new String[] { String.valueOf(uid) });
|
db.execSQL(
|
||||||
db.close();
|
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
|
||||||
return getPolicy(uid) == null;
|
"(uid INT, package_name TEXT, policy INT, " +
|
||||||
|
"until INT, logging INT, notification INT, " +
|
||||||
|
"PRIMARY KEY(uid))");
|
||||||
|
|
||||||
|
// Logs
|
||||||
|
db.execSQL(
|
||||||
|
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
|
||||||
|
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
||||||
|
"to_uid INT, action INT, time INT, command TEXT)");
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
db.execSQL(
|
||||||
|
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
|
||||||
|
"(key TEXT, value INT, PRIMARY KEY(key))");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
// Clear outdated policies
|
||||||
|
mDb.delete(POLICY_TABLE, "until > 0 AND until < ?",
|
||||||
|
new String[] { String.valueOf(System.currentTimeMillis() / 1000) });
|
||||||
|
// Clear outdated logs
|
||||||
|
mDb.delete(LOG_TABLE, "time < ?", new String[] { String.valueOf(
|
||||||
|
System.currentTimeMillis() - mm.suLogTimeout * 86400000) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deletePolicy(Policy policy) {
|
||||||
|
deletePolicy(policy.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deletePolicy(String pkg) {
|
||||||
|
mDb.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deletePolicy(int uid) {
|
||||||
|
mDb.delete(POLICY_TABLE, "uid=?", new String[]{String.valueOf(uid)});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Policy getPolicy(int uid) {
|
public Policy getPolicy(int uid) {
|
||||||
Policy policy = null;
|
Policy policy = null;
|
||||||
SQLiteDatabase db = getReadableDatabase();
|
try (Cursor c = mDb.query(POLICY_TABLE, null, "uid=?", new String[] { String.valueOf(uid) }, null, null, null)) {
|
||||||
try (Cursor c = db.query(TABLE_NAME, null, "uid=?", new String[] { String.valueOf(uid) }, null, null, null)) {
|
|
||||||
if (c.moveToNext()) {
|
if (c.moveToNext()) {
|
||||||
policy = new Policy(c);
|
policy = new Policy(c, pm);
|
||||||
}
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
deletePolicy(uid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Policy getPolicy(String pkg) {
|
||||||
|
Policy policy = null;
|
||||||
|
try (Cursor c = mDb.query(POLICY_TABLE, null, "package_name=?", new String[] { pkg }, null, null, null)) {
|
||||||
|
if (c.moveToNext()) {
|
||||||
|
policy = new Policy(c, pm);
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
deletePolicy(pkg);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
db.close();
|
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPolicy(Policy policy) {
|
public void addPolicy(Policy policy) {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
mDb.replace(POLICY_TABLE, null, policy.getContentValues());
|
||||||
db.replace(TABLE_NAME, null, policy.getContentValues());
|
}
|
||||||
db.close();
|
|
||||||
|
public void updatePolicy(Policy policy) {
|
||||||
|
mDb.update(POLICY_TABLE, policy.getContentValues(), "package_name=?",
|
||||||
|
new String[] { policy.packageName });
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Policy> getPolicyList(PackageManager pm) {
|
public List<Policy> getPolicyList(PackageManager pm) {
|
||||||
List<Policy> ret = new ArrayList<>();
|
try (Cursor c = mDb.query(POLICY_TABLE, null, null, null, null, null, null)) {
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
List<Policy> ret = new ArrayList<>(c.getCount());
|
||||||
Policy policy;
|
|
||||||
// Clear outdated policies
|
|
||||||
db.delete(TABLE_NAME, "until > 0 and until < ?", new String[] { String.valueOf(System.currentTimeMillis()) });
|
|
||||||
try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, "app_name ASC")) {
|
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
policy = new Policy(c);
|
try {
|
||||||
// Package is uninstalled
|
Policy policy = new Policy(c, pm);
|
||||||
if (pm.getPackagesForUid(policy.uid) == null) {
|
// The application changed UID for some reason, check user config
|
||||||
deletePolicy(policy.uid);
|
if (policy.info.uid != policy.uid) {
|
||||||
} else {
|
if (mm.suReauth) {
|
||||||
|
// Reauth required, remove from DB
|
||||||
|
deletePolicy(policy);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// No reauth, update to use the new UID
|
||||||
|
policy.uid = policy.info.uid;
|
||||||
|
updatePolicy(policy);
|
||||||
|
}
|
||||||
|
}
|
||||||
ret.add(policy);
|
ret.add(policy);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
// The app no longer exist, remove from DB
|
||||||
|
deletePolicy(c.getInt(c.getColumnIndex("uid")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Collections.sort(ret);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
db.close();
|
}
|
||||||
return ret;
|
|
||||||
|
public List<List<Integer>> getLogStructure() {
|
||||||
|
try (Cursor c = mDb.query(LOG_TABLE, new String[] { "time" }, null, null, null, null, "time DESC")) {
|
||||||
|
List<List<Integer>> ret = new ArrayList<>();
|
||||||
|
List<Integer> list = null;
|
||||||
|
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);
|
||||||
|
if (!TextUtils.equals(dateString, newString)) {
|
||||||
|
dateString = newString;
|
||||||
|
list = new ArrayList<>();
|
||||||
|
ret.add(list);
|
||||||
|
}
|
||||||
|
list.add(c.getPosition());
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor getLogCursor() {
|
||||||
|
return getLogCursor(mDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor getLogCursor(SQLiteDatabase db) {
|
||||||
|
return db.query(LOG_TABLE, null, null, null, null, null, "time DESC");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void migrateLegacyLogList(File oldDB, SQLiteDatabase newDB) {
|
||||||
|
try (SQLiteDatabase oldDb = SQLiteDatabase.openDatabase(oldDB.getPath(), null, SQLiteDatabase.OPEN_READWRITE);
|
||||||
|
Cursor c = getLogCursor(oldDb)) {
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
DatabaseUtils.cursorRowToContentValues(c, values);
|
||||||
|
newDB.insert(LOG_TABLE, null, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addLog(SuLogEntry log) {
|
||||||
|
mDb.insert(LOG_TABLE, null, log.getContentValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearLogs() {
|
||||||
|
mDb.delete(LOG_TABLE, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSettings(String key, int value) {
|
||||||
|
ContentValues data = new ContentValues();
|
||||||
|
data.put("key", key);
|
||||||
|
data.put("value", value);
|
||||||
|
mDb.replace(SETTINGS_TABLE, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSettings(String key, int defaultValue) {
|
||||||
|
int value = defaultValue;
|
||||||
|
try (Cursor c = mDb.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
|
||||||
|
if (c.moveToNext()) {
|
||||||
|
value = c.getInt(c.getColumnIndex("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.database;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.superuser.SuLogEntry;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SuLogDatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
private static final int DATABASE_VER = 1;
|
|
||||||
private static final String TABLE_NAME = "logs";
|
|
||||||
|
|
||||||
private MagiskManager magiskManager;
|
|
||||||
|
|
||||||
public SuLogDatabaseHelper(Context context) {
|
|
||||||
super(context, "sulog.db", null, DATABASE_VER);
|
|
||||||
magiskManager = (MagiskManager) context.getApplicationContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
onUpgrade(db, 0, DATABASE_VER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
if (oldVersion == 0) {
|
|
||||||
db.execSQL(
|
|
||||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
"from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
|
|
||||||
"to_uid INT, action INT, time INT, command TEXT)");
|
|
||||||
}
|
|
||||||
// Currently new database, no upgrading
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLog(SuLogEntry log) {
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
db.insert(TABLE_NAME, null, log.getContentValues());
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLogs() {
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
db.delete(TABLE_NAME, null, null);
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SuLogEntry> getLogList() {
|
|
||||||
return getLogList(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SuLogEntry> getLogList(int uid) {
|
|
||||||
return getLogList("uid=" + uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SuLogEntry> getLogList(String selection) {
|
|
||||||
List<SuLogEntry> ret = new ArrayList<>();
|
|
||||||
SQLiteDatabase db = getWritableDatabase();
|
|
||||||
// Clear outdated logs
|
|
||||||
db.delete(TABLE_NAME, "time < ?", new String[] { String.valueOf(
|
|
||||||
System.currentTimeMillis() / 1000 - magiskManager.suLogTimeout * 86400) });
|
|
||||||
try (Cursor c = db.query(TABLE_NAME, null, selection, null, null, null, "time DESC")) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
ret.add(new SuLogEntry(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.close();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.module;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.utils.Logger;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
public class Module extends BaseModule {
|
|
||||||
|
|
||||||
private String mRemoveFile, mDisableFile, mUpdateFile;
|
|
||||||
private boolean mEnable, mRemove, mUpdated;
|
|
||||||
|
|
||||||
public Module(String path) throws CacheModException {
|
|
||||||
|
|
||||||
parseProps(Utils.readFile(path + "/module.prop"));
|
|
||||||
|
|
||||||
mRemoveFile = path + "/remove";
|
|
||||||
mDisableFile = path + "/disable";
|
|
||||||
mUpdateFile = path + "/update";
|
|
||||||
|
|
||||||
if (getId() == null) {
|
|
||||||
int sep = path.lastIndexOf('/');
|
|
||||||
setId(path.substring(sep + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getName() == null) {
|
|
||||||
setName(getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.dev("Creating Module, id: " + getId());
|
|
||||||
|
|
||||||
mEnable = !Utils.itemExist(mDisableFile);
|
|
||||||
mRemove = Utils.itemExist(mRemoveFile);
|
|
||||||
mUpdated = Utils.itemExist(mUpdateFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createDisableFile() {
|
|
||||||
mEnable = !Utils.createFile(mDisableFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeDisableFile() {
|
|
||||||
mEnable = Utils.removeItem(mDisableFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return mEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createRemoveFile() {
|
|
||||||
mRemove = Utils.createFile(mRemoveFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteRemoveFile() {
|
|
||||||
mRemove = !Utils.removeItem(mRemoveFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean willBeRemoved() {
|
|
||||||
return mRemove;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdated() {
|
|
||||||
return mUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,26 @@ package com.topjohnwu.magisk.receivers;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.services.BootupIntentService;
|
import com.topjohnwu.magisk.services.OnBootIntentService;
|
||||||
|
|
||||||
public class BootReceiver extends BroadcastReceiver {
|
public class BootReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private void startIntentService(Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(new Intent(context, OnBootIntentService.class));
|
||||||
|
} else {
|
||||||
|
context.startService(new Intent(context, OnBootIntentService.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
context.startService(new Intent(context, BootupIntentService.class));
|
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||||
|
// There is currently no need to start an IntentService onBoot
|
||||||
|
// startIntentService(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.topjohnwu.magisk.receivers;
|
package com.topjohnwu.magisk.receivers;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.DownloadManager;
|
import android.app.DownloadManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -13,7 +12,6 @@ import com.topjohnwu.magisk.R;
|
|||||||
import com.topjohnwu.magisk.utils.Utils;
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
public abstract class DownloadReceiver extends BroadcastReceiver {
|
public abstract class DownloadReceiver extends BroadcastReceiver {
|
||||||
public Activity activity;
|
|
||||||
public String mFilename;
|
public String mFilename;
|
||||||
long downloadID;
|
long downloadID;
|
||||||
|
|
||||||
@@ -21,7 +19,6 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
activity = (Activity) context;
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
|
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
|
||||||
@@ -37,7 +34,7 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
|
|||||||
onDownloadDone(uri);
|
onDownloadDone(uri);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Toast.makeText(context, R.string.download_file_error, Toast.LENGTH_LONG).show();
|
Utils.getMagiskManager(context).toast(R.string.download_file_error, Toast.LENGTH_LONG);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
context.unregisterReceiver(this);
|
context.unregisterReceiver(this);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.topjohnwu.magisk.receivers;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ManagerUpdate extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Utils.dlAndReceive(
|
||||||
|
context,
|
||||||
|
new DownloadReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onDownloadDone(Uri uri) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||||
|
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
Uri content = FileProvider.getUriForFile(context,
|
||||||
|
"com.topjohnwu.magisk.provider", new File(uri.getPath()));
|
||||||
|
install.setData(content);
|
||||||
|
context.startActivity(install);
|
||||||
|
} else {
|
||||||
|
Intent install = new Intent(Intent.ACTION_VIEW);
|
||||||
|
install.setDataAndType(uri, "application/vnd.android.package-archive");
|
||||||
|
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(install);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
intent.getStringExtra(MagiskManager.INTENT_LINK),
|
||||||
|
Utils.getLegalFilename("MagiskManager-v" +
|
||||||
|
intent.getStringExtra(MagiskManager.INTENT_VERSION) + ".apk"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.topjohnwu.magisk.receivers;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
|
public class PackageReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
MagiskManager magiskManager = Utils.getMagiskManager(context);
|
||||||
|
|
||||||
|
String pkg = intent.getData().getEncodedSchemeSpecificPart();
|
||||||
|
Policy policy = magiskManager.suDB.getPolicy(pkg);
|
||||||
|
if (policy == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case Intent.ACTION_PACKAGE_REPLACED:
|
||||||
|
// This will only work pre-O
|
||||||
|
if (magiskManager.suReauth) {
|
||||||
|
magiskManager.suDB.deletePolicy(policy);
|
||||||
|
} else {
|
||||||
|
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
|
||||||
|
// Update the UID if available
|
||||||
|
if (uid > 0) {
|
||||||
|
policy.uid = uid % 100000;
|
||||||
|
}
|
||||||
|
magiskManager.suDB.updatePolicy(policy);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
|
||||||
|
magiskManager.suDB.deletePolicy(policy);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.services;
|
|
||||||
|
|
||||||
import android.app.IntentService;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Shell;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
|
|
||||||
public class BootupIntentService extends IntentService {
|
|
||||||
|
|
||||||
public BootupIntentService() {
|
|
||||||
super("BootupIntentService");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHandleIntent(Intent intent) {
|
|
||||||
MagiskManager magiskManager = Utils.getMagiskManager(this);
|
|
||||||
magiskManager.initSuAccess();
|
|
||||||
magiskManager.updateMagiskInfo();
|
|
||||||
if (magiskManager.prefs.getBoolean("magiskhide", false) &&
|
|
||||||
!magiskManager.disabled && !magiskManager.magiskHideStarted && magiskManager.magiskVersion > 11) {
|
|
||||||
magiskManager.toast(R.string.start_magiskhide, Toast.LENGTH_LONG);
|
|
||||||
Shell.su(true, MagiskManager.MAGISK_HIDE_PATH + "enable",
|
|
||||||
"setprop persist.magisk.hide 1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.topjohnwu.magisk.services;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v7.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
|
||||||
|
public class OnBootIntentService extends IntentService {
|
||||||
|
|
||||||
|
private static final int ONBOOT_NOTIFICATION_ID = 3;
|
||||||
|
|
||||||
|
public OnBootIntentService() {
|
||||||
|
super("OnBootIntentService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
|
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||||
|
.setContentTitle("onBoot")
|
||||||
|
.setContentText("Running onBoot operations...");
|
||||||
|
startForeground(ONBOOT_NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
// Currently nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,24 +4,15 @@ import android.app.job.JobParameters;
|
|||||||
import android.app.job.JobService;
|
import android.app.job.JobService;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
|
||||||
public class UpdateCheckService extends JobService {
|
public class UpdateCheckService extends JobService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onStartJob(JobParameters params) {
|
public boolean onStartJob(JobParameters params) {
|
||||||
new CheckUpdates(this, true){
|
Utils.getMagiskManager(this).getMagiskInfo();
|
||||||
@Override
|
new CheckUpdates(this, true)
|
||||||
protected Void doInBackground(Void... voids) {
|
.setCallBack(() -> jobFinished(params, false)).exec();
|
||||||
magiskManager.updateMagiskInfo();
|
|
||||||
magiskManager.updateNotification = magiskManager.prefs.getBoolean("notification", true);
|
|
||||||
return super.doInBackground(voids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void v) {
|
|
||||||
jobFinished(params, false);
|
|
||||||
super.onPostExecute(v);
|
|
||||||
}
|
|
||||||
}.exec();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public class RequestActivity extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplicationContext().initSuConfigs();
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClass(this, SuRequestActivity.class);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClass(this, SuRequestActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.superuser;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class SuLogEntry implements Parcelable {
|
|
||||||
|
|
||||||
public int fromUid, toUid, fromPid;
|
|
||||||
public String packageName, appName, command;
|
|
||||||
public boolean action;
|
|
||||||
public Date date;
|
|
||||||
|
|
||||||
public SuLogEntry(Policy policy) {
|
|
||||||
fromUid = policy.uid;
|
|
||||||
packageName = policy.packageName;
|
|
||||||
appName = policy.appName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SuLogEntry(Cursor c) {
|
|
||||||
fromUid = c.getInt(c.getColumnIndex("from_uid"));
|
|
||||||
fromPid = c.getInt(c.getColumnIndex("from_pid"));
|
|
||||||
toUid = c.getInt(c.getColumnIndex("to_uid"));
|
|
||||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
|
||||||
appName = c.getString(c.getColumnIndex("app_name"));
|
|
||||||
command = c.getString(c.getColumnIndex("command"));
|
|
||||||
action = c.getInt(c.getColumnIndex("action")) != 0;
|
|
||||||
date = new Date(c.getLong(c.getColumnIndex("time")) * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put("from_uid", fromUid);
|
|
||||||
values.put("package_name", packageName);
|
|
||||||
values.put("app_name", appName);
|
|
||||||
values.put("from_pid", fromPid);
|
|
||||||
values.put("command", command);
|
|
||||||
values.put("to_uid", toUid);
|
|
||||||
values.put("action", action ? 1 : 0);
|
|
||||||
values.put("time", date.getTime() / 1000);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDateString() {
|
|
||||||
return DateFormat.getDateInstance(DateFormat.MEDIUM).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTimeString() {
|
|
||||||
return new SimpleDateFormat("h:mm a", Locale.US).format(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final Creator<SuLogEntry> CREATOR = new Creator<SuLogEntry>() {
|
|
||||||
@Override
|
|
||||||
public SuLogEntry createFromParcel(Parcel in) {
|
|
||||||
return new SuLogEntry(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SuLogEntry[] newArray(int size) {
|
|
||||||
return new SuLogEntry[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected SuLogEntry(Parcel in) {
|
|
||||||
fromUid = in.readInt();
|
|
||||||
toUid = in.readInt();
|
|
||||||
fromPid = in.readInt();
|
|
||||||
packageName = in.readString();
|
|
||||||
appName = in.readString();
|
|
||||||
command = in.readString();
|
|
||||||
action = in.readByte() != 0;
|
|
||||||
date = new Date(in.readLong());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeInt(fromUid);
|
|
||||||
dest.writeInt(toUid);
|
|
||||||
dest.writeInt(fromPid);
|
|
||||||
dest.writeString(packageName);
|
|
||||||
dest.writeString(appName);
|
|
||||||
dest.writeString(command);
|
|
||||||
dest.writeByte((byte) (action ? 1 : 0));
|
|
||||||
dest.writeLong(date.getTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,25 +3,29 @@ package com.topjohnwu.magisk.superuser;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.database.SuLogDatabaseHelper;
|
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class SuReceiver extends BroadcastReceiver {
|
public class SuReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
private static final int NO_NOTIFICATION = 0;
|
public static final int NO_NOTIFICATION = 0;
|
||||||
private static final int TOAST = 1;
|
public static final int TOAST = 1;
|
||||||
|
|
||||||
|
private static final int NOTIFY_NORMAL_LOG = 0;
|
||||||
|
private static final int NOTIFY_USER_TOASTS = 1;
|
||||||
|
private static final int NOTIFY_USER_TO_OWNER = 2;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
int fromUid, toUid, pid;
|
int fromUid, toUid, pid, mode;
|
||||||
String command, action;
|
String command, action;
|
||||||
Policy policy;
|
Policy policy;
|
||||||
|
|
||||||
@@ -29,6 +33,14 @@ public class SuReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
if (intent == null) return;
|
if (intent == null) return;
|
||||||
|
|
||||||
|
mode = intent.getIntExtra("mode", -1);
|
||||||
|
if (mode < 0) return;
|
||||||
|
|
||||||
|
if (mode == NOTIFY_USER_TO_OWNER) {
|
||||||
|
magiskManager.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fromUid = intent.getIntExtra("from.uid", -1);
|
fromUid = intent.getIntExtra("from.uid", -1);
|
||||||
if (fromUid < 0) return;
|
if (fromUid < 0) return;
|
||||||
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
|
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
|
||||||
@@ -36,18 +48,16 @@ public class SuReceiver extends BroadcastReceiver {
|
|||||||
action = intent.getStringExtra("action");
|
action = intent.getStringExtra("action");
|
||||||
if (action == null) return;
|
if (action == null) return;
|
||||||
|
|
||||||
SuDatabaseHelper suDbHelper = new SuDatabaseHelper(context);
|
policy = magiskManager.suDB.getPolicy(fromUid);
|
||||||
policy = suDbHelper.getPolicy(fromUid);
|
|
||||||
if (policy == null) {
|
if (policy == null) {
|
||||||
try {
|
try {
|
||||||
policy = new Policy(fromUid, context.getPackageManager());
|
policy = new Policy(fromUid, context.getPackageManager());
|
||||||
} catch (Throwable throwable) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
magiskManager.initSuConfigs();
|
|
||||||
|
|
||||||
SuLogEntry log = new SuLogEntry(policy);
|
SuLogEntry log = new SuLogEntry(policy);
|
||||||
|
|
||||||
String message;
|
String message;
|
||||||
@@ -64,10 +74,11 @@ public class SuReceiver extends BroadcastReceiver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policy.notification && magiskManager.suNotificationType == TOAST)
|
if (policy.notification && magiskManager.suNotificationType == TOAST) {
|
||||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
magiskManager.toast(message, Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
if (policy.logging) {
|
if (mode == NOTIFY_NORMAL_LOG && policy.logging) {
|
||||||
toUid = intent.getIntExtra("to.uid", -1);
|
toUid = intent.getIntExtra("to.uid", -1);
|
||||||
if (toUid < 0) return;
|
if (toUid < 0) return;
|
||||||
pid = intent.getIntExtra("pid", -1);
|
pid = intent.getIntExtra("pid", -1);
|
||||||
@@ -78,8 +89,7 @@ public class SuReceiver extends BroadcastReceiver {
|
|||||||
log.fromPid = pid;
|
log.fromPid = pid;
|
||||||
log.command = command;
|
log.command = command;
|
||||||
log.date = new Date();
|
log.date = new Date();
|
||||||
SuLogDatabaseHelper logDbHelper = new SuLogDatabaseHelper(context);
|
magiskManager.suDB.addLog(log);
|
||||||
logDbHelper.addLog(log);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import com.topjohnwu.magisk.MagiskManager;
|
|||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||||
import com.topjohnwu.magisk.components.Activity;
|
import com.topjohnwu.magisk.components.Activity;
|
||||||
import com.topjohnwu.magisk.database.SuDatabaseHelper;
|
import com.topjohnwu.magisk.container.Policy;
|
||||||
import com.topjohnwu.magisk.utils.CallbackEvent;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -30,16 +29,13 @@ import java.io.IOException;
|
|||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class SuRequestActivity extends Activity implements CallbackEvent.Listener<Policy> {
|
public class SuRequestActivity extends Activity {
|
||||||
|
|
||||||
|
public static final int PROMPT = 0;
|
||||||
|
public static final int AUTO_DENY = 1;
|
||||||
|
public static final int AUTO_ALLOW = 2;
|
||||||
|
|
||||||
private static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
private static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||||
private static final int SU_PROTOCOL_PARAM_MAX = 20;
|
|
||||||
private static final int SU_PROTOCOL_NAME_MAX = 20;
|
|
||||||
private static final int SU_PROTOCOL_VALUE_MAX = 256;
|
|
||||||
|
|
||||||
private static final int PROMPT = 0;
|
|
||||||
private static final int AUTO_DENY = 1;
|
|
||||||
private static final int AUTO_ALLOW = 2;
|
|
||||||
|
|
||||||
@BindView(R.id.su_popup) LinearLayout suPopup;
|
@BindView(R.id.su_popup) LinearLayout suPopup;
|
||||||
@BindView(R.id.timeout) Spinner timeout;
|
@BindView(R.id.timeout) Spinner timeout;
|
||||||
@@ -54,11 +50,9 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
private MagiskManager magiskManager;
|
private MagiskManager magiskManager;
|
||||||
|
|
||||||
private int uid;
|
private boolean hasTimeout;
|
||||||
private Policy policy;
|
private Policy policy;
|
||||||
private CountDownTimer timer;
|
private CountDownTimer timer;
|
||||||
private CallbackEvent.Listener<Policy> self;
|
|
||||||
private CallbackEvent<Policy> event = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -66,19 +60,16 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
pm = getPackageManager();
|
pm = getPackageManager();
|
||||||
magiskManager = getApplicationContext();
|
magiskManager = getMagiskManager();
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
socketPath = intent.getStringExtra("socket");
|
socketPath = intent.getStringExtra("socket");
|
||||||
self = this;
|
hasTimeout = intent.getBooleanExtra("timeout", true);
|
||||||
|
|
||||||
new FileObserver(socketPath) {
|
new FileObserver(socketPath) {
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int fileEvent, String path) {
|
public void onEvent(int fileEvent, String path) {
|
||||||
if (fileEvent == FileObserver.DELETE_SELF) {
|
if (fileEvent == FileObserver.DELETE_SELF) {
|
||||||
if (event != null) {
|
|
||||||
event.trigger();
|
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,8 +78,13 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
new SocketManager(this).exec();
|
new SocketManager(this).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showRequest() {
|
private boolean cancelTimeout() {
|
||||||
|
timer.cancel();
|
||||||
|
deny_btn.setText(getString(R.string.deny));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRequest() {
|
||||||
switch (magiskManager.suResponseType) {
|
switch (magiskManager.suResponseType) {
|
||||||
case AUTO_DENY:
|
case AUTO_DENY:
|
||||||
handleAction(Policy.DENY, 0);
|
handleAction(Policy.DENY, 0);
|
||||||
@@ -100,10 +96,16 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not interactive, response directly
|
||||||
|
if (policy.policy != Policy.INTERACTIVE) {
|
||||||
|
handleAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_request);
|
setContentView(R.layout.activity_request);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
appIcon.setImageDrawable(policy.info.applicationInfo.loadIcon(pm));
|
appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||||
appNameView.setText(policy.appName);
|
appNameView.setText(policy.appName);
|
||||||
packageNameView.setText(policy.packageName);
|
packageNameView.setText(policy.packageName);
|
||||||
|
|
||||||
@@ -120,40 +122,49 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish() {
|
||||||
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
|
||||||
event.trigger();
|
handleAction(Policy.DENY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
grant_btn.setOnClickListener(v -> handleAction(Policy.ALLOW));
|
grant_btn.setOnClickListener(v -> {
|
||||||
deny_btn.setOnClickListener(v -> handleAction(Policy.DENY));
|
handleAction(Policy.ALLOW);
|
||||||
suPopup.setOnClickListener((v) -> {
|
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
deny_btn.setText(getString(R.string.deny));
|
|
||||||
});
|
});
|
||||||
timeout.setOnTouchListener((v, event) -> {
|
deny_btn.setOnClickListener(v -> {
|
||||||
|
handleAction(Policy.DENY);
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
deny_btn.setText(getString(R.string.deny));
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
suPopup.setOnClickListener(v -> cancelTimeout());
|
||||||
|
timeout.setOnTouchListener((v, event) -> cancelTimeout());
|
||||||
|
|
||||||
timer.start();
|
if (hasTimeout) {
|
||||||
|
timer.start();
|
||||||
|
} else {
|
||||||
|
cancelTimeout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
event.trigger();
|
if (policy != null) {
|
||||||
|
handleAction(Policy.DENY);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void handleAction() {
|
||||||
public void onTrigger(CallbackEvent<Policy> event) {
|
String response;
|
||||||
Policy policy = event.getResult();
|
if (policy.policy == Policy.ALLOW) {
|
||||||
String response = "socket:DENY";
|
|
||||||
if (policy != null &&policy.policy == Policy.ALLOW ) {
|
|
||||||
response = "socket:ALLOW";
|
response = "socket:ALLOW";
|
||||||
|
} else {
|
||||||
|
response = "socket:DENY";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
socket.getOutputStream().write((response).getBytes());
|
socket.getOutputStream().write((response).getBytes());
|
||||||
} catch (Exception ignored) {}
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,16 +174,16 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
|
|
||||||
void handleAction(int action, int time) {
|
void handleAction(int action, int time) {
|
||||||
policy.policy = action;
|
policy.policy = action;
|
||||||
event.trigger(policy);
|
|
||||||
if (time >= 0) {
|
if (time >= 0) {
|
||||||
policy.until = time == 0 ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
|
||||||
new SuDatabaseHelper(this).addPolicy(policy);
|
magiskManager.suDB.addPolicy(policy);
|
||||||
}
|
}
|
||||||
|
handleAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
|
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
public SocketManager(Activity context) {
|
SocketManager(Activity context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,40 +194,33 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
|
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||||
|
|
||||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||||
|
|
||||||
ContentValues payload = new ContentValues();
|
ContentValues payload = new ContentValues();
|
||||||
|
|
||||||
for (int i = 0; i < SU_PROTOCOL_PARAM_MAX; i++) {
|
while (true) {
|
||||||
|
|
||||||
int nameLen = is.readInt();
|
int nameLen = is.readInt();
|
||||||
if (nameLen > SU_PROTOCOL_NAME_MAX)
|
|
||||||
throw new IllegalArgumentException("name length too long: " + nameLen);
|
|
||||||
|
|
||||||
byte[] nameBytes = new byte[nameLen];
|
byte[] nameBytes = new byte[nameLen];
|
||||||
is.readFully(nameBytes);
|
is.readFully(nameBytes);
|
||||||
|
|
||||||
String name = new String(nameBytes);
|
String name = new String(nameBytes);
|
||||||
|
|
||||||
if (TextUtils.equals(name, "eof"))
|
if (TextUtils.equals(name, "eof"))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
int dataLen = is.readInt();
|
int dataLen = is.readInt();
|
||||||
if (dataLen > SU_PROTOCOL_VALUE_MAX)
|
|
||||||
throw new IllegalArgumentException(name + " data length too long: " + dataLen);
|
|
||||||
|
|
||||||
byte[] dataBytes = new byte[dataLen];
|
byte[] dataBytes = new byte[dataLen];
|
||||||
is.readFully(dataBytes);
|
is.readFully(dataBytes);
|
||||||
|
|
||||||
String data = new String(dataBytes);
|
String data = new String(dataBytes);
|
||||||
|
|
||||||
payload.put(name, data);
|
payload.put(name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.getAsInteger("uid") == null)
|
if (payload.getAsInteger("uid") == null) {
|
||||||
return false;
|
return false;
|
||||||
uid = payload.getAsInteger("uid");
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
int uid = payload.getAsInteger("uid");
|
||||||
|
policy = magiskManager.suDB.getPolicy(uid);
|
||||||
|
if (policy == null) {
|
||||||
|
policy = new Policy(uid, pm);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -225,35 +229,10 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
if (!result) {
|
if (result) {
|
||||||
|
showRequest();
|
||||||
|
} else {
|
||||||
finish();
|
finish();
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean showRequest = false;
|
|
||||||
event = magiskManager.uidSuRequest.get(uid);
|
|
||||||
if (event == null) {
|
|
||||||
showRequest = true;
|
|
||||||
event = new CallbackEvent<Policy>() {
|
|
||||||
@Override
|
|
||||||
public void trigger(Policy result) {
|
|
||||||
super.trigger(result);
|
|
||||||
unRegister();
|
|
||||||
magiskManager.uidSuRequest.remove(uid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
magiskManager.uidSuRequest.put(uid, event);
|
|
||||||
}
|
|
||||||
event.register(self);
|
|
||||||
try {
|
|
||||||
if (showRequest) {
|
|
||||||
policy = new Policy(uid, pm);
|
|
||||||
showRequest();
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
event.trigger();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
public class ByteArrayInOutStream extends ByteArrayOutputStream {
|
|
||||||
public ByteArrayInputStream getInputStream() {
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(buf, 0, count);
|
|
||||||
count = 0;
|
|
||||||
buf = new byte[32];
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuffer(byte[] buffer) {
|
|
||||||
buf = buffer;
|
|
||||||
count = buffer.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class CallbackEvent<Result> {
|
|
||||||
|
|
||||||
public boolean isTriggered = false;
|
|
||||||
private Result result;
|
|
||||||
private Set<Listener<Result>> listeners;
|
|
||||||
|
|
||||||
public void register(Listener<Result> l) {
|
|
||||||
if (listeners == null) {
|
|
||||||
listeners = new HashSet<>();
|
|
||||||
}
|
|
||||||
listeners.add(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unRegister() {
|
|
||||||
listeners = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unRegister(Listener<Result> l) {
|
|
||||||
if (listeners != null) {
|
|
||||||
listeners.remove(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void trigger() {
|
|
||||||
trigger(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void trigger(Result r) {
|
|
||||||
result = r;
|
|
||||||
isTriggered = true;
|
|
||||||
if (listeners != null) {
|
|
||||||
for (Listener<Result> listener : listeners) {
|
|
||||||
listener.onTrigger(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener<R> {
|
|
||||||
void onTrigger(CallbackEvent<R> event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,37 +4,36 @@ import android.util.Log;
|
|||||||
|
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
public static final String TAG = "Magisk";
|
public static final String DEBUG_TAG = "MagiskManager";
|
||||||
|
private static final boolean SHELL_LOGGING = false;
|
||||||
|
|
||||||
public static void debug(String msg) {
|
public static void debug(String line) {
|
||||||
Log.d(TAG, "DEBUG: " + msg);
|
Log.d(DEBUG_TAG, "DEBUG: " + line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(String msg) {
|
public static void debug(String fmt, Object... args) {
|
||||||
Log.e(TAG, "MANAGERERROR: " + msg);
|
debug(String.format(Locale.US, fmt, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dev(String msg, Object... args) {
|
public static void error(String line) {
|
||||||
if (MagiskManager.devLogging) {
|
Log.e(DEBUG_TAG, "ERROR: " + line);
|
||||||
if (args.length == 1 && args[0] instanceof Throwable) {
|
}
|
||||||
Log.d(TAG, "MANAGER: " + msg, (Throwable) args[0]);
|
|
||||||
} else {
|
public static void error(String fmt, Object... args) {
|
||||||
Log.d(TAG, "MANAGER: " + String.format(msg, args));
|
error(String.format(Locale.US, fmt, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void shell(String line) {
|
||||||
|
if (SHELL_LOGGING) {
|
||||||
|
Log.d(DEBUG_TAG, "SHELL: " + line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dev(String msg) {
|
public static void shell(String fmt, Object... args) {
|
||||||
if (MagiskManager.devLogging) {
|
shell(String.format(Locale.US, fmt, args));
|
||||||
Log.d(TAG, "MANAGER: " + msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void shell(boolean root, String msg) {
|
|
||||||
if (MagiskManager.shellLogging) {
|
|
||||||
Log.d(TAG, root ? "MANAGERSU" : "MANAGERSH" + msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
|
||||||
import com.google.android.gms.common.api.GoogleApiClient;
|
|
||||||
import com.google.android.gms.common.api.Status;
|
|
||||||
import com.google.android.gms.safetynet.SafetyNet;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
public abstract class SafetyNetHelper
|
|
||||||
implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
|
|
||||||
|
|
||||||
private GoogleApiClient mGoogleApiClient;
|
|
||||||
|
|
||||||
public SafetyNetHelper(Context context) {
|
|
||||||
mGoogleApiClient = new GoogleApiClient.Builder(context)
|
|
||||||
.addApi(SafetyNet.API)
|
|
||||||
.addConnectionCallbacks(this)
|
|
||||||
.addOnConnectionFailedListener(this)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionFailed(@NonNull ConnectionResult result) {
|
|
||||||
Logger.dev("SN: Google API fail");
|
|
||||||
handleResults(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnected(@Nullable Bundle bundle) {
|
|
||||||
Logger.dev("SN: Google API Connected");
|
|
||||||
safetyNetCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionSuspended(int i) {
|
|
||||||
Logger.dev("SN: Google API Suspended");
|
|
||||||
handleResults(-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestTest() {
|
|
||||||
// Connect Google Service
|
|
||||||
mGoogleApiClient.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void safetyNetCheck() {
|
|
||||||
// Create nonce
|
|
||||||
byte[] nonce = new byte[24];
|
|
||||||
new SecureRandom().nextBytes(nonce);
|
|
||||||
|
|
||||||
Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT));
|
|
||||||
|
|
||||||
// Call SafetyNet
|
|
||||||
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
|
|
||||||
.setResultCallback(result -> {
|
|
||||||
Status status = result.getStatus();
|
|
||||||
if (status.isSuccess()) {
|
|
||||||
String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT));
|
|
||||||
Logger.dev("SN: Response: " + json);
|
|
||||||
try {
|
|
||||||
JSONObject decoded = new JSONObject(json);
|
|
||||||
handleResults(decoded.getBoolean("ctsProfileMatch") ? 1 : 0);
|
|
||||||
} catch (JSONException ignored) {}
|
|
||||||
} else {
|
|
||||||
Logger.dev("SN: No response");
|
|
||||||
handleResults(-1);
|
|
||||||
}
|
|
||||||
// Disconnect
|
|
||||||
mGoogleApiClient.disconnect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void handleResults(int i);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,18 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,205 +24,202 @@ public class Shell {
|
|||||||
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
|
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
|
||||||
public static int rootStatus;
|
public static int rootStatus;
|
||||||
|
|
||||||
private static boolean isInit = false;
|
private final Process shellProcess;
|
||||||
private static Process rootShell;
|
private final DataOutputStream STDIN;
|
||||||
private static DataOutputStream rootSTDIN;
|
private final DataInputStream STDOUT;
|
||||||
private static StreamGobbler rootSTDOUT;
|
|
||||||
private static List<String> rootOutList = Collections.synchronizedList(new ArrayList<String>());
|
|
||||||
|
|
||||||
public static void init() {
|
private boolean isValid;
|
||||||
|
|
||||||
isInit = true;
|
private void testRootShell(DataOutputStream in, DataInputStream out) throws IOException {
|
||||||
|
in.write(("id\n").getBytes("UTF-8"));
|
||||||
|
in.flush();
|
||||||
|
String s = new BufferedReader(new InputStreamReader(out)).readLine();
|
||||||
|
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shell() {
|
||||||
|
rootStatus = 1;
|
||||||
|
Process process = null;
|
||||||
|
DataOutputStream in = null;
|
||||||
|
DataInputStream out = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rootShell = Runtime.getRuntime().exec("su");
|
// Try getting global namespace
|
||||||
rootStatus = 1;
|
process = Runtime.getRuntime().exec("su --mount-master");
|
||||||
} catch (IOException err) {
|
in = new DataOutputStream(process.getOutputStream());
|
||||||
// No root
|
out = new DataInputStream(process.getInputStream());
|
||||||
rootStatus = 0;
|
testRootShell(in, out);
|
||||||
return;
|
} catch (IOException e) {
|
||||||
|
// Feature not implemented, normal root shell
|
||||||
|
try {
|
||||||
|
process = Runtime.getRuntime().exec("su");
|
||||||
|
in = new DataOutputStream(process.getOutputStream());
|
||||||
|
out = new DataInputStream(process.getInputStream());
|
||||||
|
testRootShell(in, out);
|
||||||
|
} catch (IOException e1) {
|
||||||
|
rootStatus = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
|
if (!rootAccess()) {
|
||||||
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
|
// Try to gain non-root sh
|
||||||
rootSTDOUT.start();
|
try {
|
||||||
|
process = Runtime.getRuntime().exec("sh");
|
||||||
// Setup umask and PATH
|
in = new DataOutputStream(process.getOutputStream());
|
||||||
su("umask 022");
|
out = new DataInputStream(process.getInputStream());
|
||||||
su("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || echo /data/busybox`:$PATH");
|
} catch (IOException e) {
|
||||||
|
// Nothing works....
|
||||||
List<String> ret = su("echo -BOC-", "id");
|
shellProcess = null;
|
||||||
|
STDIN = null;
|
||||||
if (ret == null) {
|
STDOUT = null;
|
||||||
// Something wrong with root, not allowed?
|
isValid = false;
|
||||||
rootStatus = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String line : ret) {
|
|
||||||
if (line.contains("uid=")) {
|
|
||||||
// id command is working, let's see if we are actually root
|
|
||||||
rootStatus = line.contains("uid=0") ? rootStatus : -1;
|
|
||||||
return;
|
|
||||||
} else if (!line.contains("-BOC-")) {
|
|
||||||
rootStatus = -1;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isValid = true;
|
||||||
|
shellProcess = process;
|
||||||
|
STDIN = in;
|
||||||
|
STDOUT = out;
|
||||||
|
sh_raw("umask 022");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shell(String command) {
|
||||||
|
Process process;
|
||||||
|
DataOutputStream in;
|
||||||
|
DataInputStream out;
|
||||||
|
|
||||||
|
try {
|
||||||
|
process = Runtime.getRuntime().exec(command);
|
||||||
|
in = new DataOutputStream(process.getOutputStream());
|
||||||
|
out = new DataInputStream(process.getInputStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Nothing works....
|
||||||
|
shellProcess = null;
|
||||||
|
STDIN = null;
|
||||||
|
STDOUT = null;
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid = true;
|
||||||
|
shellProcess = process;
|
||||||
|
STDIN = in;
|
||||||
|
STDOUT = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Shell getShell() {
|
||||||
|
return new Shell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Shell getShell(String command) {
|
||||||
|
return new Shell(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Shell getShell(Context context) {
|
||||||
|
MagiskManager magiskManager = Utils.getMagiskManager(context);
|
||||||
|
if (magiskManager.shell == null || !magiskManager.shell.isValid) {
|
||||||
|
// Get new shell if needed
|
||||||
|
magiskManager.shell = getShell();
|
||||||
|
}
|
||||||
|
return magiskManager.shell;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean rootAccess() {
|
public static boolean rootAccess() {
|
||||||
return isInit && rootStatus > 0;
|
return rootStatus > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> sh(String... commands) {
|
public void loadInputStream(InputStream in) {
|
||||||
List<String> res = Collections.synchronizedList(new ArrayList<String>());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Process process = Runtime.getRuntime().exec("sh");
|
int read;
|
||||||
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
|
byte[] bytes = new byte[4096];
|
||||||
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
|
while ((read = in.read(bytes)) != -1) {
|
||||||
|
STDIN.write(bytes, 0, read);
|
||||||
STDOUT.start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String write : commands) {
|
|
||||||
STDIN.write((write + "\n").getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
Logger.shell(false, write);
|
|
||||||
}
|
|
||||||
STDIN.write("exit\n".getBytes("UTF-8"));
|
|
||||||
STDIN.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!e.getMessage().contains("EPIPE")) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
STDIN.flush();
|
||||||
process.waitFor();
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
try {
|
|
||||||
STDIN.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// might be closed already
|
|
||||||
}
|
|
||||||
STDOUT.join();
|
|
||||||
process.destroy();
|
|
||||||
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
// shell probably not found
|
|
||||||
res = null;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> sh(String... commands) {
|
||||||
|
List<String> res = new ArrayList<>();
|
||||||
|
if (!isValid) return res;
|
||||||
|
sh(res, commands);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run with the same shell by default
|
public void sh_raw(String... commands) {
|
||||||
public static List<String> su(String... commands) {
|
sh_raw(false, commands);
|
||||||
return su(false, commands);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> su(boolean newShell, String... commands) {
|
public void sh_raw(boolean stdout, String... commands) {
|
||||||
List<String> res;
|
if (!isValid) return;
|
||||||
Process process;
|
synchronized (shellProcess) {
|
||||||
DataOutputStream STDIN;
|
|
||||||
StreamGobbler STDOUT;
|
|
||||||
|
|
||||||
// Create the default shell if not init
|
|
||||||
if (!newShell && !isInit) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newShell && !rootAccess()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newShell) {
|
|
||||||
res = Collections.synchronizedList(new ArrayList<String>());
|
|
||||||
try {
|
try {
|
||||||
process = Runtime.getRuntime().exec("su");
|
for (String command : commands) {
|
||||||
STDIN = new DataOutputStream(process.getOutputStream());
|
Logger.shell(command);
|
||||||
STDOUT = new StreamGobbler(process.getInputStream(), res);
|
STDIN.write((command + (stdout ? "\n" : " >/dev/null\n")).getBytes("UTF-8"));
|
||||||
|
STDIN.flush();
|
||||||
// Run the new shell with busybox and proper umask
|
}
|
||||||
STDIN.write(("umask 022\n").getBytes("UTF-8"));
|
} catch (IOException e) {
|
||||||
STDIN.flush();
|
e.printStackTrace();
|
||||||
STDIN.write(("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || " +
|
shellProcess.destroy();
|
||||||
"echo /data/busybox`:$PATH\n").getBytes("UTF-8"));
|
isValid = false;
|
||||||
STDIN.flush();
|
|
||||||
} catch (IOException err) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
STDOUT.start();
|
|
||||||
} else {
|
|
||||||
process = rootShell;
|
|
||||||
STDIN = rootSTDIN;
|
|
||||||
STDOUT = rootSTDOUT;
|
|
||||||
res = rootOutList;
|
|
||||||
res.clear();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sh(Collection<String> output, String... commands) {
|
||||||
|
if (!isValid) return;
|
||||||
try {
|
try {
|
||||||
for (String write : commands) {
|
shellProcess.exitValue();
|
||||||
STDIN.write((write + "\n").getBytes("UTF-8"));
|
isValid = false;
|
||||||
STDIN.flush();
|
return; // The process is dead, return
|
||||||
Logger.shell(true, write);
|
} catch (IllegalThreadStateException ignored) {
|
||||||
}
|
// This should be the expected result
|
||||||
if (newShell) {
|
}
|
||||||
STDIN.write("exit\n".getBytes("UTF-8"));
|
synchronized (shellProcess) {
|
||||||
STDIN.flush();
|
StreamGobbler out = new StreamGobbler(STDOUT, output);
|
||||||
process.waitFor();
|
out.start();
|
||||||
|
sh_raw(true, commands);
|
||||||
|
sh_raw(true, "echo \'-shell-done-\'");
|
||||||
|
try { out.join(); } catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
public List<String> su(String... commands) {
|
||||||
STDIN.close();
|
if (!rootAccess()) return sh();
|
||||||
} catch (IOException ignore) {
|
return sh(commands);
|
||||||
// might be closed already
|
}
|
||||||
}
|
|
||||||
|
|
||||||
STDOUT.join();
|
public void su_raw(String... commands) {
|
||||||
process.destroy();
|
if (!rootAccess()) return;
|
||||||
} else {
|
sh_raw(commands);
|
||||||
STDIN.write(("echo\n").getBytes("UTF-8"));
|
}
|
||||||
STDIN.flush();
|
|
||||||
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
|
public void su(Collection<String> output, String... commands) {
|
||||||
STDIN.flush();
|
if (!rootAccess()) return;
|
||||||
while (true) {
|
sh(output, commands);
|
||||||
try {
|
}
|
||||||
// Process terminated, it means the interactive shell has some issues
|
|
||||||
process.exitValue();
|
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
|
||||||
rootStatus = -1;
|
|
||||||
return null;
|
@Override
|
||||||
} catch (IllegalThreadStateException e) {
|
public abstract boolean add(E e);
|
||||||
// Process still running, gobble output until done
|
|
||||||
int end = res.size() - 1;
|
@Override
|
||||||
if (end > 0) {
|
public E get(int i) {
|
||||||
if (res.get(end).equals("-root-done-")) {
|
|
||||||
res.remove(end);
|
|
||||||
if (res.get(end -1).isEmpty()) {
|
|
||||||
res.remove(end -1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try { STDOUT.join(100); } catch (InterruptedException err) {
|
|
||||||
rootStatus = -1;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (!e.getMessage().contains("EPIPE")) {
|
|
||||||
Logger.dev("Shell: Root shell error...");
|
|
||||||
rootStatus = -1;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
Logger.dev("Shell: Root shell error...");
|
|
||||||
rootStatus = -1;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ArrayList<>(res);
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.List;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||||
@@ -13,40 +15,38 @@ import java.util.List;
|
|||||||
public class StreamGobbler extends Thread {
|
public class StreamGobbler extends Thread {
|
||||||
|
|
||||||
private BufferedReader reader = null;
|
private BufferedReader reader = null;
|
||||||
private List<String> writer = null;
|
private Collection<String> writer = null;
|
||||||
private boolean isRoot = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>StreamGobbler constructor</p>
|
* <p>StreamGobbler constructor</p>
|
||||||
*
|
*
|
||||||
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
|
* <p>We use this class because sh STDOUT and STDERR should be read as quickly as
|
||||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||||
* returning (as the buffer is full, pausing the native process)</p>
|
* returning (as the buffer is full, pausing the native process)</p>
|
||||||
*
|
*
|
||||||
* @param inputStream InputStream to read from
|
* @param inputStream InputStream to read from
|
||||||
* @param outputList {@literal List<String>} to write to, or null
|
* @param outputList {@literal List<String>} to write to, or null
|
||||||
*/
|
*/
|
||||||
public StreamGobbler(InputStream inputStream, List<String> outputList) {
|
public StreamGobbler(InputStream inputStream, Collection<String> outputList) {
|
||||||
|
try {
|
||||||
|
while (inputStream.available() != 0) {
|
||||||
|
inputStream.skip(inputStream.available());
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
writer = outputList;
|
writer = outputList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamGobbler(InputStream inputStream, List<String> outputList, boolean root) {
|
|
||||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
|
||||||
writer = outputList;
|
|
||||||
isRoot = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// keep reading the InputStream until it ends (or an error occurs)
|
// keep reading the InputStream until it ends (or an error occurs)
|
||||||
try {
|
try {
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (TextUtils.equals(line, "-shell-done-"))
|
||||||
|
return;
|
||||||
writer.add(line);
|
writer.add(line);
|
||||||
if (!line.equals("-root-done-") && !line.isEmpty()) {
|
Logger.shell(line);
|
||||||
Logger.shell(isRoot, "OUT: " + line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// reader probably closed, expected exit condition
|
// reader probably closed, expected exit condition
|
||||||
|
|||||||
75
app/src/main/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
75
app/src/main/java/com/topjohnwu/magisk/utils/Topic.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Topic {
|
||||||
|
|
||||||
|
public boolean hasPublished = false;
|
||||||
|
private List<WeakReference<Subscriber>> subscribers;
|
||||||
|
|
||||||
|
public void subscribe(Subscriber sub) {
|
||||||
|
if (subscribers == null) {
|
||||||
|
subscribers = new LinkedList<>();
|
||||||
|
}
|
||||||
|
subscribers.add(new WeakReference<>(sub));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe() {
|
||||||
|
subscribers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe(Subscriber sub) {
|
||||||
|
for (Iterator<WeakReference<Subscriber>> i = subscribers.iterator(); i.hasNext();) {
|
||||||
|
WeakReference<Subscriber> subscriber = i.next();
|
||||||
|
if (subscriber.get() == null || subscriber.get() == sub) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publish() {
|
||||||
|
publish(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publish(boolean record) {
|
||||||
|
publish(record, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publish(boolean record, Object result) {
|
||||||
|
hasPublished = record;
|
||||||
|
if (subscribers != null) {
|
||||||
|
for (WeakReference<Subscriber> subscriber : subscribers) {
|
||||||
|
if (subscriber.get() != null)
|
||||||
|
subscriber.get().onTopicPublished(this, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface Subscriber {
|
||||||
|
default void subscribeTopics() {
|
||||||
|
for (Topic topic : getSubscription()) {
|
||||||
|
if (topic.hasPublished) {
|
||||||
|
onTopicPublished(topic);
|
||||||
|
}
|
||||||
|
topic.subscribe(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default void unsubscribeTopics() {
|
||||||
|
for (Topic event : getSubscription()) {
|
||||||
|
event.unsubscribe(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default void onTopicPublished() {
|
||||||
|
onTopicPublished(null, null);
|
||||||
|
}
|
||||||
|
default void onTopicPublished(Topic topic) {
|
||||||
|
onTopicPublished(topic, null);
|
||||||
|
}
|
||||||
|
void onTopicPublished(Topic topic, Object result);
|
||||||
|
Topic[] getSubscription();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,102 +3,118 @@ package com.topjohnwu.magisk.utils;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.DownloadManager;
|
import android.app.DownloadManager;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.admin.DevicePolicyManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.FlashActivity;
|
||||||
|
import com.topjohnwu.magisk.MagiskFragment;
|
||||||
import com.topjohnwu.magisk.MagiskManager;
|
import com.topjohnwu.magisk.MagiskManager;
|
||||||
import com.topjohnwu.magisk.R;
|
import com.topjohnwu.magisk.R;
|
||||||
import com.topjohnwu.magisk.asyncs.LoadRepos;
|
import com.topjohnwu.magisk.SplashActivity;
|
||||||
|
import com.topjohnwu.magisk.asyncs.RestoreStockBoot;
|
||||||
|
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||||
|
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
|
||||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||||
|
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
|
public static final int SELECT_BOOT_IMG = 3;
|
||||||
|
public static final String UNINSTALLER = "magisk_uninstaller.sh";
|
||||||
|
public static final String UTIL_FUNCTIONS= "util_functions.sh";
|
||||||
public static boolean isDownloading = false;
|
public static boolean isDownloading = false;
|
||||||
|
|
||||||
public static boolean itemExist(String path) {
|
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
|
||||||
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
|
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
|
||||||
List<String> ret = Shell.su(command);
|
|
||||||
|
public static boolean itemExist(Shell shell, String path) {
|
||||||
|
String command = "[ -e " + path + " ] && echo true || echo false";
|
||||||
|
List<String> ret = shell.su(command);
|
||||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean commandExists(String s) {
|
public static void createFile(Shell shell, String path) {
|
||||||
String command = "if [ -z $(which " + s + ") ]; then echo false; else echo true; fi";
|
|
||||||
List<String> ret = Shell.sh(command);
|
|
||||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean createFile(String path) {
|
|
||||||
String folder = path.substring(0, path.lastIndexOf('/'));
|
String folder = path.substring(0, path.lastIndexOf('/'));
|
||||||
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null; if [ -f \"" + path + "\" ]; then echo true; else echo false; fi";
|
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
|
||||||
List<String> ret = Shell.su(command);
|
shell.su_raw(command);
|
||||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean removeItem(String path) {
|
public static void removeItem(Shell shell, String path) {
|
||||||
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
|
String command = "rm -rf " + path + " 2>/dev/null";
|
||||||
List<String> ret = Shell.su(command);
|
shell.su_raw(command);
|
||||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getModList(String path) {
|
public static List<String> getModList(Shell shell, String path) {
|
||||||
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
|
String command = "ls -d " + path + "/* | grep -v lost+found";
|
||||||
return Shell.su(command);
|
return shell.su(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> readFile(String path) {
|
public static List<String> readFile(Shell shell, String path) {
|
||||||
List<String> ret;
|
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
|
||||||
String command = "cat " + path;
|
return shell.su(command);
|
||||||
if (Shell.rootAccess()) {
|
|
||||||
ret = Shell.su(command);
|
|
||||||
} else {
|
|
||||||
ret = Shell.sh(command);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
|
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
|
||||||
if (isDownloading)
|
if (isDownloading)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
runWithPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
|
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs())
|
||||||
|
|| (file.exists() && !file.delete())) {
|
||||||
|
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs()) || (file.exists() && !file.delete())) {
|
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
|
||||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
isDownloading = true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
|
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||||
isDownloading = true;
|
|
||||||
|
|
||||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
if (link != null) {
|
||||||
|
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
|
||||||
if (link != null) {
|
request.setDestinationUri(Uri.fromFile(file));
|
||||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
|
receiver.setDownloadID(downloadManager.enqueue(request));
|
||||||
request.setDestinationUri(Uri.fromFile(file));
|
}
|
||||||
receiver.setDownloadID(downloadManager.enqueue(request));
|
receiver.setFilename(filename);
|
||||||
}
|
context.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||||
receiver.setFilename(filename);
|
});
|
||||||
context.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLegalFilename(CharSequence filename) {
|
public static String getLegalFilename(CharSequence filename) {
|
||||||
@@ -107,21 +123,6 @@ public class Utils {
|
|||||||
.replace("#", "").replace("@", "").replace("*", "");
|
.replace("#", "").replace("@", "").replace("*", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String detectBootImage() {
|
|
||||||
String[] commands = {
|
|
||||||
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
|
|
||||||
"BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || readlink /dev/block/platform/*/by-name/$PARTITION || readlink /dev/block/platform/*/*/by-name/$PARTITION`",
|
|
||||||
"if [ ! -z \"$BOOTIMAGE\" ]; then break; fi",
|
|
||||||
"done",
|
|
||||||
"echo \"${BOOTIMAGE##*/}\""
|
|
||||||
};
|
|
||||||
List<String> ret = Shell.su(commands);
|
|
||||||
if (isValidShellResponse(ret)) {
|
|
||||||
return ret.get(0);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
|
||||||
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
|
||||||
}
|
}
|
||||||
@@ -140,25 +141,19 @@ public class Utils {
|
|||||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||||
|
return getPrefsInt(prefs, key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public static MagiskManager getMagiskManager(Context context) {
|
public static MagiskManager getMagiskManager(Context context) {
|
||||||
return (MagiskManager) context.getApplicationContext();
|
return (MagiskManager) context.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkSafetyNet(MagiskManager magiskManager) {
|
public static void clearRepoCache(Context context) {
|
||||||
new SafetyNetHelper(magiskManager) {
|
MagiskManager mm = getMagiskManager(context);
|
||||||
@Override
|
mm.prefs.edit().remove(UpdateRepos.ETAG_KEY).apply();
|
||||||
public void handleResults(int i) {
|
mm.repoDB.clearRepo();
|
||||||
magiskManager.SNCheckResult = i;
|
mm.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
|
||||||
magiskManager.safetyNetDone.trigger();
|
|
||||||
}
|
|
||||||
}.requestTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearRepoCache(Activity activity) {
|
|
||||||
MagiskManager magiskManager = getMagiskManager(activity);
|
|
||||||
magiskManager.prefs.edit().remove(LoadRepos.ETAG_KEY).apply();
|
|
||||||
new RepoDatabaseHelper(activity).clearRepo();
|
|
||||||
Toast.makeText(activity, R.string.repo_cache_cleared, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getNameFromUri(Context context, Uri uri) {
|
public static String getNameFromUri(Context context, Uri uri) {
|
||||||
@@ -185,4 +180,355 @@ public class Utils {
|
|||||||
Snackbar.LENGTH_LONG)
|
Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.ok, (v)->{}).show();
|
.setAction(R.string.ok, (v)->{}).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean checkNetworkStatus(Context context) {
|
||||||
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
||||||
|
return networkInfo != null && networkInfo.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showMagiskUpdateNotification(MagiskManager mm) {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
|
||||||
|
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||||
|
.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);
|
||||||
|
Intent intent = new Intent(mm, SplashActivity.class);
|
||||||
|
intent.putExtra(MagiskManager.INTENT_SECTION, "magisk");
|
||||||
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||||
|
stackBuilder.addParentStack(SplashActivity.class);
|
||||||
|
stackBuilder.addNextIntent(intent);
|
||||||
|
PendingIntent pendingIntent = stackBuilder.getPendingIntent(MAGISK_UPDATE_NOTIFICATION_ID,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
builder.setContentIntent(pendingIntent);
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
notificationManager.notify(MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showManagerUpdateNotification(MagiskManager mm) {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
|
||||||
|
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||||
|
.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);
|
||||||
|
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||||
|
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
|
||||||
|
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||||
|
APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
builder.setContentIntent(pendingIntent);
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
notificationManager.notify(APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableMagiskHide(Shell shell) {
|
||||||
|
shell.su_raw("magiskhide --enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disableMagiskHide(Shell shell) {
|
||||||
|
shell.su_raw("magiskhide --disable");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> listMagiskHide(Shell shell) {
|
||||||
|
return shell.su("magiskhide --ls");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addMagiskHide(Shell shell, String pkg) {
|
||||||
|
shell.su_raw("magiskhide --add " + pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rmMagiskHide(Shell shell, String pkg) {
|
||||||
|
shell.su_raw("magiskhide --rm " + pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLocaleString(Context context, Locale locale, @StringRes int id) {
|
||||||
|
Configuration config = context.getResources().getConfiguration();
|
||||||
|
config.setLocale(locale);
|
||||||
|
Context localizedContext = context.createConfigurationContext(config);
|
||||||
|
return localizedContext.getString(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Locale> getAvailableLocale(Context context) {
|
||||||
|
List<Locale> locales = new ArrayList<>();
|
||||||
|
HashSet<String> set = new HashSet<>();
|
||||||
|
Locale locale;
|
||||||
|
|
||||||
|
int compareId = R.string.download_file_error;
|
||||||
|
|
||||||
|
// Add default locale
|
||||||
|
locales.add(Locale.ENGLISH);
|
||||||
|
set.add(getLocaleString(context, Locale.ENGLISH, compareId));
|
||||||
|
|
||||||
|
// Add some special locales
|
||||||
|
locales.add(Locale.TAIWAN);
|
||||||
|
set.add(getLocaleString(context, Locale.TAIWAN, compareId));
|
||||||
|
locale = new Locale("pt", "BR");
|
||||||
|
locales.add(locale);
|
||||||
|
set.add(getLocaleString(context, locale, compareId));
|
||||||
|
|
||||||
|
// Other locales
|
||||||
|
for (String s : context.getAssets().getLocales()) {
|
||||||
|
locale = Locale.forLanguageTag(s);
|
||||||
|
if (set.add(getLocaleString(context, locale, compareId))) {
|
||||||
|
locales.add(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(locales, (l1, l2) -> l1.getDisplayName(l1).compareTo(l2.getDisplayName(l2)));
|
||||||
|
|
||||||
|
return locales;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runWithPermission(Context context, String permission, Runnable callback) {
|
||||||
|
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Passed in context should be an activity if not granted, need to show dialog!
|
||||||
|
if (!(context instanceof com.topjohnwu.magisk.components.Activity))
|
||||||
|
return;
|
||||||
|
com.topjohnwu.magisk.components.Activity activity = (com.topjohnwu.magisk.components.Activity) context;
|
||||||
|
activity.setPermissionGrantCallback(callback);
|
||||||
|
ActivityCompat.requestPermissions(activity, new String[] { permission }, 0);
|
||||||
|
} else {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showMagiskInstallDialog(MagiskFragment fragment, boolean enc, boolean verity) {
|
||||||
|
MagiskManager mm = getMagiskManager(fragment.getActivity());
|
||||||
|
String filename = getLegalFilename("Magisk-v" + mm.remoteMagiskVersionString + ".zip");
|
||||||
|
new AlertDialogBuilder(fragment.getActivity())
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
List<String> res = Shell.getShell(mm).su("echo $SLOT");
|
||||||
|
if (isValidShellResponse(res)) {
|
||||||
|
options.add(mm.getString(R.string.install_second_slot));
|
||||||
|
}
|
||||||
|
char[] slot = isValidShellResponse(res) ? res.get(0).toCharArray() : null;
|
||||||
|
new AlertDialog.Builder(fragment.getActivity())
|
||||||
|
.setTitle(R.string.select_method)
|
||||||
|
.setItems(
|
||||||
|
options.toArray(new String [0]),
|
||||||
|
(dialog, idx) -> {
|
||||||
|
String boot;
|
||||||
|
DownloadReceiver receiver = null;
|
||||||
|
switch (idx) {
|
||||||
|
case 1:
|
||||||
|
if (mm.remoteMagiskVersionCode < 1400) {
|
||||||
|
mm.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mm.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.setType("*/*");
|
||||||
|
fragment.startActivityForResult(intent, SELECT_BOOT_IMG,
|
||||||
|
(requestCode, resultCode, data) -> {
|
||||||
|
if (requestCode == SELECT_BOOT_IMG
|
||||||
|
&& resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
dlAndReceive(
|
||||||
|
fragment.getActivity(),
|
||||||
|
new DownloadReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onDownloadDone(Uri uri) {
|
||||||
|
Intent intent = new Intent(mm, FlashActivity.class);
|
||||||
|
intent.setData(uri)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(FlashActivity.SET_BOOT, data.getData())
|
||||||
|
.putExtra(FlashActivity.SET_ENC, enc)
|
||||||
|
.putExtra(FlashActivity.SET_VERITY, verity)
|
||||||
|
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
|
||||||
|
mm.startActivity(intent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mm.magiskLink,
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case 0:
|
||||||
|
receiver = new DownloadReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onDownloadDone(Uri uri) {
|
||||||
|
showUriSnack(fragment.getActivity(), uri);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
boot = fragment.getSelectedBootImage();
|
||||||
|
if (boot == null)
|
||||||
|
return;
|
||||||
|
receiver = new DownloadReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onDownloadDone(Uri uri) {
|
||||||
|
Intent intent = new Intent(mm, FlashActivity.class);
|
||||||
|
intent.setData(uri)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(FlashActivity.SET_BOOT, boot)
|
||||||
|
.putExtra(FlashActivity.SET_ENC, enc)
|
||||||
|
.putExtra(FlashActivity.SET_VERITY, verity)
|
||||||
|
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
|
||||||
|
mm.startActivity(intent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
assert (slot != null);
|
||||||
|
// Choose the other slot
|
||||||
|
if (slot[1] == 'a') slot[1] = 'b';
|
||||||
|
else slot[1] = 'a';
|
||||||
|
// Then find the boot image again
|
||||||
|
List<String> ret = Shell.getShell(mm).su(
|
||||||
|
"BOOTIMAGE=",
|
||||||
|
"SLOT=" + String.valueOf(slot),
|
||||||
|
"find_boot_image",
|
||||||
|
"echo \"$BOOTIMAGE\""
|
||||||
|
);
|
||||||
|
boot = isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
|
||||||
|
Shell.getShell(mm).su_raw("mount_partitions");
|
||||||
|
if (boot == null)
|
||||||
|
return;
|
||||||
|
receiver = new DownloadReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onDownloadDone(Uri uri) {
|
||||||
|
Intent intent = new Intent(mm, FlashActivity.class);
|
||||||
|
intent.setData(uri)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(FlashActivity.SET_BOOT, boot)
|
||||||
|
.putExtra(FlashActivity.SET_ENC, enc)
|
||||||
|
.putExtra(FlashActivity.SET_VERITY, verity)
|
||||||
|
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
|
||||||
|
mm.startActivity(intent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
Utils.dlAndReceive(
|
||||||
|
fragment.getActivity(),
|
||||||
|
receiver,
|
||||||
|
mm.magiskLink,
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
).show();
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||||
|
if (mm.releaseNoteLink != null) {
|
||||||
|
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
|
||||||
|
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
mm.startActivity(openLink);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showManagerInstallDialog(Activity activity) {
|
||||||
|
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||||
|
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,
|
||||||
|
Utils.getLegalFilename("MagiskManager-v" +
|
||||||
|
mm.remoteManagerVersionString + ".apk")))
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.install, (d, i) -> {
|
||||||
|
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||||
|
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
|
||||||
|
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
|
||||||
|
mm.sendBroadcast(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showUninstallDialog(MagiskFragment fragment) {
|
||||||
|
MagiskManager mm = Utils.getMagiskManager(fragment.getActivity());
|
||||||
|
new AlertDialogBuilder(fragment.getActivity())
|
||||||
|
.setTitle(R.string.uninstall_magisk_title)
|
||||||
|
.setMessage(R.string.uninstall_magisk_msg)
|
||||||
|
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||||
|
try {
|
||||||
|
InputStream in = mm.getAssets().open(UNINSTALLER);
|
||||||
|
File uninstaller = new File(mm.getCacheDir(), UNINSTALLER);
|
||||||
|
FileOutputStream out = new FileOutputStream(uninstaller);
|
||||||
|
byte[] bytes = new byte[1024];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(bytes)) != -1) {
|
||||||
|
out.write(bytes, 0, read);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
in = mm.getAssets().open(UTIL_FUNCTIONS);
|
||||||
|
File utils = new File(mm.getCacheDir(), UTIL_FUNCTIONS);
|
||||||
|
out = new FileOutputStream(utils);
|
||||||
|
while ((read = in.read(bytes)) != -1) {
|
||||||
|
out.write(bytes, 0, read);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
Shell.getShell(mm).su(
|
||||||
|
"cat " + uninstaller + " > /cache/" + UNINSTALLER,
|
||||||
|
"cat " + utils + " > /data/magisk/" + UTIL_FUNCTIONS
|
||||||
|
);
|
||||||
|
mm.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
|
||||||
|
Shell.getShell(mm).su_raw(
|
||||||
|
"sleep 5",
|
||||||
|
"pm uninstall " + mm.getApplicationInfo().packageName
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
|
||||||
|
String boot = fragment.getSelectedBootImage();
|
||||||
|
if (boot == null) return;
|
||||||
|
new RestoreStockBoot(mm, boot).exec();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no_thanks, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean useFDE(Context context) {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||||
|
&& context.getSystemService(DevicePolicyManager.class).getStorageEncryptionStatus()
|
||||||
|
== DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getEncContext(Context context) {
|
||||||
|
if (useFDE(context))
|
||||||
|
return context.createDeviceProtectedStorageContext();
|
||||||
|
else
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,51 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
public class WebService {
|
public class WebService {
|
||||||
|
|
||||||
public final static int GET = 1;
|
public static String getString(String url) {
|
||||||
public final static int POST = 2;
|
return getString(url, null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Making web service call
|
|
||||||
*
|
|
||||||
* @url - url to make request
|
|
||||||
* @requestmethod - http request method
|
|
||||||
*/
|
|
||||||
public static String request(String url, int method) {
|
|
||||||
return request(url, method, null, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String request(String url, int method, boolean newline) {
|
public static String getString(String url, Map<String, String> header) {
|
||||||
return request(url, method, null, newline);
|
HttpURLConnection conn = request(url, header);
|
||||||
|
if (conn == null) return "";
|
||||||
|
return getString(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static String getString(HttpURLConnection conn) {
|
||||||
* Making service call
|
|
||||||
*
|
|
||||||
* @url - url to make request
|
|
||||||
* @requestmethod - http request method
|
|
||||||
* @params - http request params
|
|
||||||
* @header - http request header
|
|
||||||
* @newline - true to append a newline each line
|
|
||||||
*/
|
|
||||||
public static String request(String urlAddress, int method,
|
|
||||||
Map<String, String> header, boolean newline) {
|
|
||||||
Logger.dev("WebService: Service call " + urlAddress);
|
|
||||||
URL url;
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
try {
|
try {
|
||||||
url = new URL(urlAddress);
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
|
int len;
|
||||||
|
char buf[] = new char[4096];
|
||||||
|
while ((len = br.read(buf)) != -1) {
|
||||||
|
builder.append(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.disconnect();
|
||||||
|
return builder.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpURLConnection request(String address, Map<String, String> header) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(address);
|
||||||
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setReadTimeout(15000);
|
conn.setReadTimeout(15000);
|
||||||
conn.setConnectTimeout(15000);
|
conn.setConnectTimeout(15000);
|
||||||
conn.setDoInput(true);
|
|
||||||
|
|
||||||
if (method == POST) {
|
|
||||||
conn.setRequestMethod("POST");
|
|
||||||
} else if (method == GET) {
|
|
||||||
conn.setRequestMethod("GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header != null) {
|
if (header != null) {
|
||||||
for (Map.Entry<String, String> entry : header.entrySet()) {
|
for (Map.Entry<String, String> entry : header.entrySet()) {
|
||||||
@@ -62,31 +53,20 @@ public class WebService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
conn.connect();
|
||||||
|
|
||||||
if (responseCode == HttpsURLConnection.HTTP_OK) {
|
if (header != null) {
|
||||||
String line;
|
header.clear();
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
|
||||||
while ((line = br.readLine()) != null) {
|
List<String> l = entry.getValue();
|
||||||
if (newline) {
|
header.put(entry.getKey(), l.get(l.size() - 1));
|
||||||
response.append(line).append("\n");
|
|
||||||
} else {
|
|
||||||
response.append(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (header != null) {
|
|
||||||
header.clear();
|
|
||||||
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
|
|
||||||
List<String> l = entry.getValue();
|
|
||||||
header.put(entry.getKey(), l.get(l.size() - 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,58 +3,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include "zipadjust.h"
|
#include "zipadjust.h"
|
||||||
|
|
||||||
JNIEXPORT jbyteArray JNICALL
|
|
||||||
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust___3BI(JNIEnv *env, jclass type,
|
|
||||||
jbyteArray jbytes, jint size) {
|
|
||||||
fin = (*env)->GetPrimitiveArrayCritical(env, jbytes, NULL);
|
|
||||||
insize = (size_t) size;
|
|
||||||
|
|
||||||
zipadjust(0);
|
|
||||||
|
|
||||||
(*env)->ReleasePrimitiveArrayCritical(env, jbytes, fin, 0);
|
|
||||||
|
|
||||||
jbyteArray ret = (*env)->NewByteArray(env, outsize);
|
|
||||||
(*env)->SetByteArrayRegion(env, ret, 0, outsize, (const jbyte*) fout);
|
|
||||||
free(fout);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust__Ljava_lang_String_2(JNIEnv *env, jclass type, jstring name) {
|
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust(JNIEnv *env, jclass type, jstring filenameIn_,
|
||||||
const char *filename = (*env)->GetStringUTFChars(env, name, NULL);
|
jstring filenameOut_) {
|
||||||
int fd = open(filename, O_RDONLY);
|
const char *filenameIn = (*env)->GetStringUTFChars(env, filenameIn_, 0);
|
||||||
if (fd < 0)
|
const char *filenameOut = (*env)->GetStringUTFChars(env, filenameOut_, 0);
|
||||||
return;
|
|
||||||
|
|
||||||
// Load the file to memory
|
// TODO
|
||||||
insize = lseek(fd, 0, SEEK_END);
|
zipadjust(filenameIn, filenameOut, 0);
|
||||||
lseek(fd, 0, SEEK_SET);
|
|
||||||
fin = malloc(insize);
|
|
||||||
read(fd, fin, insize);
|
|
||||||
|
|
||||||
zipadjust(0);
|
(*env)->ReleaseStringUTFChars(env, filenameIn_, filenameIn);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, filenameOut_, filenameOut);
|
||||||
close(fd);
|
}
|
||||||
|
|
||||||
// Open file for output
|
|
||||||
fd = open(filename, O_WRONLY | O_TRUNC);
|
|
||||||
if (fd < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
(*env)->ReleaseStringUTFChars(env, name, filename);
|
|
||||||
|
|
||||||
// Write back to file
|
|
||||||
lseek(fd, 0, SEEK_SET);
|
|
||||||
write(fd, fout, outsize);
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
free(fin);
|
|
||||||
free(fout);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <fcntl.h>
|
||||||
#include <zlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include "zipadjust.h"
|
#include "zipadjust.h"
|
||||||
|
|
||||||
size_t insize = 0, outsize = 0, alloc = 0;
|
#ifndef O_BINARY
|
||||||
unsigned char *fin = NULL, *fout = NULL;
|
#define O_BINARY 0
|
||||||
|
#define O_TEXT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
struct local_header_struct {
|
struct local_header_struct {
|
||||||
@@ -83,41 +86,43 @@ static int xerror(char* message) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xseekread(off_t offset, void* buf, size_t bytes) {
|
static int xseekread(int fd, off_t offset, void* buf, size_t bytes) {
|
||||||
memcpy(buf, fin + offset, bytes);
|
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
|
||||||
|
if (read(fd, buf, bytes) != bytes) return xerror("Read failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xseekwrite(off_t offset, const void* buf, size_t bytes) {
|
static int xseekwrite(int fd, off_t offset, void* buf, size_t bytes) {
|
||||||
if (offset + bytes > outsize) outsize = offset + bytes;
|
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
|
||||||
if (outsize > alloc) {
|
if (write(fd, buf, bytes) != bytes) return xerror("Write failed");
|
||||||
fout = realloc(fout, outsize);
|
|
||||||
alloc = outsize;
|
|
||||||
}
|
|
||||||
memcpy(fout + offset, buf, bytes);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xfilecopy(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
static int xfilecopy(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
|
||||||
unsigned int CHUNK = 256 * 1024;
|
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||||
unsigned char* buf = malloc(CHUNK);
|
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||||
|
|
||||||
|
int CHUNK = 256 * 1024;
|
||||||
|
void* buf = malloc(CHUNK);
|
||||||
if (buf == NULL) return xerror("malloc failed");
|
if (buf == NULL) return xerror("malloc failed");
|
||||||
size_t left = bytes;
|
size_t left = bytes;
|
||||||
while (left > 0) {
|
while (left > 0) {
|
||||||
size_t wanted = (left < CHUNK) ? left : CHUNK;
|
size_t wanted = (left < CHUNK) ? left : CHUNK;
|
||||||
xseekread(offsetIn, buf, wanted);
|
size_t r = read(fdIn, buf, wanted);
|
||||||
xseekwrite(offsetOut, buf, wanted);
|
if (r <= 0) return xerror("Read failed");
|
||||||
offsetIn += wanted;
|
if (write(fdOut, buf, r) != r) return xerror("Write failed");
|
||||||
offsetOut += wanted;
|
left -= r;
|
||||||
left -= wanted;
|
|
||||||
}
|
}
|
||||||
free(buf);
|
free(buf);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
static int xdecompress(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
|
||||||
unsigned int CHUNK = 256 * 1024;
|
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||||
|
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
|
||||||
|
|
||||||
|
int CHUNK = 256 * 1024;
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
unsigned have;
|
unsigned have;
|
||||||
@@ -134,12 +139,9 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
|||||||
if (ret != Z_OK) return xerror("ret != Z_OK");
|
if (ret != Z_OK) return xerror("ret != Z_OK");
|
||||||
|
|
||||||
do {
|
do {
|
||||||
strm.avail_in = insize - offsetIn;
|
strm.avail_in = read(fdIn, in, CHUNK);
|
||||||
if (strm.avail_in == 0) break;
|
if (strm.avail_in == 0) break;
|
||||||
strm.avail_in = (strm.avail_in > CHUNK) ? CHUNK : strm.avail_in;
|
|
||||||
xseekread(offsetIn, in, strm.avail_in);
|
|
||||||
strm.next_in = in;
|
strm.next_in = in;
|
||||||
offsetIn += strm.avail_in;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
strm.avail_out = CHUNK;
|
strm.avail_out = CHUNK;
|
||||||
@@ -157,8 +159,10 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
have = CHUNK - strm.avail_out;
|
have = CHUNK - strm.avail_out;
|
||||||
xseekwrite(offsetOut, out, have);
|
if (write(fdOut, out, have) != have) {
|
||||||
offsetOut += have;
|
(void)inflateEnd(&strm);
|
||||||
|
return xerror("Write failed");
|
||||||
|
}
|
||||||
} while (strm.avail_out == 0);
|
} while (strm.avail_out == 0);
|
||||||
} while (ret != Z_STREAM_END);
|
} while (ret != Z_STREAM_END);
|
||||||
(void)inflateEnd(&strm);
|
(void)inflateEnd(&strm);
|
||||||
@@ -166,118 +170,128 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
|
|||||||
return ret == Z_STREAM_END ? 1 : 0;
|
return ret == Z_STREAM_END ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int zipadjust(int decompress) {
|
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress) {
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
|
|
||||||
char filename[1024];
|
int fin = open(filenameIn, O_RDONLY | O_BINARY);
|
||||||
|
if (fin > 0) {
|
||||||
|
unsigned int size = lseek(fin, 0, SEEK_END);
|
||||||
|
lseek(fin, 0, SEEK_SET);
|
||||||
|
LOGD("%d bytes\n", size);
|
||||||
|
|
||||||
central_footer_t central_footer;
|
char filename[1024];
|
||||||
uint32_t central_directory_in_position = 0;
|
|
||||||
uint32_t central_directory_in_size = 0;
|
|
||||||
uint32_t central_directory_out_size = 0;
|
|
||||||
|
|
||||||
int i;
|
central_footer_t central_footer;
|
||||||
for (i = insize - 4; i >= 0; i--) {
|
uint32_t central_directory_in_position = 0;
|
||||||
uint32_t magic = 0;
|
uint32_t central_directory_in_size = 0;
|
||||||
if (!xseekread(i, &magic, sizeof(uint32_t))) return 0;
|
uint32_t central_directory_out_size = 0;
|
||||||
if (magic == MAGIC_CENTRAL_FOOTER) {
|
|
||||||
LOGD("central footer @ %08X\n", i);
|
|
||||||
if (!xseekread(i, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
|
||||||
|
|
||||||
central_header_t central_header;
|
int i;
|
||||||
if (!xseekread(central_footer.central_directory_offset, ¢ral_header, sizeof(central_header_t))) return 0;
|
for (i = size - 4; i >= 0; i--) {
|
||||||
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
|
uint32_t magic = 0;
|
||||||
central_directory_in_position = central_footer.central_directory_offset;
|
if (!xseekread(fin, i, &magic, sizeof(uint32_t))) return 0;
|
||||||
central_directory_in_size = insize - central_footer.central_directory_offset;
|
if (magic == MAGIC_CENTRAL_FOOTER) {
|
||||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
LOGD("central footer @ %08X\n", i);
|
||||||
break;
|
if (!xseekread(fin, i, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
||||||
|
|
||||||
|
central_header_t central_header;
|
||||||
|
if (!xseekread(fin, central_footer.central_directory_offset, ¢ral_header, sizeof(central_header_t))) return 0;
|
||||||
|
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
|
||||||
|
central_directory_in_position = central_footer.central_directory_offset;
|
||||||
|
central_directory_in_size = size - central_footer.central_directory_offset;
|
||||||
|
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (central_directory_in_position == 0) return 0;
|
||||||
|
|
||||||
|
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
|
||||||
|
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
|
||||||
|
if (!xseekread(fin, central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
|
||||||
|
memset(central_directory_out, 0, central_directory_in_size);
|
||||||
|
|
||||||
|
unlink(filenameOut);
|
||||||
|
int fout = open(filenameOut, O_CREAT | O_WRONLY | O_BINARY, 0644);
|
||||||
|
if (fout > 0) {
|
||||||
|
|
||||||
|
uintptr_t central_directory_in_index = 0;
|
||||||
|
uintptr_t central_directory_out_index = 0;
|
||||||
|
|
||||||
|
central_header_t* central_header = NULL;
|
||||||
|
|
||||||
|
uint32_t out_index = 0;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
central_header = (central_header_t*)¢ral_directory_in[central_directory_in_index];
|
||||||
|
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
|
||||||
|
|
||||||
|
filename[central_header->length_filename] = (char)0;
|
||||||
|
memcpy(filename, ¢ral_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
|
||||||
|
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
|
||||||
|
|
||||||
|
local_header_t local_header;
|
||||||
|
if (!xseekread(fin, central_header->offset, &local_header, sizeof(local_header_t))) return 0;
|
||||||
|
|
||||||
|
// save and update to next index before we clobber the data
|
||||||
|
uint16_t compression_method_old = central_header->compression_method;
|
||||||
|
uint32_t size_compressed_old = central_header->size_compressed;
|
||||||
|
uint32_t offset_old = central_header->offset;
|
||||||
|
uint32_t length_extra_old = central_header->length_extra;
|
||||||
|
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
|
||||||
|
|
||||||
|
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
|
||||||
|
central_header->offset = out_index;
|
||||||
|
central_header->flags = central_header->flags & !8;
|
||||||
|
if (decompress && (compression_method_old == 8)) {
|
||||||
|
central_header->compression_method = 0;
|
||||||
|
central_header->size_compressed = central_header->size_uncompressed;
|
||||||
|
}
|
||||||
|
central_header->length_extra = 0;
|
||||||
|
central_header->length_comment = 0;
|
||||||
|
local_header.compression_method = central_header->compression_method;
|
||||||
|
local_header.flags = central_header->flags;
|
||||||
|
local_header.crc32 = central_header->crc32;
|
||||||
|
local_header.size_uncompressed = central_header->size_uncompressed;
|
||||||
|
local_header.size_compressed = central_header->size_compressed;
|
||||||
|
local_header.length_extra = 0;
|
||||||
|
|
||||||
|
if (!xseekwrite(fout, out_index, &local_header, sizeof(local_header_t))) return 0;
|
||||||
|
out_index += sizeof(local_header_t);
|
||||||
|
if (!xseekwrite(fout, out_index, &filename[0], central_header->length_filename)) return 0;
|
||||||
|
out_index += central_header->length_filename;
|
||||||
|
|
||||||
|
if (decompress && (compression_method_old == 8)) {
|
||||||
|
if (!xdecompress(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
||||||
|
} else {
|
||||||
|
if (!xfilecopy(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
||||||
|
}
|
||||||
|
out_index += local_header.size_compressed;
|
||||||
|
|
||||||
|
memcpy(¢ral_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
|
||||||
|
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
central_directory_out_size = central_directory_out_index;
|
||||||
|
central_footer.central_directory_size = central_directory_out_size;
|
||||||
|
central_footer.central_directory_offset = out_index;
|
||||||
|
central_footer.length_comment = 0;
|
||||||
|
if (!xseekwrite(fout, out_index, central_directory_out, central_directory_out_size)) return 0;
|
||||||
|
out_index += central_directory_out_size;
|
||||||
|
if (!xseekwrite(fout, out_index, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
||||||
|
|
||||||
|
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
||||||
|
LOGD("central footer @ %08X\n", out_index);
|
||||||
|
|
||||||
|
close(fout);
|
||||||
|
ok = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(central_directory_in);
|
||||||
|
free(central_directory_out);
|
||||||
|
close(fin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (central_directory_in_position == 0) return 0;
|
|
||||||
|
|
||||||
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
|
|
||||||
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
|
|
||||||
if (!xseekread(central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
|
|
||||||
memset(central_directory_out, 0, central_directory_in_size);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fout = (unsigned char*) malloc(insize);
|
|
||||||
alloc = insize;
|
|
||||||
|
|
||||||
uintptr_t central_directory_in_index = 0;
|
|
||||||
uintptr_t central_directory_out_index = 0;
|
|
||||||
|
|
||||||
central_header_t* central_header = NULL;
|
|
||||||
|
|
||||||
uint32_t out_index = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
central_header = (central_header_t*)¢ral_directory_in[central_directory_in_index];
|
|
||||||
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
|
|
||||||
|
|
||||||
filename[central_header->length_filename] = (char)0;
|
|
||||||
memcpy(filename, ¢ral_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
|
|
||||||
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
|
|
||||||
|
|
||||||
local_header_t local_header;
|
|
||||||
if (!xseekread(central_header->offset, &local_header, sizeof(local_header_t))) return 0;
|
|
||||||
|
|
||||||
// save and update to next index before we clobber the data
|
|
||||||
uint16_t compression_method_old = central_header->compression_method;
|
|
||||||
uint32_t size_compressed_old = central_header->size_compressed;
|
|
||||||
uint32_t offset_old = central_header->offset;
|
|
||||||
uint32_t length_extra_old = central_header->length_extra;
|
|
||||||
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
|
|
||||||
|
|
||||||
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
|
|
||||||
central_header->offset = out_index;
|
|
||||||
central_header->flags = central_header->flags & !8;
|
|
||||||
if (decompress && (compression_method_old == 8)) {
|
|
||||||
central_header->compression_method = 0;
|
|
||||||
central_header->size_compressed = central_header->size_uncompressed;
|
|
||||||
}
|
|
||||||
central_header->length_extra = 0;
|
|
||||||
central_header->length_comment = 0;
|
|
||||||
local_header.compression_method = central_header->compression_method;
|
|
||||||
local_header.flags = central_header->flags;
|
|
||||||
local_header.crc32 = central_header->crc32;
|
|
||||||
local_header.size_uncompressed = central_header->size_uncompressed;
|
|
||||||
local_header.size_compressed = central_header->size_compressed;
|
|
||||||
local_header.length_extra = 0;
|
|
||||||
|
|
||||||
if (!xseekwrite(out_index, &local_header, sizeof(local_header_t))) return 0;
|
|
||||||
out_index += sizeof(local_header_t);
|
|
||||||
if (!xseekwrite(out_index, &filename[0], central_header->length_filename)) return 0;
|
|
||||||
out_index += central_header->length_filename;
|
|
||||||
|
|
||||||
if (decompress && (compression_method_old == 8)) {
|
|
||||||
if (!xdecompress(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
|
||||||
} else {
|
|
||||||
if (!xfilecopy(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
|
|
||||||
}
|
|
||||||
out_index += local_header.size_compressed;
|
|
||||||
|
|
||||||
memcpy(¢ral_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
|
|
||||||
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
central_directory_out_size = central_directory_out_index;
|
|
||||||
central_footer.central_directory_size = central_directory_out_size;
|
|
||||||
central_footer.central_directory_offset = out_index;
|
|
||||||
central_footer.length_comment = 0;
|
|
||||||
if (!xseekwrite(out_index, central_directory_out, central_directory_out_size)) return 0;
|
|
||||||
out_index += central_directory_out_size;
|
|
||||||
if (!xseekwrite(out_index, ¢ral_footer, sizeof(central_footer_t))) return 0;
|
|
||||||
|
|
||||||
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
|
|
||||||
LOGD("central footer @ %08X\n", out_index);
|
|
||||||
|
|
||||||
ok = 1;
|
|
||||||
|
|
||||||
free(central_directory_in);
|
|
||||||
free(central_directory_out);
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,7 @@
|
|||||||
|
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
|
||||||
int zipadjust(int decompress);
|
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress);
|
||||||
|
|
||||||
extern size_t insize, outsize, alloc;
|
|
||||||
extern unsigned char *fin, *fout;
|
|
||||||
|
|
||||||
#define LOG_TAG "zipadjust"
|
#define LOG_TAG "zipadjust"
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
android:fillColor="#000000"
|
android:fillColor="#000000"
|
||||||
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
|
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
|
||||||
<path
|
<path
|
||||||
android:pathData="M0-.75h24v24H0z" />
|
android:pathData="M0-0.75h24v24H0z" />
|
||||||
</vector>
|
</vector>
|
||||||
56
app/src/main/res/layout/activity_flash.xml
Normal file
56
app/src/main/res/layout/activity_flash.xml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@android:color/black"
|
||||||
|
tools:context="com.topjohnwu.magisk.FlashActivity">
|
||||||
|
|
||||||
|
<include layout="@layout/toolbar"/>
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/flash_logs"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layoutManager="android.support.v7.widget.LinearLayoutManager">
|
||||||
|
|
||||||
|
</android.support.v7.widget.RecyclerView>
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/button_panel"
|
||||||
|
style="?android:buttonStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@android:color/darker_gray"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/no_thanks"
|
||||||
|
style="?android:borderlessButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/close" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/reboot"
|
||||||
|
style="?android:borderlessButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/reboot" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/install_info_card"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="5dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/install_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/current_version_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:maxWidth="400dp"
|
|
||||||
android:minWidth="400dp">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/bootimage_card"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginBottom="15dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="@string/boot_image_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginEnd="15dp"
|
|
||||||
android:layout_marginStart="15dp">
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/block_spinner"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:text="@string/detect_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/detect_bootimage"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/install_option_card"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent">
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginBottom="15dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="@string/advanced_settings_title"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:text="@string/keep_force_encryption"
|
|
||||||
android:id="@+id/keep_force_enc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:text="@string/keep_dm_verity"
|
|
||||||
android:id="@+id/keep_verity"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/install_button"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:clickable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginBottom="5dp"
|
|
||||||
android:layout_marginEnd="25dp"
|
|
||||||
android:layout_marginStart="25dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:srcCompat="@mipmap/ic_launcher"
|
|
||||||
android:id="@+id/imageView"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:layout_marginEnd="20dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/install_text"
|
|
||||||
android:ems="10"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:fontFamily="sans-serif" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:layout_marginStart="20dp">
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/uninstall_button"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:clickable="true">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/uninstall"
|
|
||||||
android:ems="10"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_marginBottom="10dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:fontFamily="sans-serif" />
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
466
app/src/main/res/layout/fragment_magisk.xml
Normal file
466
app/src/main/res/layout/fragment_magisk.xml
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v4.widget.SwipeRefreshLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/magisk_update_card"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/magisk_update_icon"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_margin="15dp"
|
||||||
|
android:layout_toStartOf="@+id/magisk_update_status"
|
||||||
|
android:src="@drawable/ic_refresh"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/magisk_update_progress"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_margin="15dp"
|
||||||
|
android:layout_toStartOf="@+id/magisk_update_status" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/magisk_update_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="225dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:text="@string/checking_for_updates"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/magisk_status_icon"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="15dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_toStartOf="@+id/magisk_version" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/magisk_version"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="225dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/root_status_icon"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="15dp"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_toStartOf="@+id/root_status" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/root_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="225dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/safetyNet_card"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/safetyNet_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/safetyNet_refresh"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_margin="15dp"
|
||||||
|
android:layout_toStartOf="@+id/safetyNet_status"
|
||||||
|
android:src="@drawable/ic_refresh"
|
||||||
|
android:tint="?attr/imageColorTint" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/safetyNet_check_progress"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_margin="15dp"
|
||||||
|
android:layout_toStartOf="@+id/safetyNet_status"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/safetyNet_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="175dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:text="@string/safetyNet_check_text"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/expand_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
android:paddingStart="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/cts_status_icon"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="5dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cts_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:minWidth="150dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/basic_status_icon"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_margin="10dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/basic_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:minWidth="150dp"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/bootimage_card"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:text="@string/boot_image_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:minHeight="35dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/block_spinner"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/detect_bootimage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/detect_button"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/install_option_card"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:text="@string/advanced_settings_title"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/keep_force_enc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="50dp"
|
||||||
|
android:layout_marginStart="50dp"
|
||||||
|
android:text="@string/keep_force_encryption" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/keep_verity"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="50dp"
|
||||||
|
android:layout_marginStart="50dp"
|
||||||
|
android:text="@string/keep_dm_verity" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/install_button"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
app:srcCompat="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/install_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ems="10"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/install"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_weight="0">
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
android:id="@+id/uninstall_button"
|
||||||
|
style="?attr/cardStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/uninstall"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</android.support.v4.widget.SwipeRefreshLayout>
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.v4.widget.SwipeRefreshLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginTop="?attr/actionBarSize"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/magiskStatusView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/magisk_version"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/magisk_status_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/magisk_status_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/magisk_check_updates_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/magisk_update_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/checking_for_updates" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/rootStatusView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/root_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/root_status_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/root_status_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/root_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
<android.support.v7.widget.CardView
|
|
||||||
android:id="@+id/safetyNetView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginLeft="5dip"
|
|
||||||
android:layout_marginRight="5dip"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
style="?attr/cardStyle"
|
|
||||||
app:cardUseCompatPadding="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/safetyNet_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:foregroundGravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/grey500">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/safetyNet_icon"
|
|
||||||
android:layout_width="84dp"
|
|
||||||
android:layout_height="84dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_safetynet"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/safetyNet_check_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/safetyNet_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="6dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/safetyNet_check_text" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</android.support.v4.widget.SwipeRefreshLayout>
|
|
||||||
@@ -56,9 +56,9 @@
|
|||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:marqueeRepeatLimit="marquee_forever"
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingEnd="3dp"
|
android:paddingEnd="3dp"
|
||||||
android:paddingStart="3dp"/>
|
android:paddingStart="3dp"
|
||||||
|
android:singleLine="true"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
9
app/src/main/res/layout/list_item_flashlog.xml
Normal file
9
app/src/main/res/layout/list_item_flashlog.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="10sp" />
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user