Compare commits

...

133 Commits

Author SHA1 Message Date
topjohnwu
0a0ad9a184 Bump to 4.3.1 2017-03-31 13:17:58 +08:00
topjohnwu
234bead59e Bump version 2017-03-31 06:58:47 +08:00
Primokorn
76de310986 Create french strings.xml
Hope it's not too late for the update :)
2017-03-31 03:23:23 +08:00
topjohnwu
817f050bcd Say goodbye to old modules 2017-03-30 06:52:18 +08:00
topjohnwu
60ae685d1e Change disable to Core Only Mode 2017-03-30 05:16:50 +08:00
Wang Han
4c7bdbb284 Fix crashing when selecting release notes on some devices 2017-03-26 23:55:11 +08:00
topjohnwu
435251ca41 Bump version 2017-03-20 06:24:59 +08:00
topjohnwu
324a0dd38f Update uninstall script 2017-03-20 04:17:04 +08:00
topjohnwu
cc77d93918 Fix string.xml(vi) 2017-03-20 03:38:24 +08:00
Nguyễn Thanh Tài
0ea7d8bd8c Added Vietnamese translation 2017-03-20 03:12:03 +08:00
topjohnwu
849b217143 Fix build issues 2017-03-16 14:08:40 +08:00
Fabio
9af6efba59 Update Italian Translation [2/2] 2017-03-16 13:40:52 +08:00
Fatih Fırıncı
079d6f06ef Added turkish language
Please merge it
2017-03-16 13:40:43 +08:00
gargamelek
9cf0757689 Added czech translation 2017-03-16 13:40:30 +08:00
c727
b54c438948 update strings-de 2017-03-16 13:40:10 +08:00
linar10
c3ff4bfdad Update strings pl 2017-03-16 13:39:49 +08:00
topjohnwu
5d62e066e2 Bump version 2017-02-22 05:06:19 +08:00
topjohnwu
e94219c5a3 Add notification settings 2017-02-22 04:58:03 +08:00
topjohnwu
8ed9634adf Fix Samsung crash 2017-02-22 04:13:21 +08:00
topjohnwu
0aefa9599f Version bump 2017-02-21 03:52:35 +08:00
c727
e279cf0575 update strings-de 2017-02-20 13:40:01 -06:00
topjohnwu
a3f0ef8e77 Many improvements and bug fixes
Close #114
2017-02-21 03:38:37 +08:00
topjohnwu
8eba05ed4a Potentially fix Samsung crash and change colors 2017-02-20 20:11:07 +08:00
topjohnwu
2f78155723 Bump version 2017-02-19 10:49:47 +08:00
topjohnwu
6785221479 Small refinements and bugfixes
Close #109
2017-02-19 10:14:29 +08:00
topjohnwu
9bc410dd3d Add MarkDown styles 2017-02-18 04:35:51 +08:00
gh2923
2491ab6bf9 Update Simplified Chinese Translation 2017-02-17 10:56:44 -06:00
topjohnwu
f615ed40cd Several refinements 2017-02-17 14:07:15 +08:00
linar10
430f2cafc1 Update strings.xml 2017-02-16 23:27:51 -06:00
Deiki-kun
0ad049da88 Updated and corrected Spanish strings.xml 2017-02-16 23:27:39 -06:00
c727
2c7691567b Update strings-de 2017-02-16 23:27:22 -06:00
topjohnwu
1d70d0fe94 Don't show notification again if coming from notification 2017-02-17 09:26:27 +08:00
topjohnwu
ac44f05811 Resource cleanup 2017-02-17 09:03:40 +08:00
topjohnwu
d99252f394 Add update notification 2017-02-17 08:51:51 +08:00
topjohnwu
b58c7ba7c5 Add download button to repo, close #99 2017-02-16 17:50:36 +08:00
topjohnwu
8c5acd1a0a Add traditional Chinese 2017-02-16 17:09:11 +08:00
linar10
b9b1ebf18c Update strings.xml 2017-02-16 01:44:37 -06:00
lilymaniac
8ca132cef0 Add Korean translation
Change-Id: Ie5b9ee02dc179c99b1ff5c50e5ce046cc2f2522e
Signed-off-by: lilymaniac <lilymaniac@outlook.com>
2017-02-16 01:43:46 -06:00
topjohnwu
a03bb90754 Use README.md in details for repo 2017-02-16 05:48:26 +08:00
topjohnwu
d1c939f48a Use temporary files to process zips
Fix #96
2017-02-15 23:46:50 +08:00
gh2923
21b11f1b48 Update Simplified Chinese Translation 2017-02-15 08:44:45 +08:00
topjohnwu
23c84a7803 Massive Zip flashing refactoring 2017-02-15 05:25:24 +08:00
topjohnwu
f9ab060403 Fix su request crashing 2017-02-15 05:07:14 +08:00
topjohnwu
df7a5bf149 Redo styling 2017-02-14 16:35:03 +08:00
topjohnwu
c4afa069df Add custom AlertDialog 2017-02-13 23:11:50 +08:00
topjohnwu
1bfafdb44f Don't reload ApplicationInfo list
Fix #94
2017-02-13 04:00:45 +08:00
topjohnwu
1ef5bd7076 Remove URL in resources 2017-02-13 03:16:39 +08:00
linar10
29176fa4f4 Update strings-pl 2017-02-13 03:14:24 +08:00
topjohnwu
958c95732b Move AboutCardRow to components package 2017-02-13 03:13:24 +08:00
topjohnwu
44b0d4127c Remove GSON and switch to database 2017-02-12 23:27:20 +08:00
topjohnwu
1418ec2416 Remove module helper 2017-02-12 20:53:41 +08:00
topjohnwu
b51978f51c Move asynctasks to seperate package 2017-02-12 19:49:46 +08:00
topjohnwu
b07361580a Contexts are different: Make context clearer 2017-02-12 05:02:18 +08:00
topjohnwu
d1b5ebad7d Several fixes 2017-02-07 07:32:40 +08:00
Exalm
f4ce813de9 Better icon 2017-02-07 06:17:54 +08:00
drbeat
b44ac994d8 fix typos and translate new strings 2017-02-07 06:16:53 +08:00
Exalm
333948814c Russian translation 2017-02-07 06:16:14 +08:00
linar10
1a51ad6e01 Update strings - pl 2017-02-07 06:15:52 +08:00
topjohnwu
22a5c11f0d Fix MagiskHide startup issue 2017-02-07 06:02:06 +08:00
topjohnwu
51b22d1ad4 Make callback events non-static 2017-02-07 04:09:49 +08:00
topjohnwu
bef5969580 No more static crap :) 2017-02-07 02:01:32 +08:00
topjohnwu
c6bf7bb9cd Bump version 2017-02-06 08:34:55 +08:00
linar10
2a84d92cbf Update strings.xml 2017-02-06 08:32:20 +08:00
linar10
62de36b0da Update strings - pl last changes 2017-02-06 08:32:20 +08:00
c727
03a9aaeff7 strings-de latest changes for release 2017-02-06 08:32:07 +08:00
topjohnwu
45765e292d Final fixes 2017-02-06 08:16:48 +08:00
topjohnwu
6e28a26015 Add uninstall button 2017-02-06 03:20:17 +08:00
topjohnwu
9150bf720d Add info for MagiskHide when not using MagiskSU
Close #63
2017-02-06 03:20:16 +08:00
topjohnwu
845864679c Allow multi lines
Fix #53
2017-02-05 22:05:44 +08:00
topjohnwu
b3b2149ebb Optimize root shell and startups 2017-02-05 22:02:14 +08:00
topjohnwu
0886dca385 string.xml update 2017-02-05 04:46:59 +08:00
c727
53198ba4a7 update for strings-de 2017-02-05 04:42:31 +08:00
killer7mod
a9652ee1fd update strings.xml PT 2017-02-05 04:42:22 +08:00
gh2923
75caf2f01c Update Simplified Chinese Translation 2017-02-05 04:42:04 +08:00
linar10
65bab2666e Update strings.xml -PL 2017-02-05 04:41:54 +08:00
Fabio
6d93ae399a Update italian Translation [1/2] 2017-02-05 04:41:41 +08:00
topjohnwu
7239c2e31a Update to the latest settings 2017-02-05 04:40:52 +08:00
topjohnwu
43b7ef8110 Add disable, change busybox 2017-02-02 19:19:22 +08:00
topjohnwu
99ef0b8cb4 Handle MagiskHide at boot 2017-02-01 23:54:32 +08:00
topjohnwu
0efb4da0ee Several bug fixes
Fix #57
2017-01-31 03:39:24 +08:00
linar10
ed7920d61e Added missing entries for strings-pl 2017-01-30 20:12:53 +08:00
c727
c0379c8e25 update strings-de to "Add Superuser settings" 2017-01-30 20:11:56 +08:00
tonymanou
00a0e64fdd Prefer List/Map/Set as declaring type over their implementations
Unless your are using a method declared in subclasses of an
interface, it is better to use the interface as declaring type.
One advantage of this is that changing used implementation will
be much simpler (you will have less declarations to edit).
2017-01-30 20:11:17 +08:00
tonymanou
0dc60debea Fix warning about use of API limited to support package 2017-01-30 20:11:17 +08:00
tonymanou
c44ae5888c Optimize map operations 2017-01-30 20:11:17 +08:00
topjohnwu
b9495cd1bb Improve static data management 2017-01-30 20:04:49 +08:00
topjohnwu
bfec381933 Improve su requests 2017-01-30 19:27:00 +08:00
topjohnwu
2dddb8df69 Reset menu every transaction 2017-01-30 01:51:55 +08:00
topjohnwu
d30397e9c0 Let users know why blacklist PoGO and AP... 2017-01-30 01:40:51 +08:00
topjohnwu
d9597549fd Prevent excessive su requests 2017-01-30 00:44:33 +08:00
topjohnwu
13512b4146 Add BootReceiver 2017-01-29 16:52:43 +08:00
topjohnwu
49e546919a Update logs 2017-01-29 16:20:41 +08:00
topjohnwu
586015c2ed Fix ButterKnife issue
https://code.google.com/p/android/issues/detail?id=231597
2017-01-29 10:27:06 +08:00
topjohnwu
4a7e067d1a Use support library 2017-01-29 00:20:43 +08:00
topjohnwu
9bc0b7f183 Update settings 2017-01-28 22:02:33 +08:00
topjohnwu
cd4dfc9861 Add Superuser settings 2017-01-28 06:13:07 +08:00
topjohnwu
09bdbc1224 Revert "Read only the first line instead of loading the whole file"
This reverts commit a5b573eaaa.

The file shall always have one single line, no need to create a new method
2017-01-28 01:25:51 +08:00
tonymanou
978b3a64c5 Remove context reference from recyclerview adapter 2017-01-28 01:25:15 +08:00
tonymanou
651547ef20 Fix raw use of generics warnings 2017-01-28 01:25:15 +08:00
tonymanou
b4d95977d0 Remove redundant XML namespaces 2017-01-28 01:25:15 +08:00
tonymanou
5d8bb897db Separate JNI glue from actual C code, move CMakeLists file 2017-01-28 01:25:15 +08:00
tonymanou
84c8ecb372 Slight improvement for the navigation drawer 2017-01-28 01:25:15 +08:00
tonymanou
61abe5b948 Do not close the whole application in case of error 2017-01-28 01:25:15 +08:00
tonymanou
a5b573eaaa Read only the first line instead of loading the whole file 2017-01-28 01:25:15 +08:00
topjohnwu
cbb32f82eb Add Superuser logging UI 2017-01-28 01:13:28 +08:00
topjohnwu
ca9334b2df Add tabs to log fragment 2017-01-27 03:43:37 +08:00
topjohnwu
959ed7f866 Implement logging and bug fixes 2017-01-27 01:02:40 +08:00
c727
a5c0411be0 update strings-de 2017-01-26 14:28:27 +08:00
linar10
32e1303742 Add Polish translate 2017-01-26 14:28:04 +08:00
topjohnwu
7263b6fe89 Handle bootblock detect failure cases 2017-01-26 14:25:12 +08:00
topjohnwu
46a4070f84 Prevent shell response crashes 2017-01-26 13:46:54 +08:00
topjohnwu
c3c155a1ed Improved settings 2017-01-26 04:17:51 +08:00
topjohnwu
b067105660 Fix bug where no info is available 2017-01-26 03:45:05 +08:00
topjohnwu
15ca18848e Add su revoke 2017-01-26 03:30:12 +08:00
topjohnwu
67c9e2ead6 Add Superuser management UI 2017-01-26 01:13:23 +08:00
topjohnwu
3681177be4 Rename fragment layouts 2017-01-25 17:07:23 +08:00
topjohnwu
6eb814ef0b Fix some small issues 2017-01-25 16:45:55 +08:00
topjohnwu
bcc695234c Seperate Configs 2017-01-25 13:17:33 +08:00
topjohnwu
ad16a6fc1b Project restructure 2017-01-25 04:33:22 +08:00
topjohnwu
478b7eeb65 Stop countdown when user reacts 2017-01-25 02:16:36 +08:00
topjohnwu
151a153dc9 Fix toasts and timeouts 2017-01-25 01:23:41 +08:00
topjohnwu
ad131854ca Update request popup UI 2017-01-25 01:01:12 +08:00
topjohnwu
0bd0eb9e59 Magisk Manager is now a SU client
1. Add request popup
2. Add su request notifications
3. Add su database helpers
2017-01-24 14:19:28 +08:00
c727
cf16fd0104 update strings-de for Magisk Manager 3.1 2017-01-15 02:37:58 +08:00
tonymanou
21b00ac6ca Use try-with-resources in some places 2017-01-15 02:37:40 +08:00
tonymanou
57e6f3080c Fix generic type 2017-01-15 02:37:40 +08:00
tonymanou
89744100ce Remove unnecessary Butterknife binding in adapters 2017-01-15 02:37:40 +08:00
tonymanou
a718f9bbfd Unbind Butterknife-injected views in fragment's onDestroyView() 2017-01-15 02:37:40 +08:00
tonymanou
e81bc4f044 Clean up main activity code
No need to catch IllegalStateException as we display the fragment from
onCreate() without delay.
2017-01-15 02:37:40 +08:00
tonymanou
4dbacd79ae Matching event [un]registering, call super at the end of onPause/onDestroy
Event unregistered in onDestroy() should be registered in onCreate() to
avoid being registered multiple times.
2017-01-15 02:37:40 +08:00
tonymanou
ae74d54451 Events should be final in order to work 2017-01-15 02:37:40 +08:00
tonymanou
dc316c5669 Set fragment title and [un]register callbacks in onStart/onStop
onStart() is called when the fragment is made visible, whereas onPause()
is called when the fragment looses focus e.g. if a dialog is shown.
Thus:
- there is no need to set the activity's title everytime the fragment
regains focus,
- it is better to listen to event tasks and refresh the state of the UI
while the fragment is actually visible, listening to events until the
fragment is destroyed is useless: if an event is received between
onStop() and onDestroy(), there will be some processing but nothing will
be shown because the fragment is no longer visible.
2017-01-15 02:37:40 +08:00
tonymanou
e9f04256c9 setHasOptionsMenu() should be called from fragment's onCreate() 2017-01-15 02:37:40 +08:00
141 changed files with 9069 additions and 3077 deletions

View File

@@ -1,5 +0,0 @@
cmake_minimum_required(VERSION 3.6)
add_library(zipadjust SHARED src/main/jni/zipadjust.c)
find_library(libz z)
find_library(liblog log)
target_link_libraries(zipadjust ${libz} ${liblog})

View File

@@ -8,10 +8,11 @@ android {
applicationId "com.topjohnwu.magisk"
minSdkVersion 21
targetSdkVersion 25
versionCode 12
versionName "3.1"
versionCode 28
versionName "4.3.1"
jackOptions {
enabled true
jackInProcess true
}
ndk {
moduleName 'zipadjust'
@@ -19,10 +20,6 @@ android {
}
}
compileOptions {
incremental false
}
buildTypes {
release {
minifyEnabled true
@@ -34,11 +31,11 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
dexOptions {
preDexLibraries = false
preDexLibraries = true
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
path 'src/main/jni/CMakeLists.txt'
}
}
}
@@ -49,16 +46,19 @@ repositories {
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.android.support:cardview-v7:25.1.0'
compile 'com.android.support:design:25.1.0'
compile 'com.jakewharton:butterknife:8.4.0'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.jakewharton:butterknife:8.5.1'
compile 'com.github.clans:fab:1.6.4'
compile 'com.thoughtbot:expandablerecyclerview:1.4'
compile 'us.feras.mdv:markdownview:1.1.0'
compile 'com.madgag.spongycastle:core:1.54.0.0'
compile 'com.madgag.spongycastle:prov:1.54.0.0'
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.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}

View File

@@ -16,27 +16,6 @@
# public *;
#}
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.topjohnwu.magisk.module.** { *; }
-keep class com.topjohnwu.magisk.utils.ModuleHelper$ValueSortedMap { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keep class android.support.v7.internal.** { *; }
-keep interface android.support.v7.internal.** { *; }
-keep class android.support.v7.** { *; }

View File

@@ -8,11 +8,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".MagiskManager"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -41,6 +40,33 @@
<activity
android:name=".SettingsActivity"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".superuser.RequestActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity="internal.superuser"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".superuser.SuRequestActivity"
android:excludeFromRecents="true"
android:taskAffinity="internal.superuser"
android:theme="@style/SuRequest" />
<receiver
android:name=".superuser.SuReceiver" />
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name=".services.BootupIntentService" />
<service
android:name=".services.UpdateCheckService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
<provider
android:name="android.support.v4.content.FileProvider"

View File

@@ -0,0 +1,276 @@
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: #303030;
color: white;
padding: 15px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #888888;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0; }
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
ul :last-child, ol :last-child {
margin-bottom: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #404040;
padding: 0 15px;
color: #888888; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0; }
table tr {
border-top: 1px solid #707070;
background-color: #303030;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #505050; }
table tr th {
font-weight: bold;
border: 1px solid #707070;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #707070;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #cccccc;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #707070;
background-color: #606060;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #3f3f3f;
border: 1px solid #707070;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #606060;
border: 1px solid #707070;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }

View File

@@ -0,0 +1,277 @@
body {
font-family: Helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.6;
padding-top: 10px;
padding-bottom: 10px;
background-color: white;
padding: 15px; }
body > *:first-child {
margin-top: 0 !important; }
body > *:last-child {
margin-bottom: 0 !important; }
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0; }
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
ul :last-child, ol :last-child {
margin-bottom: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }

View File

@@ -0,0 +1,133 @@
[ -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.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -1,13 +1,12 @@
package com.topjohnwu.magisk;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.Spanned;
@@ -17,8 +16,10 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import com.topjohnwu.magisk.components.AboutCardRow;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import java.io.IOException;
import java.io.InputStream;
@@ -26,11 +27,12 @@ import java.io.InputStream;
import butterknife.BindView;
import butterknife.ButterKnife;
public class AboutActivity extends AppCompatActivity {
public class AboutActivity extends Activity {
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
private static final String DONATION_URL = "http://topjohnwu.github.io/donate";
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";
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.app_version_info) AboutCardRow appVersionInfo;
@BindView(R.id.app_changelog) AboutCardRow appChangelog;
@@ -45,8 +47,8 @@ public class AboutActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
Logger.dev("AboutActivity: Theme is " + theme);
if (Utils.isDarkTheme) {
setTheme(R.style.AppTheme_dh);
if (getApplicationContext().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
}
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
@@ -63,13 +65,11 @@ public class AboutActivity extends AppCompatActivity {
appVersionInfo.setSummary(BuildConfig.VERSION_NAME);
String changes = null;
try {
InputStream is = getAssets().open("changelog.html");
try (InputStream is = getAssets().open("changelog.html")) {
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
changes = new String(buffer);
} catch (IOException ignored) {
@@ -86,7 +86,7 @@ public class AboutActivity extends AppCompatActivity {
result = Html.fromHtml(changes);
}
appChangelog.setOnClickListener(v -> {
AlertDialog d = Utils.getAlertDialogBuilder(this)
AlertDialog d = new AlertDialogBuilder(this)
.setTitle(R.string.app_changelog)
.setMessage(result)
.setPositiveButton(android.R.string.ok, null)
@@ -105,7 +105,7 @@ public class AboutActivity extends AppCompatActivity {
} else {
result = Html.fromHtml(getString(R.string.app_developers_));
}
AlertDialog d = Utils.getAlertDialogBuilder(this)
AlertDialog d = new AlertDialogBuilder(this)
.setTitle(R.string.app_developers)
.setMessage(result)
.setPositiveButton(android.R.string.ok, null)

View File

@@ -1,10 +1,12 @@
package com.topjohnwu.magisk;
import android.app.Fragment;
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;
@@ -15,98 +17,212 @@ import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.receivers.MagiskDlReceiver;
import com.topjohnwu.magisk.utils.CallbackHandler;
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 CallbackHandler.EventListener {
public class InstallFragment extends Fragment implements CallbackEvent.Listener<Void> {
public static final CallbackHandler.Event blockDetectionDone = new CallbackHandler.Event();
public static List<String> blockList;
public static String bootBlock = null;
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.flash_button) CardView flashButton;
@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;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.install_fragment, container, false);
ButterKnife.bind(this, v);
detectButton.setOnClickListener(v1 -> toAutoDetect());
currentVersionTitle.setText(getString(R.string.current_magisk_title, StatusFragment.magiskVersionString));
installTitle.setText(getString(R.string.install_magisk_title, StatusFragment.remoteMagiskVersion));
flashButton.setOnClickListener(v1 -> {
String bootImage = bootBlock;
if (bootImage == null) {
bootImage = blockList.get(spinner.getSelectedItemPosition() - 1);
}
String filename = "Magisk-v" + StatusFragment.remoteMagiskVersion + ".zip";
String finalBootImage = bootImage;
Utils.getAlertDialogBuilder(getActivity())
.setTitle(getString(R.string.repo_install_title, getString(R.string.magisk)))
.setMessage(getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive(
getActivity(),
new MagiskDlReceiver(finalBootImage, keepEncChkbox.isChecked(), keepVerityChkbox.isChecked()),
StatusFragment.magiskLink,
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.check_release_notes, (dialog, which) -> {
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(StatusFragment.releaseNoteLink)));
})
.setNegativeButton(R.string.no_thanks, null)
.show();
});
if (blockDetectionDone.isTriggered) {
updateUI();
}
return v;
}
@Override
public void onTrigger(CallbackHandler.Event event) {
updateUI();
}
private void updateUI() {
List<String> items = new ArrayList<>(blockList);
items.add(0, getString(R.string.auto_detect, bootBlock));
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();
}
private void toAutoDetect() {
if (bootBlock != null) {
@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 void onResume() {
super.onResume();
getActivity().setTitle(R.string.install);
CallbackHandler.register(blockDetectionDone, this);
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 onDestroy() {
super.onDestroy();
CallbackHandler.unRegister(blockDetectionDone, this);
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();
}
}

View File

@@ -1,236 +1,53 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Fragment;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
import com.topjohnwu.magisk.components.Fragment;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class LogFragment extends Fragment {
private static final String MAGISK_LOG = "/cache/magisk.log";
private Unbinder unbinder;
@BindView(R.id.txtLog) TextView txtLog;
@BindView(R.id.svLog) ScrollView svLog;
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
@BindView(R.id.progressBar) ProgressBar progressBar;
private MenuItem mClickedMenuItem;
@BindView(R.id.container) ViewPager viewPager;
@BindView(R.id.tab) TabLayout tab;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.log_fragment, container, false);
ButterKnife.bind(this, view);
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_log, container, false);
unbinder = ButterKnife.bind(this, v);
txtLog.setTextIsSelectable(true);
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
new LogManager().read();
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
return view;
if (getApplication().isSuClient) {
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
tab.setupWithViewPager(viewPager);
tab.setVisibility(View.VISIBLE);
}
viewPager.setAdapter(adapter);
return v;
}
@Override
public void onResume() {
super.onResume();
setHasOptionsMenu(true);
new LogManager().read();
getActivity().setTitle(R.string.log);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_log, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
mClickedMenuItem = item;
switch (item.getItemId()) {
case R.id.menu_refresh:
new LogManager().read();
return true;
case R.id.menu_send:
new LogManager().send();
return true;
case R.id.menu_save:
new LogManager().save();
return true;
case R.id.menu_clear:
new LogManager().clear();
return true;
default:
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mClickedMenuItem != null) {
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500);
}
} else {
Snackbar.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
}
}
}
public class LogManager extends Async.RootTask<Object, Void, Object> {
int mode;
File targetFile;
@SuppressLint("DefaultLocale")
@Override
protected Object doInBackground(Object... params) {
mode = (int) params[0];
switch (mode) {
case 0:
List<String> logList = Utils.readFile(MAGISK_LOG);
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
for (String s : logList) {
llog.append(s).append("\n");
}
return llog.toString();
case 1:
Shell.su("echo > " + MAGISK_LOG);
Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
return "";
case 2:
case 3:
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();
String filename = String.format(
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
targetFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MagiskManager/" + filename);
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|| (targetFile.exists() && !targetFile.delete()))
return false;
List<String> in = Utils.readFile(MAGISK_LOG);
try {
FileWriter out = new FileWriter(targetFile);
for (String line : in) {
out.write(line + "\n");
}
out.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return null;
}
@Override
protected void onPostExecute(Object o) {
boolean bool;
String llog;
switch (mode) {
case 0:
case 1:
llog = (String) o;
progressBar.setVisibility(View.GONE);
if (llog.length() == 0)
txtLog.setText(R.string.log_is_empty);
else
txtLog.setText(llog);
svLog.post(() -> svLog.scrollTo(0, txtLog.getHeight()));
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
break;
case 2:
bool = (boolean) o;
if (bool)
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
else
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
break;
case 3:
bool = (boolean) o;
if (bool) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(targetFile));
sendIntent.setType("application/html");
startActivity(Intent.createChooser(sendIntent, getResources().getString(R.string.menuSend)));
} else {
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
}
}
}
public void read() {
exec(0);
}
public void clear() {
exec(1);
}
public void save() {
exec(2);
}
public void send() {
exec(3);
}
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -16,46 +15,42 @@ import android.view.ViewGroup;
import android.widget.SearchView;
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.CallbackHandler;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import java.util.Arrays;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class MagiskHideFragment extends Fragment implements CallbackHandler.EventListener {
public class MagiskHideFragment extends Fragment implements CallbackEvent.Listener<Void> {
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
// Don't show in list...
public static final List<String> BLACKLIST = Arrays.asList(
"android",
"com.topjohnwu.magisk",
"com.google.android.gms",
"com.google.android.apps.walletnfcrel",
"com.nianticlabs.pokemongo"
);
public static CallbackHandler.Event packageLoadDone = new CallbackHandler.Event();
private ApplicationAdapter appAdapter;
private SearchView.OnQueryTextListener searchListener;
private String lastFilter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.magisk_hide_fragment, container, false);
ButterKnife.bind(this, view);
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
unbinder = ButterKnife.bind(this, view);
PackageManager packageManager = getActivity().getPackageManager();
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setOnRefreshListener(() -> new Async.LoadApps(packageManager).exec());
mSwipeRefreshLayout.setOnRefreshListener(() -> new MagiskHide(getActivity()).list());
appAdapter = new ApplicationAdapter(packageManager);
recyclerView.setAdapter(appAdapter);
@@ -76,6 +71,10 @@ public class MagiskHideFragment extends Fragment implements CallbackHandler.Even
}
};
if (getApplication().magiskHideDone.isTriggered) {
onTrigger(getApplication().magiskHideDone);
}
return view;
}
@@ -87,27 +86,28 @@ public class MagiskHideFragment extends Fragment implements CallbackHandler.Even
}
@Override
public void onResume() {
super.onResume();
setHasOptionsMenu(true);
public void onStart() {
super.onStart();
getActivity().setTitle(R.string.magiskhide);
CallbackHandler.register(packageLoadDone, this);
if (packageLoadDone.isTriggered) {
onTrigger(packageLoadDone);
}
getApplication().magiskHideDone.register(this);
}
@Override
public void onPause() {
super.onPause();
CallbackHandler.unRegister(packageLoadDone, this);
public void onStop() {
getApplication().magiskHideDone.unRegister(this);
super.onStop();
}
@Override
public void onTrigger(CallbackHandler.Event event) {
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("MagiskHideFragment: UI refresh");
Async.LoadApps.Result result = (Async.LoadApps.Result) event.getResult();
appAdapter.setLists(result.listApps, result.hideList);
appAdapter.setLists(getApplication().appList, getApplication().magiskHideList);
mSwipeRefreshLayout.setRefreshing(false);
if (!TextUtils.isEmpty(lastFilter)) {
appAdapter.filter(lastFilter);

View File

@@ -0,0 +1,241 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.SerialTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class MagiskLogFragment extends Fragment {
private static final String MAGISK_LOG = "/cache/magisk.log";
private Unbinder unbinder;
@BindView(R.id.txtLog) TextView txtLog;
@BindView(R.id.svLog) ScrollView svLog;
@BindView(R.id.hsvLog) HorizontalScrollView hsvLog;
@BindView(R.id.progressBar) ProgressBar progressBar;
private MenuItem mClickedMenuItem;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
unbinder = ButterKnife.bind(this, view);
txtLog.setTextIsSelectable(true);
new LogManager().read();
return view;
}
@Override
public void onStart() {
super.onStart();
getActivity().setTitle(R.string.log);
}
@Override
public void onResume() {
super.onResume();
new LogManager().read();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_log, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
mClickedMenuItem = item;
switch (item.getItemId()) {
case R.id.menu_refresh:
new LogManager().read();
return true;
case R.id.menu_save:
new LogManager().save();
return true;
case R.id.menu_clear:
new LogManager().clear();
return true;
default:
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mClickedMenuItem != null) {
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500);
}
} 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")
@Override
protected Object doInBackground(Object... params) {
mode = (int) params[0];
switch (mode) {
case 0:
List<String> logList = Utils.readFile(MAGISK_LOG);
if (Utils.isValidShellResponse(logList)) {
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
for (String s : logList) {
llog.append(s).append("\n");
}
return llog.toString();
}
return "";
case 1:
Shell.su("echo > " + MAGISK_LOG);
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
return "";
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();
String filename = String.format(
"magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error",
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
targetFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MagiskManager/" + filename);
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|| (targetFile.exists() && !targetFile.delete())) {
return false;
}
List<String> in = Utils.readFile(MAGISK_LOG);
if (Utils.isValidShellResponse(in)) {
try (FileWriter out = new FileWriter(targetFile)) {
for (String line : in)
out.write(line + "\n");
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return false;
}
return null;
}
@Override
protected void onPostExecute(Object o) {
if (o == null) return;
boolean bool;
String llog;
switch (mode) {
case 0:
case 1:
llog = (String) o;
progressBar.setVisibility(View.GONE);
if (TextUtils.isEmpty(llog))
txtLog.setText(R.string.log_is_empty);
else
txtLog.setText(llog);
svLog.post(() -> svLog.scrollTo(0, txtLog.getHeight()));
hsvLog.post(() -> hsvLog.scrollTo(0, 0));
break;
case 2:
bool = (boolean) o;
if (bool) {
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show();
}
break;
}
}
public void read() {
exec(0);
}
public void clear() {
exec(1);
}
public void save() {
exec(2);
}
}
}

View File

@@ -0,0 +1,179 @@
package com.topjohnwu.magisk;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.SparseArray;
import android.widget.Toast;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import java.io.File;
import java.util.List;
public class MagiskManager extends Application {
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
public static final String MAGISK_HIDE_PATH = "/magisk/.core/magiskhide/";
public static final String TMP_FOLDER_PATH = "/dev/tmp";
public static final String MAGISK_PATH = "/magisk";
public static final String INTENT_SECTION = "section";
// Events
public final CallbackEvent<Void> blockDetectionDone = new CallbackEvent<>();
public final CallbackEvent<Void> magiskHideDone = new CallbackEvent<>();
public final CallbackEvent<Void> reloadMainActivity = new CallbackEvent<>();
public final CallbackEvent<Void> moduleLoadDone = new CallbackEvent<>();
public final CallbackEvent<Void> repoLoadDone = new CallbackEvent<>();
public final CallbackEvent<Void> updateCheckDone = new CallbackEvent<>();
public final CallbackEvent<Void> safetyNetDone = new CallbackEvent<>();
public final SparseArray<CallbackEvent<Policy>> uidSuRequest = new SparseArray<>();
// Info
public double magiskVersion;
public String magiskVersionString;
public double remoteMagiskVersion = -1;
public String magiskLink;
public String releaseNoteLink;
public int SNCheckResult = -1;
public String bootBlock = null;
public boolean isSuClient = false;
public String suVersion = null;
public boolean disabled;
public boolean magiskHideStarted;
// Data
public ValueSortedMap<String, Repo> repoMap;
public ValueSortedMap<String, Module> moduleMap;
public List<String> blockList;
public List<ApplicationInfo> appList;
public List<String> magiskHideList;
// Configurations
public static boolean shellLogging;
public static boolean devLogging;
public boolean magiskHide;
public boolean isDarkTheme;
public boolean updateNotification;
public boolean busybox;
public int suRequestTimeout;
public int suLogTimeout = 14;
public int suAccessState;
public int suResponseType;
public int suNotificationType;
public SharedPreferences prefs;
private static Handler mHandler = new Handler();
@Override
public void onCreate() {
super.onCreate();
prefs = PreferenceManager.getDefaultSharedPreferences(this);
}
public void toast(String msg, int duration) {
mHandler.post(() -> Toast.makeText(this, msg, duration).show());
}
public void toast(int resId, int duration) {
mHandler.post(() -> Toast.makeText(this, resId, duration).show());
}
public void init() {
isDarkTheme = prefs.getBoolean("dark_theme", false);
devLogging = prefs.getBoolean("developer_logging", false);
shellLogging = prefs.getBoolean("shell_logging", false);
magiskHide = prefs.getBoolean("magiskhide", false);
updateNotification = prefs.getBoolean("notification", true);
// Always start a new root shell manually, just for safety
Shell.init();
updateMagiskInfo();
initSuAccess();
initSuConfigs();
// Initialize prefs
prefs.edit()
.putBoolean("dark_theme", isDarkTheme)
.putBoolean("magiskhide", magiskHide)
.putBoolean("notification", updateNotification)
.putBoolean("busybox", busybox)
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
.putBoolean("disable", Utils.itemExist(MAGISK_DISABLE_FILE))
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
.putString("su_auto_response", String.valueOf(suResponseType))
.putString("su_notification", String.valueOf(suNotificationType))
.putString("su_access", String.valueOf(suAccessState))
.apply();
}
public void initSuConfigs() {
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0);
suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1);
}
public void initSuAccess() {
List<String> ret = Shell.sh("su -v");
if (Utils.isValidShellResponse(ret)) {
suVersion = ret.get(0);
isSuClient = suVersion.toUpperCase().contains("MAGISK");
}
if (isSuClient) {
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)) {
magiskVersion = -1;
} else {
try {
magiskVersionString = ret.get(0);
magiskVersion = Double.parseDouble(ret.get(0));
} catch (NumberFormatException e) {
// Custom version don't need to receive updates
magiskVersion = Double.POSITIVE_INFINITY;
}
}
ret = Shell.sh("getprop persist.magisk.busybox");
try {
busybox = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
busybox = false;
}
ret = Shell.sh("getprop ro.magisk.disable");
try {
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
disabled = false;
}
ret = Shell.sh("getprop persist.magisk.hide");
try {
magiskHideStarted = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
magiskHideStarted = false;
}
if (magiskHideStarted) {
magiskHide = true;
}
}
}

View File

@@ -1,59 +1,51 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.ActivityCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.topjohnwu.magisk.utils.CallbackHandler;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, CallbackHandler.EventListener {
private static final String SELECTED_ITEM_ID = "SELECTED_ITEM_ID";
public static CallbackHandler.Event recreate = new CallbackHandler.Event();
public class MainActivity extends Activity
implements NavigationView.OnNavigationItemSelectedListener, CallbackEvent.Listener<Void> {
private final Handler mDrawerHandler = new Handler();
private SharedPreferences prefs;
private int mDrawerItem;
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.drawer_layout) DrawerLayout drawer;
@BindView(R.id.nav_view) public NavigationView navigationView;
@IdRes
private int mSelectedId = R.id.status;
private float toolbarElevation;
@Override
protected void onCreate(final Bundle savedInstanceState) {
prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
prefs = getApplicationContext().prefs;
if (Utils.isDarkTheme) {
setTheme(R.style.AppTheme_dh);
if (getApplicationContext().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@@ -79,133 +71,164 @@ public class MainActivity extends AppCompatActivity
}
};
toolbarElevation = toolbar.getElevation();
drawer.addDrawerListener(toggle);
toggle.syncState();
//noinspection ResourceType
mSelectedId = savedInstanceState == null ? mSelectedId : savedInstanceState.getInt(SELECTED_ITEM_ID);
navigationView.setCheckedItem(mSelectedId);
if (savedInstanceState == null) {
mDrawerHandler.removeCallbacksAndMessages(null);
mDrawerHandler.postDelayed(() -> navigate(mSelectedId), 250);
}
if (savedInstanceState == null)
navigate(getIntent().getStringExtra(MagiskManager.INTENT_SECTION));
navigationView.setNavigationItemSelectedListener(this);
getApplicationContext().reloadMainActivity.register(this);
getApplicationContext().updateCheckDone.register(this);
}
@Override
protected void onResume() {
super.onResume();
CallbackHandler.register(StatusFragment.updateCheckDone, this);
CallbackHandler.register(recreate, this);
if (StatusFragment.updateCheckDone.isTriggered) {
onTrigger(StatusFragment.updateCheckDone);
}
checkHideSection();
}
@Override
protected void onPause() {
super.onPause();
CallbackHandler.unRegister(StatusFragment.updateCheckDone, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
CallbackHandler.unRegister(recreate, this);
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(SELECTED_ITEM_ID, mSelectedId);
outState.putInt(MagiskManager.INTENT_SECTION, mDrawerItem);
}
@Override
protected void onDestroy() {
getApplicationContext().reloadMainActivity.unRegister(this);
getApplicationContext().updateCheckDone.unRegister(this);
super.onDestroy();
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
if (drawer.isDrawerOpen(navigationView))
drawer.closeDrawer(navigationView);
else
finish();
}
}
@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
mSelectedId = menuItem.getItemId();
mDrawerHandler.removeCallbacksAndMessages(null);
mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
drawer.closeDrawer(GravityCompat.START);
drawer.closeDrawer(navigationView);
return true;
}
@Override
public void onTrigger(CallbackHandler.Event event) {
if (event == StatusFragment.updateCheckDone) {
Menu menu = navigationView.getMenu();
menu.findItem(R.id.install).setVisible(StatusFragment.remoteMagiskVersion > 0 &&
Shell.rootAccess());
} else if (event == recreate) {
public void onTrigger(CallbackEvent<Void> event) {
if (event == getApplicationContext().reloadMainActivity) {
recreate();
} else if (event == getApplicationContext().updateCheckDone) {
checkHideSection();
}
}
private void checkHideSection() {
Menu menu = navigationView.getMenu();
menu.findItem(R.id.magiskhide).setVisible(StatusFragment.magiskVersion >= 8 &&
prefs.getBoolean("magiskhide", false) && Shell.rootAccess());
menu.findItem(R.id.modules).setVisible(StatusFragment.magiskVersion >= 4 &&
Shell.rootAccess());
menu.findItem(R.id.downloads).setVisible(StatusFragment.magiskVersion >= 4 &&
Shell.rootAccess());
menu.findItem(R.id.magiskhide).setVisible(
Shell.rootAccess() && getApplicationContext().magiskVersion >= 8
&& prefs.getBoolean("magiskhide", false));
menu.findItem(R.id.modules).setVisible(
Shell.rootAccess() && getApplicationContext().magiskVersion >= 4);
menu.findItem(R.id.downloads).setVisible(
Shell.rootAccess() && getApplicationContext().magiskVersion >= 4);
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
menu.findItem(R.id.install).setVisible(Shell.rootAccess());
menu.findItem(R.id.superuser).setVisible(
Shell.rootAccess() && getApplicationContext().isSuClient);
menu.findItem(R.id.install).setVisible(getApplicationContext().remoteMagiskVersion > 0);
}
public void navigate(final int itemId) {
Fragment navFragment = null;
String tag = "";
public void navigate(String item) {
int itemId = R.id.status;
if (item != null) {
switch (item) {
case "status":
itemId = R.id.status;
break;
case "install":
itemId = R.id.install;
break;
case "superuser":
itemId = R.id.superuser;
break;
case "modules":
itemId = R.id.modules;
break;
case "downloads":
itemId = R.id.downloads;
break;
case "magiskhide":
itemId = R.id.magiskhide;
break;
case "log":
itemId = R.id.log;
break;
case "settings":
itemId = R.id.settings;
break;
case "about":
itemId = R.id.app_about;
break;
}
}
navigate(itemId);
}
public void navigate(int itemId) {
int bak = mDrawerItem;
mDrawerItem = itemId;
navigationView.setCheckedItem(itemId);
switch (itemId) {
case R.id.status:
tag = "status";
navFragment = new StatusFragment();
displayFragment(new StatusFragment(), "status", true);
break;
case R.id.install:
tag = "install";
navFragment = new InstallFragment();
displayFragment(new InstallFragment(), "install", true);
break;
case R.id.superuser:
displayFragment(new SuperuserFragment(), "superuser", true);
break;
case R.id.modules:
tag = "modules";
navFragment = new ModulesFragment();
displayFragment(new ModulesFragment(), "modules", true);
break;
case R.id.downloads:
tag = "downloads";
navFragment = new ReposFragment();
displayFragment(new ReposFragment(), "downloads", true);
break;
case R.id.magiskhide:
tag = "magiskhide";
navFragment = new MagiskHideFragment();
displayFragment(new MagiskHideFragment(), "magiskhide", true);
break;
case R.id.log:
tag = "log";
navFragment = new LogFragment();
displayFragment(new LogFragment(), "log", false);
break;
case R.id.settings:
startActivity(new Intent(this, SettingsActivity.class));
mDrawerItem = bak;
break;
case R.id.app_about:
startActivity(new Intent(this, AboutActivity.class));
return;
}
if (navFragment != null) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
try {
transaction.replace(R.id.content_frame, navFragment, tag).commit();
} catch (IllegalStateException ignored) {}
mDrawerItem = bak;
break;
}
}
}
private void displayFragment(@NonNull Fragment navFragment, String tag, boolean setElevation) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
supportInvalidateOptionsMenu();
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
transaction.replace(R.id.content_frame, navFragment, tag).commitNow();
if (setElevation) toolbar.setElevation(toolbarElevation);
else toolbar.setElevation(0);
}
}

View File

@@ -1,7 +1,6 @@
package com.topjohnwu.magisk;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -15,27 +14,28 @@ import android.widget.TextView;
import com.github.clans.fab.FloatingActionButton;
import com.topjohnwu.magisk.adapters.ModulesAdapter;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.CallbackHandler;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ModuleHelper;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class ModulesFragment extends Fragment implements CallbackHandler.EventListener {
public static final CallbackHandler.Event moduleLoadDone = new CallbackHandler.Event();
public class ModulesFragment extends Fragment implements CallbackEvent.Listener<Void> {
private static final int FETCH_ZIP_CODE = 2;
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.fab) FloatingActionButton fabio;
private List<Module> listModules = new ArrayList<>();
@@ -43,8 +43,8 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.modules_fragment, container, false);
ButterKnife.bind(this, view);
View view = inflater.inflate(R.layout.fragment_modules, container, false);
unbinder = ButterKnife.bind(this, view);
fabio.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
@@ -54,7 +54,7 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
mSwipeRefreshLayout.setOnRefreshListener(() -> {
recyclerView.setVisibility(View.GONE);
new Async.LoadModules().exec();
new LoadModules(getActivity()).exec();
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -69,7 +69,7 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
}
});
if (moduleLoadDone.isTriggered) {
if (getApplication().moduleLoadDone.isTriggered) {
updateUI();
}
@@ -77,7 +77,7 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
}
@Override
public void onTrigger(CallbackHandler.Event event) {
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("ModulesFragment: UI refresh triggered");
updateUI();
}
@@ -87,31 +87,38 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
final Uri uri = data.getData();
new Async.FlashZIP(getActivity(), uri).exec();
new FlashZip(getActivity(), uri).exec();
}
}
@Override
public void onResume() {
super.onResume();
CallbackHandler.register(moduleLoadDone, this);
public void onStart() {
super.onStart();
getApplication().moduleLoadDone.register(this);
getActivity().setTitle(R.string.modules);
}
@Override
public void onDestroy() {
super.onDestroy();
CallbackHandler.unRegister(moduleLoadDone, this);
public void onStop() {
getApplication().moduleLoadDone.unRegister(this);
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
private void updateUI() {
ModuleHelper.getModuleList(listModules);
listModules.clear();
listModules.addAll(getApplication().moduleMap.values());
if (listModules.size() == 0) {
emptyTv.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
emptyTv.setVisibility(View.GONE);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
recyclerView.setAdapter(new ModulesAdapter(listModules));
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk;
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.MenuItemCompat;
@@ -16,24 +15,26 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.ReposAdapter;
import com.topjohnwu.magisk.adapters.SimpleSectionedRecyclerViewAdapter;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.CallbackHandler;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ModuleHelper;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class ReposFragment extends Fragment implements CallbackHandler.EventListener {
public static final CallbackHandler.Event repoLoadDone = new CallbackHandler.Event();
public class ReposFragment extends Fragment implements CallbackEvent.Listener<Void> {
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
private List<Repo> mUpdateRepos = new ArrayList<>();
@@ -47,16 +48,19 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
private SearchView.OnQueryTextListener searchListener;
@Nullable
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.repos_fragment, container, false);
View view = inflater.inflate(R.layout.fragment_repos, container, false);
unbinder = ButterKnife.bind(this, view);
ButterKnife.bind(this, view);
mSectionedAdapter = new
SimpleSectionedRecyclerViewAdapter(getActivity(), R.layout.section,
mSectionedAdapter = new SimpleSectionedRecyclerViewAdapter(R.layout.section,
R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos));
recyclerView.setAdapter(mSectionedAdapter);
@@ -65,10 +69,10 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
mSwipeRefreshLayout.setOnRefreshListener(() -> {
recyclerView.setVisibility(View.GONE);
new Async.LoadRepos(getActivity()).exec();
new LoadRepos(getActivity()).exec();
});
if (repoLoadDone.isTriggered) {
if (getApplication().repoLoadDone.isTriggered) {
reloadRepos();
updateUI();
}
@@ -90,7 +94,7 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
}
@Override
public void onTrigger(CallbackHandler.Event event) {
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("ReposFragment: UI refresh triggered");
reloadRepos();
updateUI();
@@ -104,21 +108,40 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
}
@Override
public void onResume() {
super.onResume();
setHasOptionsMenu(true);
CallbackHandler.register(repoLoadDone, this);
public void onStart() {
super.onStart();
getApplication().repoLoadDone.register(this);
getActivity().setTitle(R.string.downloads);
}
@Override
public void onDestroy() {
super.onDestroy();
CallbackHandler.unRegister(repoLoadDone, this);
public void onStop() {
getApplication().repoLoadDone.unRegister(this);
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
private void reloadRepos() {
ModuleHelper.getRepoLists(mUpdateRepos, mInstalledRepos, mOthersRepos);
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();
@@ -129,7 +152,7 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
private void updateUI() {
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
emptyTv.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
@@ -144,13 +167,13 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
}
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
mSectionedAdapter.setSections(array);
emptyTv.setVisibility(View.GONE);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
mSwipeRefreshLayout.setRefreshing(false);
}
private class FilterApps extends Async.NormalTask<String, Void, Void> {
private class FilterApps extends ParallelTask<String, Void, Void> {
@Override
protected Void doInBackground(String... strings) {
String newText = strings[0];

View File

@@ -1,39 +1,38 @@
package com.topjohnwu.magisk;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.WindowManager;
import android.widget.Toast;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.asyncs.SerialTask;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ModuleHelper;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SettingsActivity extends AppCompatActivity {
public class SettingsActivity extends Activity {
@BindView(R.id.toolbar) Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
Logger.dev("AboutActivity: Theme is " + theme);
if (Utils.isDarkTheme) {
setTheme(R.style.AppTheme_dh);
if (getApplicationContext().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
}
setContentView(R.layout.activity_container);
@@ -74,52 +73,47 @@ public class SettingsActivity extends AppCompatActivity {
public static class SettingsFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
private ListPreference themePreference;
private SharedPreferences prefs;
private PreferenceScreen prefScreen;
private ListPreference suAccess, autoRes, suNotification, requestTimeout;
private MagiskManager getApplication() {
return Utils.getMagiskManager(getActivity());
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.app_settings);
PreferenceManager.setDefaultValues(getActivity(), R.xml.app_settings, false);
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
prefScreen = getPreferenceScreen();
themePreference = (ListPreference) findPreference("theme");
CheckBoxPreference busyboxPreference = (CheckBoxPreference) findPreference("busybox");
CheckBoxPreference magiskhidePreference = (CheckBoxPreference) findPreference("magiskhide");
CheckBoxPreference hostsPreference = (CheckBoxPreference) findPreference("hosts");
Preference clear = findPreference("clear");
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
clear.setOnPreferenceClickListener((pref) -> {
SharedPreferences repoMap = getActivity().getSharedPreferences(ModuleHelper.FILE_KEY, Context.MODE_PRIVATE);
repoMap.edit()
.putString(ModuleHelper.ETAG_KEY, "")
.putInt(ModuleHelper.VERSION_KEY, 0)
.apply();
new Async.LoadRepos(getActivity()).exec();
Toast.makeText(getActivity(), R.string.repo_cache_cleared, Toast.LENGTH_LONG).show();
suAccess = (ListPreference) findPreference("su_access");
autoRes = (ListPreference) findPreference("su_auto_response");
requestTimeout = (ListPreference) findPreference("su_request_timeout");
suNotification = (ListPreference) findPreference("su_notification");
setSummary();
findPreference("clear").setOnPreferenceClickListener((pref) -> {
Utils.clearRepoCache(getActivity());
return true;
});
if (Utils.isDarkTheme) {
themePreference.setSummary(R.string.theme_dark);
if (!Shell.rootAccess()) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
} else {
themePreference.setSummary(R.string.theme_default);
}
if (StatusFragment.magiskVersion < 9) {
hostsPreference.setEnabled(false);
busyboxPreference.setEnabled(false);
} else if (StatusFragment.magiskVersion < 8) {
magiskhidePreference.setEnabled(false);
} else if (! Shell.rootAccess()) {
busyboxPreference.setEnabled(false);
magiskhidePreference.setEnabled(false);
hostsPreference.setEnabled(false);
} else {
busyboxPreference.setEnabled(true);
magiskhidePreference.setEnabled(true);
hostsPreference.setEnabled(true);
if (!getApplication().isSuClient) {
prefScreen.removePreference(suCategory);
}
if (getApplication().magiskVersion < 11) {
prefScreen.removePreference(magiskCategory);
}
}
}
@@ -130,62 +124,81 @@ public class SettingsActivity extends AppCompatActivity {
}
@Override
public void onDestroy() {
super.onDestroy();
public void onPause() {
super.onPause();
prefs.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
Logger.dev("Settings: Prefs change " + key);
boolean checked;
boolean enabled;
switch (key) {
case "theme":
String theme = prefs.getString("theme", getString(R.string.theme_default_value));
if (Utils.isDarkTheme != theme.equalsIgnoreCase(getString(R.string.theme_dark_value))) {
Utils.isDarkTheme = !Utils.isDarkTheme;
case "dark_theme":
enabled = prefs.getBoolean("dark_theme", false);
if (getApplication().isDarkTheme != enabled) {
getApplication().isDarkTheme = enabled;
getApplication().reloadMainActivity.trigger();
getActivity().recreate();
MainActivity.recreate.trigger();
}
break;
case "magiskhide":
checked = prefs.getBoolean("magiskhide", false);
new Async.RootTask<Void, Void, Void>() {
private boolean enable = checked;
case "disable":
enabled = prefs.getBoolean("disable", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... params) {
protected Void doInBackground(Void... voids) {
if (enable) {
Utils.createFile("/magisk/.core/magiskhide/enable");
Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem("/magisk/.core/magiskhide/enable");
Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE);
}
return null;
}
}.exec();
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
break;
case "busybox":
checked = prefs.getBoolean("busybox", false);
new Async.RootTask<Void, Void, Void>() {
private boolean enable = checked;
enabled = prefs.getBoolean("busybox", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... params) {
protected Void doInBackground(Void... voids) {
if (enable) {
Utils.createFile("/magisk/.core/busybox/enable");
Shell.su(
"setprop persist.magisk.busybox 1",
"sh /sbin/magic_mask.sh mount_busybox");
} else {
Utils.removeItem("/magisk/.core/busybox/enable");
Shell.su(
"setprop persist.magisk.busybox 0",
"umount /system/xbin");
}
return null;
}
}.exec();
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
break;
case "magiskhide":
enabled = prefs.getBoolean("magiskhide", false);
if (enabled) {
if (!getApplication().isSuClient) {
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 {
new MagiskHide().disable();
}
break;
case "hosts":
checked = prefs.getBoolean("hosts", false);
new Async.RootTask<Void, Void, Void>() {
private boolean enable = checked;
enabled = prefs.getBoolean("hosts", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... voids) {
if (enable) {
@@ -199,14 +212,38 @@ public class SettingsActivity extends AppCompatActivity {
}
}.exec();
break;
case "su_access":
getApplication().suAccessState = Utils.getPrefsInt(prefs, "su_access", 0);
Shell.su("setprop persist.sys.root_access " + getApplication().suAccessState);
break;
case "su_request_timeout":
getApplication().suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
break;
case "su_auto_response":
getApplication().suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0);
break;
case "su_notification":
getApplication().suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1);
break;
case "developer_logging":
Logger.devLog = prefs.getBoolean("developer_logging", false);
MagiskManager.devLogging = prefs.getBoolean("developer_logging", false);
break;
case "shell_logging":
Logger.logShell = prefs.getBoolean("shell_logging", false);
MagiskManager.shellLogging = prefs.getBoolean("shell_logging", false);
break;
}
setSummary();
}
private void setSummary() {
suAccess.setSummary(getResources()
.getStringArray(R.array.su_access)[getApplication().suAccessState]);
autoRes.setSummary(getResources()
.getStringArray(R.array.auto_response)[getApplication().suResponseType]);
suNotification.setSummary(getResources()
.getStringArray(R.array.su_notification)[getApplication().suNotificationType]);
requestTimeout.setSummary(
getString(R.string.request_timeout_summary, prefs.getString("su_request_timeout", "10")));
}
}

View File

@@ -1,55 +1,73 @@
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.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
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.services.UpdateCheckService;
public class SplashActivity extends AppCompatActivity {
public class SplashActivity extends Activity{
private static final int UPDATE_SERVICE_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplication());
String theme = prefs.getString("theme", getString(R.string.theme_default_value));
Utils.isDarkTheme = theme.equalsIgnoreCase(getString(R.string.theme_dark_value));
MagiskManager magiskManager = getApplicationContext();
if (Utils.isDarkTheme) {
setTheme(R.style.AppTheme_dh);
// Init the info and configs and root shell
magiskManager.init();
// Initialize the update check service, notify every 3 hours
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);
}
Logger.devLog = prefs.getBoolean("developer_logging", false);
Logger.logShell = prefs.getBoolean("shell_logging", false);
// Initialize prefs
prefs.edit()
.putBoolean("magiskhide", Utils.itemExist(false, "/magisk/.core/magiskhide/enable"))
.putBoolean("busybox", Utils.commandExists("busybox"))
.putBoolean("hosts", Utils.itemExist(false, "/magisk/.core/hosts"))
.apply();
// Start all async tasks
new Async.GetBootBlocks().exec();
new Async.CheckUpdates().exec();
new Async.LoadModules() {
// Now fire all async tasks
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 Async.LoadRepos(getApplicationContext()).exec();
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();
new Async.LoadApps(getPackageManager()).exec();
// Start main activity
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
}
}

View File

@@ -1,10 +1,5 @@
package com.topjohnwu.magisk;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
@@ -15,29 +10,25 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.CallbackHandler;
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 java.util.List;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
public class StatusFragment extends Fragment implements CallbackHandler.EventListener {
public static double magiskVersion, remoteMagiskVersion = -1;
public static String magiskVersionString = "(none)", magiskLink, releaseNoteLink;
public static int SNCheckResult = -1;
public class StatusFragment extends Fragment implements CallbackEvent.Listener<Void> {
private static boolean noDialog = false;
public static final CallbackHandler.Event updateCheckDone = new CallbackHandler.Event();
public static final CallbackHandler.Event safetyNetDone = new CallbackHandler.Event();
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.magisk_status_container) View magiskStatusContainer;
@@ -62,19 +53,30 @@ public class StatusFragment extends Fragment implements CallbackHandler.EventLis
@BindColor(R.color.grey500) int colorNeutral;
@BindColor(R.color.blue500) int colorInfo;
@BindColor(android.R.color.transparent) int trans;
int defaultColor;
static {
checkMagiskInfo();
@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());
}
private AlertDialog updateMagisk;
@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.status_fragment, container, false);
ButterKnife.bind(this, v);
View v = inflater.inflate(R.layout.fragment_status, container, false);
unbinder = ButterKnife.bind(this, v);
defaultColor = magiskUpdateText.getCurrentTextColor();
@@ -91,119 +93,100 @@ public class StatusFragment extends Fragment implements CallbackHandler.EventLis
safetyNetStatusText.setText(R.string.safetyNet_check_text);
safetyNetStatusText.setTextColor(defaultColor);
safetyNetDone.isTriggered = false;
getApplication().safetyNetDone.isTriggered = false;
noDialog = false;
updateUI();
new Async.CheckUpdates().exec();
new CheckUpdates(getActivity()).exec();
});
safetyNetContainer.setOnClickListener(view -> {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetContainer.setBackgroundColor(trans);
safetyNetIcon.setImageResource(0);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
Async.checkSafetyNet(getActivity());
});
if (magiskVersion < 0 && Shell.rootAccess() && !noDialog) {
if (getApplication().magiskVersion < 0 && Shell.rootAccess() && !noDialog) {
noDialog = true;
Utils.getAlertDialogBuilder(getActivity())
new AlertDialogBuilder(getActivity())
.setTitle(R.string.no_magisk_title)
.setMessage(R.string.no_magisk_msg)
.setCancelable(true)
.setPositiveButton(R.string.goto_install, (dialogInterface, i) -> {
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
try {
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
} catch (IllegalStateException ignored) {}
})
.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(CallbackHandler.Event event) {
if (event == updateCheckDone) {
public void onTrigger(CallbackEvent<Void> event) {
if (event == getApplication().updateCheckDone) {
Logger.dev("StatusFragment: Update Check UI refresh triggered");
updateCheckUI();
} else if (event == safetyNetDone) {
} else if (event == getApplication().safetyNetDone) {
Logger.dev("StatusFragment: SafetyNet UI refresh triggered");
updateSafetyNetUI();
}
}
@Override
public void onResume() {
super.onResume();
CallbackHandler.register(updateCheckDone, this);
CallbackHandler.register(safetyNetDone, this);
if (updateCheckDone.isTriggered) {
updateCheckUI();
}
if (safetyNetDone.isTriggered) {
updateSafetyNetUI();
}
public void onStart() {
super.onStart();
getApplication().updateCheckDone.register(this);
getApplication().safetyNetDone.register(this);
getActivity().setTitle(R.string.status);
}
@Override
public void onPause() {
super.onPause();
CallbackHandler.unRegister(updateCheckDone, this);
CallbackHandler.unRegister(safetyNetDone, this);
public void onStop() {
getApplication().updateCheckDone.unRegister(this);
getApplication().safetyNetDone.unRegister(this);
super.onStop();
}
private static void checkMagiskInfo() {
List<String> ret = Shell.sh("getprop magisk.version");
if (ret.get(0).length() == 0) {
magiskVersion = -1;
} else {
try {
magiskVersionString = ret.get(0);
magiskVersion = Double.parseDouble(ret.get(0));
} catch (NumberFormatException e) {
// Custom version don't need to receive updates
magiskVersion = Double.POSITIVE_INFINITY;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
private void updateUI() {
int image, color;
checkMagiskInfo();
getApplication().updateMagiskInfo();
if (magiskVersion < 0) {
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, magiskVersionString));
magiskVersionText.setText(getString(R.string.magisk_version, getApplication().magiskVersionString));
}
if (Shell.rootStatus == 1) {
color = colorOK;
image = R.drawable.ic_check_circle;
rootStatusText.setText(R.string.proper_root);
rootInfoText.setText(Shell.sh("su -v").get(0));
} else {
rootInfoText.setText(R.string.root_info_warning);
if (Shell.rootStatus == 0) {
switch (Shell.rootStatus) {
case 0:
color = colorBad;
image = R.drawable.ic_cancel;
rootStatusText.setText(R.string.not_rooted);
} else {
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);
@@ -214,24 +197,25 @@ public class StatusFragment extends Fragment implements CallbackHandler.EventLis
private void updateCheckUI() {
int image, color;
if (remoteMagiskVersion < 0) {
if (getApplication().remoteMagiskVersion < 0) {
color = colorNeutral;
image = R.drawable.ic_help;
magiskUpdateText.setText(R.string.cannot_check_updates);
} else if (remoteMagiskVersion > magiskVersion) {
} else if (getApplication().remoteMagiskVersion > getApplication().magiskVersion) {
color = colorInfo;
image = R.drawable.ic_update;
magiskUpdateText.setText(getString(R.string.magisk_update_available, remoteMagiskVersion));
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 (magiskVersion < 0) {
if (getApplication().magiskVersion < 0) {
color = colorBad;
image = R.drawable.ic_cancel;
}
magiskStatusContainer.setBackgroundColor(color);
magiskVersionText.setTextColor(color);
magiskUpdateText.setTextColor(color);
@@ -239,38 +223,12 @@ public class StatusFragment extends Fragment implements CallbackHandler.EventLis
magiskCheckUpdatesProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
updateMagisk = Utils.getAlertDialogBuilder(getActivity())
.setTitle(R.string.magisk_update_title)
.setMessage(getString(R.string.magisk_update_message, remoteMagiskVersion))
.setCancelable(true)
.setPositiveButton(R.string.goto_install, (dialogInterface, i) -> {
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
try {
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
} catch (IllegalStateException ignored) {}
})
.setNeutralButton(R.string.check_release_notes, (dialog, which) -> {
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(releaseNoteLink)));
})
.setNegativeButton(R.string.no_thanks, null)
.create();
if (magiskVersion < remoteMagiskVersion && Shell.rootAccess()) {
magiskStatusContainer.setOnClickListener(view -> updateMagisk.show());
if (!noDialog) {
noDialog = true;
updateMagisk.show();
}
}
}
private void updateSafetyNetUI() {
int image, color;
safetyNetProgress.setVisibility(View.GONE);
switch (SNCheckResult) {
switch (getApplication().SNCheckResult) {
case -3:
color = colorNeutral;
image = R.drawable.ic_help;

View File

@@ -0,0 +1,92 @@
package com.topjohnwu.magisk;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.topjohnwu.magisk.adapters.SuLogAdapter;
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.ButterKnife;
import butterknife.Unbinder;
public class SuLogFragment extends Fragment {
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
private Unbinder unbinder;
private SuLogDatabaseHelper dbHelper;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_log, menu);
menu.findItem(R.id.menu_save).setVisible(false);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
unbinder = ButterKnife.bind(this, v);
dbHelper = new SuLogDatabaseHelper(getActivity());
updateList();
return v;
}
private void updateList() {
List<SuLogEntry> logs = dbHelper.getLogList();
if (logs.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new SuLogAdapter(logs).getAdapter());
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
updateList();
return true;
case R.id.menu_clear:
dbHelper.clearLogs();
updateList();
return true;
default:
return true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@@ -0,0 +1,64 @@
package com.topjohnwu.magisk;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.topjohnwu.magisk.adapters.PolicyAdapter;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class SuperuserFragment extends Fragment {
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyRv;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
unbinder = ButterKnife.bind(this, view);
PackageManager pm = getActivity().getPackageManager();
SuDatabaseHelper dbHelper = new SuDatabaseHelper(getActivity());
List<Policy> policyList = dbHelper.getPolicyList(pm);
if (policyList.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new PolicyAdapter(policyList, dbHelper, pm));
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
return view;
}
@Override
public void onStart() {
super.onStart();
getActivity().setTitle(getString(R.string.superuser));
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.adapters;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -12,10 +13,12 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -24,6 +27,17 @@ import butterknife.ButterKnife;
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
public static final List<String> BLACKLIST = Arrays.asList(
"android",
"com.topjohnwu.magisk",
"com.google.android.gms"
);
private static final List<String> SNLIST = Arrays.asList(
"com.google.android.apps.walletnfcrel",
"com.nianticlabs.pokemongo"
);
private List<ApplicationInfo> mOriginalList, mList;
private List<String> mHideList;
private PackageManager packageManager;
@@ -33,18 +47,18 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
mOriginalList = mList = Collections.emptyList();
mHideList = Collections.emptyList();
this.packageManager = packageManager;
filter = new ApplicationFilter();
}
public void setLists(List<ApplicationInfo> listApps, List<String> hideList) {
mOriginalList = mList = Collections.unmodifiableList(listApps);
mHideList = new ArrayList<>(hideList);
mOriginalList = mList = listApps;
mHideList = hideList;
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
ButterKnife.bind(this, mView);
return new ViewHolder(mView);
}
@@ -56,17 +70,30 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
holder.appName.setText(info.loadLabel(packageManager));
holder.appPackage.setText(info.packageName);
// Remove all listeners
holder.itemView.setOnClickListener(null);
holder.checkBox.setOnCheckedChangeListener(null);
holder.checkBox.setChecked(mHideList.contains(info.packageName));
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked) {
new Async.MagiskHide().add(info.packageName);
mHideList.add(info.packageName);
} else {
new Async.MagiskHide().rm(info.packageName);
mHideList.remove(info.packageName);
}
});
if (SNLIST.contains(info.packageName)) {
holder.checkBox.setChecked(true);
holder.checkBox.setEnabled(false);
holder.itemView.setOnClickListener(v ->
SnackbarMaker.make(holder.itemView,
R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG).show()
);
} else {
holder.checkBox.setEnabled(true);
holder.checkBox.setChecked(mHideList.contains(info.packageName));
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked) {
new MagiskHide().add(info.packageName);
mHideList.add(info.packageName);
} else {
new MagiskHide().rm(info.packageName);
mHideList.remove(info.packageName);
}
});
}
}
@Override
@@ -75,9 +102,6 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
}
public void filter(String constraint) {
if (filter == null) {
filter = new ApplicationFilter();
}
filter.filter(constraint);
}

View File

@@ -12,8 +12,9 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.SerialTask;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
@@ -32,7 +33,6 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
ButterKnife.bind(this, view);
return new ViewHolder(view);
}
@@ -41,15 +41,19 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
Context context = holder.itemView.getContext();
final Module module = mList.get(position);
holder.title.setText(module.getName());
holder.versionName.setText(module.getVersion());
String version = module.getVersion();
String author = module.getAuthor();
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
holder.description.setText(module.getDescription());
String description = module.getDescription();
String noInfo = context.getString(R.string.no_info_provided);
holder.title.setText(module.getName());
holder.versionName.setText( TextUtils.isEmpty(version) ? noInfo : version);
holder.author.setText( TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
holder.description.setText( TextUtils.isEmpty(description) ? noInfo : description);
holder.checkBox.setOnCheckedChangeListener(null);
holder.checkBox.setChecked(module.isEnabled());
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new Async.RootTask<Void, Void, Void>() {
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new SerialTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
if (isChecked) {
@@ -62,12 +66,12 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
protected void onPostExecute(Void v) {
int title = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
Snackbar.make(holder.title, title, Snackbar.LENGTH_SHORT).show();
int snack = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
}
}.exec());
holder.delete.setOnClickListener(v -> new Async.RootTask<Void, Void, Void>() {
holder.delete.setOnClickListener(v -> new SerialTask<Void, Void, Void>() {
private final boolean removed = module.willBeRemoved();
@Override
@@ -82,8 +86,8 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
protected void onPostExecute(Void v) {
int title = removed ? R.string.remove_file_deleted : R.string.remove_file_created;
Snackbar.make(holder.title, title, Snackbar.LENGTH_SHORT).show();
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());

View File

@@ -0,0 +1,223 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.pm.PackageManager;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
private List<Policy> policyList;
private SuDatabaseHelper dbHelper;
private PackageManager pm;
private Set<Policy> expandList = new HashSet<>();
public PolicyAdapter(List<Policy> list, SuDatabaseHelper db, PackageManager pm) {
policyList = list;
dbHelper = db;
this.pm = pm;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Policy policy = policyList.get(position);
try {
holder.setExpanded(expandList.contains(policy));
holder.itemView.setOnClickListener(view -> {
if (holder.mExpanded) {
holder.collapse();
expandList.remove(policy);
} else {
holder.expand();
expandList.add(policy);
}
});
holder.appName.setText(policy.appName);
holder.packageName.setText(policy.packageName);
holder.appIcon.setImageDrawable(pm.getPackageInfo(policy.packageName, 0).applicationInfo.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.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.moreInfo.setVisibility(View.GONE);
} catch (PackageManager.NameNotFoundException e) {
policyList.remove(position);
dbHelper.deletePolicy(policy.uid);
onBindViewHolder(holder, position);
}
}
@Override
public int getItemCount() {
return policyList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.package_name) TextView packageName;
@BindView(R.id.expand_layout) LinearLayout expandLayout;
@BindView(R.id.app_icon) ImageView appIcon;
@BindView(R.id.master_switch) Switch masterSwitch;
@BindView(R.id.notification_switch) Switch notificationSwitch;
@BindView(R.id.logging_switch) Switch loggingSwitch;
@BindView(R.id.delete) ImageView delete;
@BindView(R.id.more_info) ImageView moreInfo;
private ValueAnimator mAnimator;
private boolean mExpanded = false;
private static int expandHeight = 0;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
expandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@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) {
mExpanded = expanded;
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
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;
}
}
}

View File

@@ -1,28 +1,24 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.MarkDownWindow;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.receivers.RepoDlReceiver;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebWindow;
import java.util.HashSet;
import java.util.List;
import butterknife.BindView;
@@ -31,7 +27,7 @@ import butterknife.ButterKnife;
public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> {
private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos;
private HashSet<Repo> expandList = new HashSet<>();
private Context mContext;
public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) {
mUpdateRepos = update;
@@ -41,62 +37,52 @@ public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder>
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
ButterKnife.bind(this, v);
mContext = parent.getContext();
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_repo, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
Repo repo = getItem(position);
holder.title.setText(repo.getName());
holder.versionName.setText(repo.getVersion());
String author = repo.getAuthor();
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
holder.author.setText(TextUtils.isEmpty(author) ? null : mContext.getString(R.string.author, author));
holder.description.setText(repo.getDescription());
holder.setExpanded(expandList.contains(repo));
holder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), mContext));
holder.itemView.setOnClickListener(view -> {
if (holder.mExpanded) {
holder.collapse();
expandList.remove(repo);
} else {
holder.expand();
expandList.add(repo);
}
});
holder.changeLog.setOnClickListener(view -> {
if (!TextUtils.isEmpty(repo.getLogUrl())) {
new WebWindow(context.getString(R.string.changelog), repo.getLogUrl(), context);
}
});
holder.updateImage.setOnClickListener(view -> {
holder.downloadImage.setOnClickListener(v -> {
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
Utils.getAlertDialogBuilder(context)
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
.setMessage(context.getString(R.string.repo_install_msg, filename))
new AlertDialogBuilder(mContext)
.setTitle(mContext.getString(R.string.repo_install_title, repo.getName()))
.setMessage(mContext.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive(
context,
new RepoDlReceiver(),
.setPositiveButton(R.string.install, (d, i) -> Utils.dlAndReceive(
mContext,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
new ProcessRepoZip(activity, uri, true).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)
.show();
});
holder.authorLink.setOnClickListener(view -> {
if (!TextUtils.isEmpty(repo.getDonateUrl())) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getDonateUrl())));
}
});
holder.supportLink.setOnClickListener(view -> {
if (!TextUtils.isEmpty(repo.getSupportUrl())) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getSupportUrl())));
}
});
}
@Override
@@ -124,97 +110,12 @@ public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder>
@BindView(R.id.version_name) TextView versionName;
@BindView(R.id.description) TextView description;
@BindView(R.id.author) TextView author;
@BindView(R.id.expand_layout) LinearLayout expandLayout;
@BindView(R.id.update) ImageView updateImage;
@BindView(R.id.changeLog) ImageView changeLog;
@BindView(R.id.authorLink) ImageView authorLink;
@BindView(R.id.supportLink) ImageView supportLink;
private ValueAnimator mAnimator;
private ObjectAnimator animY2;
private boolean mExpanded = false;
private static int expandHeight = 0;
@BindView(R.id.info_layout) LinearLayout infoLayout;
@BindView(R.id.download) ImageView downloadImage;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
expandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@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);
animY2 = ObjectAnimator.ofFloat(updateImage, "translationY", expandHeight / 2);
return true;
}
});
}
private void setExpanded(boolean expanded) {
mExpanded = expanded;
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
layoutParams.height = expanded ? expandHeight : 0;
expandLayout.setLayoutParams(layoutParams);
expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
if (expanded) {
updateImage.setTranslationY(expandHeight / 2);
} else {
updateImage.setTranslationY(0);
}
}
private void expand() {
expandLayout.setVisibility(View.VISIBLE);
mAnimator.start();
animY2.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();
animY2.reverse();
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;
}
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.adapters;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -13,25 +12,21 @@ import java.util.Comparator;
public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context mContext;
private static final int SECTION_TYPE = 0;
private boolean mValid = true;
private int mSectionResourceId;
private int mTextResourceId;
private LayoutInflater mLayoutInflater;
private RecyclerView.Adapter mBaseAdapter;
private SparseArray<Section> mSections = new SparseArray<Section>();
public SimpleSectionedRecyclerViewAdapter(Context context, int sectionResourceId, int textResourceId,
public SimpleSectionedRecyclerViewAdapter(int sectionResourceId, int textResourceId,
RecyclerView.Adapter baseAdapter) {
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mSectionResourceId = sectionResourceId;
mTextResourceId = textResourceId;
mBaseAdapter = baseAdapter;
mContext = context;
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
@@ -74,7 +69,7 @@ public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<Rec
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
if (typeView == SECTION_TYPE) {
final View view = LayoutInflater.from(mContext).inflate(mSectionResourceId, parent, false);
View view = LayoutInflater.from(parent.getContext()).inflate(mSectionResourceId, parent, false);
return new SectionViewHolder(view,mTextResourceId);
}else{
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);

View File

@@ -0,0 +1,242 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
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.superuser.SuLogEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SuLogAdapter {
private ExpandableAdapter adapter;
private Set<SuLogEntry> expandList = new HashSet<>();
public SuLogAdapter(List<SuLogEntry> list) {
// Separate the logs with date
Map<String, List<SuLogEntry>> logEntryMap = new LinkedHashMap<>();
List<SuLogEntry> group;
for (SuLogEntry log : list) {
String date = log.getDateString();
group = logEntryMap.get(date);
if (group == null) {
group = new ArrayList<>();
logEntryMap.put(date, group);
}
group.add(log);
}
// Then format them into expandable groups
List<LogGroup> logEntryGroups = new ArrayList<>();
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() {
return adapter;
}
private class ExpandableAdapter
extends ExpandableRecyclerViewAdapter<LogGroupViewHolder, LogViewHolder> {
ExpandableAdapter(List<? extends ExpandableGroup> groups) {
super(groups);
expandableList.expandedGroupIndexes[0] = true;
}
@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();
}
}
}
private class LogGroup extends ExpandableGroup<SuLogEntry> {
LogGroup(String title, List<SuLogEntry> items) {
super(title, items);
}
}
static class LogGroupViewHolder extends GroupViewHolder {
@BindView(R.id.date) TextView date;
@BindView(R.id.arrow) ImageView arrow;
public LogGroupViewHolder(View itemView) {
super(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 {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.action) TextView action;
@BindView(R.id.time) TextView time;
@BindView(R.id.fromPid) TextView fromPid;
@BindView(R.id.toUid) TextView toUid;
@BindView(R.id.command) TextView command;
@BindView(R.id.expand_layout) LinearLayout expandLayout;
private ValueAnimator mAnimator;
private boolean mExpanded = false;
private static int expandHeight = 0;
public LogViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
expandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@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) {
mExpanded = expanded;
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
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;
}
}
}

View File

@@ -0,0 +1,41 @@
package com.topjohnwu.magisk.adapters;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
public class TabFragmentAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
private List<String> titleList;
public TabFragmentAdapter(FragmentManager fm) {
super(fm);
fragmentList = new ArrayList<>();
titleList = new ArrayList<>();
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
public void addTab(Fragment fragment, String title) {
fragmentList.add(fragment);
titleList.add(title);
}
}

View File

@@ -0,0 +1,72 @@
package com.topjohnwu.magisk.asyncs;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.NotificationCompat;
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.WebService;
import org.json.JSONException;
import org.json.JSONObject;
public class CheckUpdates extends ParallelTask<Void, Void, Void> {
private static final String UPDATE_JSON = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/updates/magisk_update.json";
private static final int NOTIFICATION_ID = 1;
private boolean showNotification = false;
public CheckUpdates(Context context, boolean b) {
this(context);
showNotification = b;
}
public CheckUpdates(Context context) {
magiskManager = Utils.getMagiskManager(context);
}
@Override
protected Void doInBackground(Void... voids) {
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
try {
JSONObject json = new JSONObject(jsonStr);
JSONObject magisk = json.getJSONObject("magisk");
magiskManager.remoteMagiskVersion = magisk.getDouble("versionCode");
magiskManager.magiskLink = magisk.getString("link");
magiskManager.releaseNoteLink = magisk.getString("note");
} catch (JSONException ignored) {
}
return null;
}
@Override
protected void onPostExecute(Void v) {
if (magiskManager.magiskVersion < magiskManager.remoteMagiskVersion
&& showNotification && magiskManager.updateNotification) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(magiskManager);
builder.setSmallIcon(R.drawable.ic_magisk)
.setContentTitle(magiskManager.getString(R.string.magisk_update_title))
.setContentText(magiskManager.getString(R.string.magisk_update_available, magiskManager.remoteMagiskVersion))
.setVibrate(new long[]{0, 100, 100, 100})
.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();
}
}

View File

@@ -0,0 +1,154 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
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;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class FlashZip extends SerialTask<Void, String, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
private String mFilename;
private ProgressDialog progress;
public FlashZip(Activity context, Uri uri) {
super(context);
mUri = uri;
mCachedFile = new File(magiskManager.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 {
publishProgress(magiskManager.getString(R.string.copying_msg));
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");
}
private int cleanup(int ret) {
Shell.su(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);
return ret;
}
@Override
protected void onPreExecute() {
progress = new ProgressDialog(activity);
progress.setTitle(R.string.zip_install_progress_title);
progress.show();
}
@Override
protected void onProgressUpdate(String... values) {
progress.setMessage(values[0]);
}
@Override
protected Integer doInBackground(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename);
List<String> ret;
try {
copyToCache();
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) {
e.printStackTrace();
}
return cleanup(-1);
}
// -1 = error, manual install; 0 = invalid zip; 1 = success
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
progress.dismiss();
switch (result) {
case -1:
Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show();
Utils.showUriSnack(activity, mUri);
break;
case 0:
Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
break;
case 1:
onSuccess();
break;
}
}
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();
}
}

View File

@@ -0,0 +1,27 @@
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();
}
}

View File

@@ -0,0 +1,39 @@
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();
}
}

View File

@@ -0,0 +1,41 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
public class LoadModules extends SerialTask<Void, Void, Void> {
public LoadModules(Activity context) {
super(context);
}
@Override
protected Void doInBackground(Void... voids) {
Logger.dev("LoadModules: Loading modules");
magiskManager.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) {
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;
}
@Override
protected void onPostExecute(Void v) {
magiskManager.moduleLoadDone.trigger();
}
}

View File

@@ -0,0 +1,125 @@
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.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
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/orgs/Magisk-Modules-Repo/repos";
private String prefsPath;
public LoadRepos(Activity context) {
super(context);
prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
}
@Override
protected Void doInBackground(Void... voids) {
Logger.dev("LoadRepos: Loading repos");
SharedPreferences prefs = magiskManager.prefs;
RepoDatabaseHelper dbHelper = 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();
dbHelper.clearRepo();
}
Map<String, String> header = new HashMap<>();
// Get cached ETag to add in the request header
String etag = prefs.getString(ETAG_KEY, "");
header.put("If-None-Match", etag);
// Make a request to main URL for repo info
String jsonString = WebService.request(REPO_URL, WebService.GET, null, header, false);
ValueSortedMap<String, Repo> cached = dbHelper.getRepoMap(false), fetched = new ValueSortedMap<>();
if (!TextUtils.isEmpty(jsonString)) {
try {
JSONArray jsonArray = new JSONArray(jsonString);
// If it gets to this point, the response is valid, update ETag
etag = WebService.getLastResponseHeader().get(ETAG_KEY).get(0);
// Maybe bug in Android build tools, sometimes the ETag has crap in it...
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
// Update repo info
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;
try {
updatedDate = format.parse(lastUpdate);
} catch (ParseException e) {
continue;
}
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);
Logger.dev("LoadRepos: Update cached repo " + id);
repo.update(updatedDate);
}
if (repo.getId() != null) {
fetched.put(id, repo);
}
} catch (BaseModule.CacheModException ignored) {}
// Update the database
dbHelper.addRepoMap(fetched);
// The leftover cached are those removed remote, cleanup db
dbHelper.removeRepo(cached);
// Update ETag
prefs.edit().putString(ETAG_KEY, etag).apply();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
magiskManager.repoMap = dbHelper.getRepoMap();
Logger.dev("LoadRepos: Repo load done");
return null;
}
@Override
protected void onPostExecute(Void v) {
magiskManager.repoLoadDone.trigger();
}
}

View File

@@ -0,0 +1,59 @@
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");
}
}

View File

@@ -0,0 +1,24 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Activity activity;
protected MagiskManager magiskManager;
public ParallelTask() {}
public ParallelTask(Activity context) {
activity = context;
magiskManager = Utils.getMagiskManager(context);
}
@SafeVarargs
public final void exec(Params... params) {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
}

View File

@@ -0,0 +1,96 @@
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);
}
}
}

View File

@@ -0,0 +1,116 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.widget.Toast;
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;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
private Uri mUri;
private ProgressDialog progressDialog;
private boolean mInstall;
public ProcessRepoZip(Activity context, Uri uri, boolean install) {
super(context);
mUri = uri;
mInstall = install;
}
@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_process_title),
activity.getString(R.string.zip_process_msg));
}
@Override
protected Boolean doInBackground(Void... params) {
FileInputStream in;
FileOutputStream out;
try {
// Create temp file
File temp1 = new File(magiskManager.getCacheDir(), "1.zip");
File temp2 = new File(magiskManager.getCacheDir(), "2.zip");
if (magiskManager.getCacheDir().mkdirs()) {
temp1.createNewFile();
temp2.createNewFile();
}
out = new FileOutputStream(temp1);
// First remove top folder in Github source zip, Uri -> temp1
ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), out);
out.flush();
out.close();
out = new FileOutputStream(temp2);
// Then sign the zip for the first time, temp1 -> temp2
ZipUtils.signZip(activity, temp1, out, false);
out.flush();
out.close();
// Adjust the zip to prevent unzip issues, temp2 -> temp2
ZipUtils.adjustZip(temp2);
out = new FileOutputStream(temp1);
// Finally, sign the whole zip file again, temp2 -> temp1
ZipUtils.signZip(activity, temp2, out, true);
out.flush();
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];
int length;
if (target == null) throw new FileNotFoundException();
while ((length = in.read(buffer)) > 0)
target.write(buffer, 0, length);
}
// Delete the temp file
temp1.delete();
temp2.delete();
return true;
} catch (Exception e) {
Logger.error("ProcessRepoZip: Error!");
e.printStackTrace();
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
if (Shell.rootAccess() && mInstall) {
new FlashZip(activity, mUri).exec();
} else {
Utils.showUriSnack(activity, mUri);
}
} else {
Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show();
}
}
}

View File

@@ -0,0 +1,30 @@
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);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.topjohnwu.magisk;
package com.topjohnwu.magisk.components;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,6 +26,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
/**
* @author dvdandroid
*/

View File

@@ -0,0 +1,13 @@
package com.topjohnwu.magisk.components;
import android.support.v7.app.AppCompatActivity;
import com.topjohnwu.magisk.MagiskManager;
public class Activity extends AppCompatActivity {
@Override
public MagiskManager getApplicationContext() {
return (MagiskManager) super.getApplicationContext();
}
}

View File

@@ -0,0 +1,161 @@
package com.topjohnwu.magisk.components;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.StyleRes;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import butterknife.BindView;
import butterknife.ButterKnife;
public class AlertDialogBuilder extends AlertDialog.Builder {
@BindView(R.id.button_panel) LinearLayout buttons;
@BindView(R.id.message_panel) LinearLayout messagePanel;
@BindView(R.id.negative) Button negative;
@BindView(R.id.positive) Button positive;
@BindView(R.id.neutral) Button neutral;
@BindView(R.id.message) TextView messageView;
@BindView(R.id.custom_view) ViewStub custom;
private DialogInterface.OnClickListener positiveListener;
private DialogInterface.OnClickListener negativeListener;
private DialogInterface.OnClickListener neutralListener;
private AlertDialog dialog;
public AlertDialogBuilder(@NonNull Context context) {
super(context);
setup();
}
public AlertDialogBuilder(@NonNull Context context, @StyleRes int themeResId) {
super(context, themeResId);
setup();
}
private void setup() {
View v = LayoutInflater.from(getContext()).inflate(R.layout.alert_dialog, null);
ButterKnife.bind(this, v);
super.setView(v);
negative.setVisibility(View.GONE);
positive.setVisibility(View.GONE);
neutral.setVisibility(View.GONE);
buttons.setVisibility(View.GONE);
messagePanel.setVisibility(View.GONE);
}
@Override
public AlertDialog.Builder setView(int layoutResId) {
custom.setLayoutResource(layoutResId);
custom.inflate();
return this;
}
@Override
public AlertDialog.Builder setView(View view) {
ViewGroup parent = (ViewGroup) custom.getParent();
int idx = parent.indexOfChild(custom);
parent.removeView(custom);
parent.addView(view, idx);
return this;
}
@Override
public AlertDialog.Builder setMessage(@Nullable CharSequence message) {
messageView.setText(message);
messagePanel.setVisibility(View.VISIBLE);
return this;
}
@Override
public AlertDialog.Builder setMessage(@StringRes int messageId) {
return setMessage(getContext().getString(messageId));
}
@Override
public AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
buttons.setVisibility(View.VISIBLE);
positive.setVisibility(View.VISIBLE);
positive.setText(text);
positiveListener = listener;
positive.setOnClickListener((v) -> {
if (positiveListener != null) {
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
}
dialog.dismiss();
});
return this;
}
@Override
public AlertDialog.Builder setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setPositiveButton(getContext().getString(textId), listener);
}
@Override
public AlertDialog.Builder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
buttons.setVisibility(View.VISIBLE);
negative.setVisibility(View.VISIBLE);
negative.setText(text);
negativeListener = listener;
negative.setOnClickListener((v) -> {
if (negativeListener != null) {
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
dialog.dismiss();
});
return this;
}
@Override
public AlertDialog.Builder setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setNegativeButton(getContext().getString(textId), listener);
}
@Override
public AlertDialog.Builder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
buttons.setVisibility(View.VISIBLE);
neutral.setVisibility(View.VISIBLE);
neutral.setText(text);
neutralListener = listener;
neutral.setOnClickListener((v) -> {
if (neutralListener != null) {
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
}
dialog.dismiss();
});
return this;
}
@Override
public AlertDialog.Builder setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setNeutralButton(getContext().getString(textId), listener);
}
@Override
public AlertDialog create() {
dialog = super.create();
return dialog;
}
@Override
public AlertDialog show() {
create();
dialog.show();
return dialog;
}
}

View File

@@ -0,0 +1,12 @@
package com.topjohnwu.magisk.components;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
public class Fragment extends android.support.v4.app.Fragment {
public MagiskManager getApplication() {
return Utils.getMagiskManager(getActivity());
}
}

View File

@@ -0,0 +1,31 @@
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();
}
}

View File

@@ -0,0 +1,39 @@
package com.topjohnwu.magisk.components;
import android.app.Activity;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import butterknife.ButterKnife;
public class SnackbarMaker {
public static Snackbar make(Activity activity, CharSequence text, int duration) {
View view = activity.findViewById(android.R.id.content);
return make(view, text, duration);
}
public static Snackbar make(Activity activity, @StringRes int resId, int duration) {
return make(activity, activity.getString(resId), duration);
}
public static Snackbar make(View view, CharSequence text, int duration) {
Snackbar snack = Snackbar.make(view, text, duration);
setup(snack);
return snack;
}
public static Snackbar make(View view, @StringRes int resId, int duration) {
Snackbar snack = Snackbar.make(view, resId, duration);
setup(snack);
return snack;
}
private static void setup(Snackbar snack) {
TextView text = ButterKnife.findById(snack.getView(), android.support.design.R.id.snackbar_text);
text.setMaxLines(Integer.MAX_VALUE);
}
}

View File

@@ -0,0 +1,90 @@
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.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import java.util.Collection;
public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 2;
private static final String TABLE_NAME = "repos";
public RepoDatabaseHelper(Context context) {
super(context, "repo.db", null, DATABASE_VER);
}
@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 TEXT, name TEXT, version TEXT, versionCode INT, " +
"author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
"PRIMARY KEY(id))");
oldVersion++;
}
if (oldVersion == 1) {
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD template INT");
oldVersion++;
}
}
public void addRepoMap(ValueSortedMap<String, Repo> map) {
SQLiteDatabase db = getWritableDatabase();
Collection<Repo> list = map.values();
for (Repo repo : list) {
db.replace(TABLE_NAME, null, repo.getContentValues());
}
db.close();
}
public void clearRepo() {
SQLiteDatabase db = getWritableDatabase();
db.delete(TABLE_NAME, null, null);
db.close();
}
public void removeRepo(ValueSortedMap<String, Repo> map) {
SQLiteDatabase db = getWritableDatabase();
Collection<Repo> list = map.values();
for (Repo repo : list) {
db.delete(TABLE_NAME, "id=?", new String[] { repo.getId() });
}
db.close();
}
public ValueSortedMap<String, Repo> getRepoMap() {
return getRepoMap(true);
}
public ValueSortedMap<String, Repo> getRepoMap(boolean filtered) {
ValueSortedMap<String, Repo> ret = new ValueSortedMap<>();
SQLiteDatabase db = getReadableDatabase();
Repo repo;
try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, null)) {
while (c.moveToNext()) {
repo = new Repo(c);
Logger.dev("Load from cache: " + repo.getId());
if (repo.getTemplateVersion() < 3 && filtered) {
Logger.dev("Outdated repo: " + repo.getId());
continue;
}
ret.put(repo.getId(), repo);
}
}
db.close();
return ret;
}
}

View File

@@ -0,0 +1,85 @@
package com.topjohnwu.magisk.database;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.superuser.Policy;
import java.util.ArrayList;
import java.util.List;
public class SuDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 1;
private static final String TABLE_NAME = "policies";
public SuDatabaseHelper(Context context) {
super(context, "su.db", null, DATABASE_VER);
}
@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 + " " +
"(uid INT, package_name TEXT, app_name TEXT, policy INT, " +
"until INT, logging INT, notification INT, " +
"PRIMARY KEY(uid))");
}
// Currently new database, no upgrading
}
public boolean deletePolicy(int uid) {
SQLiteDatabase db = getWritableDatabase();
db.delete(TABLE_NAME, "uid=?", new String[] { String.valueOf(uid) });
db.close();
return getPolicy(uid) == null;
}
public Policy getPolicy(int uid) {
Policy policy = null;
SQLiteDatabase db = getReadableDatabase();
try (Cursor c = db.query(TABLE_NAME, null, "uid=?", new String[] { String.valueOf(uid) }, null, null, null)) {
if (c.moveToNext()) {
policy = new Policy(c);
}
}
db.close();
return policy;
}
public void addPolicy(Policy policy) {
SQLiteDatabase db = getWritableDatabase();
db.replace(TABLE_NAME, null, policy.getContentValues());
db.close();
}
public List<Policy> getPolicyList(PackageManager pm) {
List<Policy> ret = new ArrayList<>();
SQLiteDatabase db = getWritableDatabase();
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()) {
policy = new Policy(c);
// Package is uninstalled
if (pm.getPackagesForUid(policy.uid) == null) {
deletePolicy(policy.uid);
} else {
ret.add(policy);
}
}
}
db.close();
return ret;
}
}

View File

@@ -0,0 +1,76 @@
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;
}
}

View File

@@ -1,6 +1,7 @@
package com.topjohnwu.magisk.module;
import android.database.Cursor;
import android.support.annotation.NonNull;
import com.topjohnwu.magisk.utils.Logger;
@@ -9,66 +10,76 @@ import java.util.List;
public abstract class BaseModule implements Comparable<BaseModule> {
protected String mId, mName, mVersion, mAuthor, mDescription, mSupportUrl, mDonateUrl;
protected boolean mIsCacheModule = false;
protected int mVersionCode = 0;
private String mId, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = 0, templateVersion = 0;
protected BaseModule() {}
protected BaseModule(Cursor c) {
mId = c.getString(c.getColumnIndex("id"));
mName = c.getString(c.getColumnIndex("name"));
mVersion = c.getString(c.getColumnIndex("version"));
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
mAuthor = c.getString(c.getColumnIndex("author"));
mDescription = c.getString(c.getColumnIndex("description"));
templateVersion = c.getInt(c.getColumnIndex("template"));
}
protected void parseProps(List<String> props) throws CacheModException { parseProps(props.toArray(new String[props.size()])); }
protected void parseProps(String[] props) throws CacheModException {
for (String line : props) {
String[] prop = line.split("=", 2);
if (prop.length != 2) {
if (prop.length != 2)
continue;
}
String key = prop[0].trim();
if (key.charAt(0) == '#') {
if (key.charAt(0) == '#')
continue;
}
switch (key) {
case "id":
this.mId = prop[1];
mId = prop[1];
break;
case "name":
this.mName = prop[1];
mName = prop[1];
break;
case "version":
this.mVersion = prop[1];
mVersion = prop[1];
break;
case "versionCode":
try {
this.mVersionCode = Integer.parseInt(prop[1]);
mVersionCode = Integer.parseInt(prop[1]);
} catch (NumberFormatException ignored) {}
break;
case "author":
this.mAuthor = prop[1];
mAuthor = prop[1];
break;
case "description":
this.mDescription = prop[1];
break;
case "support":
this.mSupportUrl = prop[1];
break;
case "donate":
this.mDonateUrl = prop[1];
mDescription = prop[1];
break;
case "template":
try {
templateVersion = Integer.parseInt(prop[1]);
} catch (NumberFormatException ignored) {}
case "cacheModule":
this.mIsCacheModule = Boolean.parseBoolean(prop[1]);
if (Boolean.parseBoolean(prop[1]))
throw new CacheModException(mId);
break;
default:
break;
}
}
if (mIsCacheModule)
throw new CacheModException(mId);
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getVersion() {
return mVersion;
}
@@ -77,7 +88,13 @@ public abstract class BaseModule implements Comparable<BaseModule> {
return mAuthor;
}
public String getId() {return mId; }
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getDescription() {
return mDescription;
@@ -87,22 +104,18 @@ public abstract class BaseModule implements Comparable<BaseModule> {
return mVersionCode;
}
public String getDonateUrl() {
return mDonateUrl;
}
public String getSupportUrl() {
return mSupportUrl;
public int getTemplateVersion() {
return templateVersion;
}
public static class CacheModException extends Exception {
public CacheModException(String id) {
Logger.dev("Cache mods are no longer supported! id: " + id);
Logger.error("Cache mods are no longer supported! id: " + id);
}
}
@Override
public int compareTo(@NonNull BaseModule o) {
return this.getName().toLowerCase().compareTo(o.getName().toLowerCase());
public int compareTo(@NonNull BaseModule module) {
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
}
}

View File

@@ -16,15 +16,16 @@ public class Module extends BaseModule {
mDisableFile = path + "/disable";
mUpdateFile = path + "/update";
if (mId == null) {
if (getId() == null) {
int sep = path.lastIndexOf('/');
mId = path.substring(sep + 1);
setId(path.substring(sep + 1));
}
if (mName == null)
mName = mId;
if (getName() == null) {
setName(getId());
}
Logger.dev("Creating Module, id: " + mId);
Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(mDisableFile);
mRemove = Utils.itemExist(mRemoveFile);

View File

@@ -1,53 +1,72 @@
package com.topjohnwu.magisk.module;
import android.content.Context;
import android.content.ContentValues;
import android.database.Cursor;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.WebService;
import java.util.Date;
public class Repo extends BaseModule {
private String mLogUrl, mManifestUrl, mZipUrl;
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 String repoName;
private Date mLastUpdate;
public Repo(Context context, String name, Date lastUpdate) throws CacheModException {
public Repo(String name, Date lastUpdate) throws CacheModException {
mLastUpdate = lastUpdate;
mLogUrl = context.getString(R.string.file_url, name, "changelog.txt");
mManifestUrl = context.getString(R.string.file_url, name, "module.prop");
mZipUrl = context.getString(R.string.zip_url, name);
repoName = name;
update();
}
public Repo(Cursor c) {
super(c);
repoName = c.getString(c.getColumnIndex("repo_name"));
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
}
public void update() throws CacheModException {
Logger.dev("Repo: Re-fetch prop");
String props = WebService.request(mManifestUrl, WebService.GET, true);
String props = WebService.request(getManifestUrl(), WebService.GET, true);
String lines[] = props.split("\\n");
parseProps(lines);
}
public void update(Date lastUpdate) throws CacheModException {
Logger.dev("Repo: Old: " + mLastUpdate);
Logger.dev("Repo: New: " + lastUpdate);
if (mIsCacheModule)
throw new CacheModException(mId);
Logger.dev("Repo: Local: " + mLastUpdate + " Remote: " + lastUpdate);
if (lastUpdate.after(mLastUpdate)) {
mLastUpdate = lastUpdate;
update();
}
}
public String getZipUrl() {
return mZipUrl;
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
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("last_update", mLastUpdate.getTime());
values.put("template", getTemplateVersion());
return values;
}
public String getLogUrl() {
return mLogUrl;
public String getZipUrl() {
return String.format(ZIP_URL, repoName);
}
public String getManifestUrl() {
return mManifestUrl;
return String.format(FILE_URL, repoName, "module.prop");
}
public String getDetailUrl() {
return String.format(FILE_URL, repoName, "README.md");
}
public Date getLastUpdate() {

View File

@@ -0,0 +1,16 @@
package com.topjohnwu.magisk.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.topjohnwu.magisk.services.BootupIntentService;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, BootupIntentService.class));
}
}

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.receivers;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -12,7 +13,7 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
public abstract class DownloadReceiver extends BroadcastReceiver {
public Context mContext;
public Activity activity;
public String mFilename;
long downloadID;
@@ -20,7 +21,7 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
activity = (Activity) context;
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {

View File

@@ -1,67 +0,0 @@
package com.topjohnwu.magisk.receivers;
import android.net.Uri;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.StatusFragment;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.File;
public class MagiskDlReceiver extends DownloadReceiver {
String mBoot;
boolean mEnc, mVerity;
public MagiskDlReceiver(String bootImage, boolean keepEnc, boolean keepVerity) {
mBoot = bootImage;
mEnc = keepEnc;
mVerity = keepVerity;
}
@Override
public void onDownloadDone(Uri uri) {
new Async.FlashZIP(mContext, uri, mFilename) {
@Override
protected void preProcessing() throws Throwable {
Shell.su(
"echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" > /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
);
}
@Override
protected boolean unzipAndCheck() {
publishProgress(mContext.getString(R.string.zip_install_unzip_zip_msg));
if (Shell.rootAccess()) {
// We might not have busybox yet, unzip with Java
// We will have complete busybox after Magisk installation
ZipUtils.unzip(mCachedFile, new File(mCachedFile.getParent(), "magisk"));
Shell.su(
"mkdir -p " + Async.TMP_FOLDER_PATH + "/magisk",
"cp -af " + mCachedFile.getParent() + "/magisk/. " + Async.TMP_FOLDER_PATH + "/magisk",
"mv -f " + mCachedFile.getParent() + "/magisk/META-INF " + mCachedFile.getParent() + "/META-INF"
);
}
return true;
}
@Override
protected void onSuccess() {
new Async.RootTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
Shell.su("setprop magisk.version "
+ String.valueOf(StatusFragment.remoteMagiskVersion));
return null;
}
}.exec();
super.onSuccess();
}
}.exec();
}
}

View File

@@ -1,42 +0,0 @@
package com.topjohnwu.magisk.receivers;
import android.net.Uri;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Async;
import com.topjohnwu.magisk.utils.Utils.ByteArrayInOutStream;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.OutputStream;
public class RepoDlReceiver extends DownloadReceiver {
@Override
public void onDownloadDone(Uri uri) {
// Flash the zip
new Async.FlashZIP(mContext, uri, mFilename){
@Override
protected void preProcessing() throws Throwable {
// Process and sign the zip
publishProgress(mContext.getString(R.string.zip_install_process_zip_msg));
ByteArrayInOutStream buffer = new ByteArrayInOutStream();
// First remove top folder (the folder with the repo name) in Github source zip
ZipUtils.removeTopFolder(mContext.getContentResolver().openInputStream(mUri), buffer);
// Then sign the zip for the first time
ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, false);
// Adjust the zip to prevent unzip issues
ZipUtils.adjustZip(buffer);
// Finally, sign the whole zip file again
ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, true);
// Write it back to the downloaded zip
OutputStream out = mContext.getContentResolver().openOutputStream(mUri);
buffer.writeTo(out);
out.close();
}
}.exec();
}
}

View File

@@ -0,0 +1,30 @@
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");
}
}
}

View File

@@ -0,0 +1,32 @@
package com.topjohnwu.magisk.services;
import android.app.job.JobParameters;
import android.app.job.JobService;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
public class UpdateCheckService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
new CheckUpdates(this, true){
@Override
protected Void doInBackground(Void... voids) {
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;
}
@Override
public boolean onStopJob(JobParameters params) {
return true;
}
}

View File

@@ -0,0 +1,51 @@
package com.topjohnwu.magisk.superuser;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
public class Policy {
public static final int INTERACTIVE = 0;
public static final int DENY = 1;
public static final int ALLOW = 2;
public int uid, policy;
public long until;
public boolean logging = true, notification = true;
public String packageName, appName;
public PackageInfo info;
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
String[] pkgs = pm.getPackagesForUid(uid);
if (pkgs != null && pkgs.length > 0) {
info = pm.getPackageInfo(pkgs[0], 0);
this.uid = uid;
packageName = pkgs[0];
appName = info.applicationInfo.loadLabel(pm).toString();
} else throw new PackageManager.NameNotFoundException();
}
public Policy(Cursor c) {
uid = c.getInt(c.getColumnIndex("uid"));
packageName = c.getString(c.getColumnIndex("package_name"));
appName = c.getString(c.getColumnIndex("app_name"));
policy = c.getInt(c.getColumnIndex("policy"));
until = c.getLong(c.getColumnIndex("until"));
logging = c.getInt(c.getColumnIndex("logging")) != 0;
notification = c.getInt(c.getColumnIndex("notification")) != 0;
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("uid", uid);
values.put("package_name", packageName);
values.put("app_name", appName);
values.put("policy", policy);
values.put("until", until);
values.put("logging", logging ? 1 : 0);
values.put("notification", notification ? 1 : 0);
return values;
}
}

View File

@@ -0,0 +1,25 @@
package com.topjohnwu.magisk.superuser;
import android.content.Intent;
import android.os.Bundle;
import com.topjohnwu.magisk.components.Activity;
public class RequestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent == null) {
finish();
return;
}
getApplicationContext().initSuConfigs();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClass(this, SuRequestActivity.class);
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,98 @@
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());
}
}

View File

@@ -0,0 +1,85 @@
package com.topjohnwu.magisk.superuser;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.database.SuLogDatabaseHelper;
import java.util.Date;
public class SuReceiver extends BroadcastReceiver {
private static final int NO_NOTIFICATION = 0;
private static final int TOAST = 1;
@Override
public void onReceive(Context context, Intent intent) {
int fromUid, toUid, pid;
String command, action;
Policy policy;
MagiskManager magiskManager = (MagiskManager) context.getApplicationContext();
if (intent == null) return;
fromUid = intent.getIntExtra("from.uid", -1);
if (fromUid < 0) return;
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
action = intent.getStringExtra("action");
if (action == null) return;
SuDatabaseHelper suDbHelper = new SuDatabaseHelper(context);
policy = suDbHelper.getPolicy(fromUid);
if (policy == null) {
try {
policy = new Policy(fromUid, context.getPackageManager());
} catch (Throwable throwable) {
return;
}
}
magiskManager.initSuConfigs();
SuLogEntry log = new SuLogEntry(policy);
String message;
switch (action) {
case "allow":
message = context.getString(R.string.su_allow_toast, policy.appName);
log.action = true;
break;
case "deny":
message = context.getString(R.string.su_deny_toast, policy.appName);
log.action = false;
break;
default:
return;
}
if (policy.notification && magiskManager.suNotificationType == TOAST)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
if (policy.logging) {
toUid = intent.getIntExtra("to.uid", -1);
if (toUid < 0) return;
pid = intent.getIntExtra("pid", -1);
if (pid < 0) return;
command = intent.getStringExtra("command");
if (command == null) return;
log.toUid = toUid;
log.fromPid = pid;
log.command = command;
log.date = new Date();
SuLogDatabaseHelper logDbHelper = new SuLogDatabaseHelper(context);
logDbHelper.addLog(log);
}
}
}

View File

@@ -0,0 +1,260 @@
package com.topjohnwu.magisk.superuser;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.FileObserver;
import android.text.TextUtils;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.utils.CallbackEvent;
import java.io.DataInputStream;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SuRequestActivity extends Activity implements CallbackEvent.Listener<Policy> {
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.timeout) Spinner timeout;
@BindView(R.id.app_icon) ImageView appIcon;
@BindView(R.id.app_name) TextView appNameView;
@BindView(R.id.package_name) TextView packageNameView;
@BindView(R.id.grant_btn) Button grant_btn;
@BindView(R.id.deny_btn) Button deny_btn;
private String socketPath;
private LocalSocket socket;
private PackageManager pm;
private MagiskManager magiskManager;
private int uid;
private Policy policy;
private CountDownTimer timer;
private CallbackEvent.Listener<Policy> self;
private CallbackEvent<Policy> event = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
pm = getPackageManager();
magiskManager = getApplicationContext();
Intent intent = getIntent();
socketPath = intent.getStringExtra("socket");
self = this;
new FileObserver(socketPath) {
@Override
public void onEvent(int fileEvent, String path) {
if (fileEvent == FileObserver.DELETE_SELF) {
if (event != null) {
event.trigger();
}
finish();
}
}
}.startWatching();
new SocketManager(this).exec();
}
void showRequest() {
switch (magiskManager.suResponseType) {
case AUTO_DENY:
handleAction(Policy.DENY, 0);
return;
case AUTO_ALLOW:
handleAction(Policy.ALLOW, 0);
return;
case PROMPT:
default:
}
setContentView(R.layout.activity_request);
ButterKnife.bind(this);
appIcon.setImageDrawable(policy.info.applicationInfo.loadIcon(pm));
appNameView.setText(policy.appName);
packageNameView.setText(policy.packageName);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.allow_timeout, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
timeout.setAdapter(adapter);
timer = new CountDownTimer(magiskManager.suRequestTimeout * 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")"));
}
@Override
public void onFinish() {
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
event.trigger();
}
};
grant_btn.setOnClickListener(v -> handleAction(Policy.ALLOW));
deny_btn.setOnClickListener(v -> handleAction(Policy.DENY));
suPopup.setOnClickListener((v) -> {
timer.cancel();
deny_btn.setText(getString(R.string.deny));
});
timeout.setOnTouchListener((v, event) -> {
timer.cancel();
deny_btn.setText(getString(R.string.deny));
return false;
});
timer.start();
}
@Override
public void onBackPressed() {
event.trigger();
}
@Override
public void onTrigger(CallbackEvent<Policy> event) {
Policy policy = event.getResult();
String response = "socket:DENY";
if (policy != null &&policy.policy == Policy.ALLOW ) {
response = "socket:ALLOW";
}
try {
socket.getOutputStream().write((response).getBytes());
} catch (Exception ignored) {}
finish();
}
void handleAction(int action) {
handleAction(action, timeoutList[timeout.getSelectedItemPosition()]);
}
void handleAction(int action, int time) {
policy.policy = action;
event.trigger(policy);
if (time >= 0) {
policy.until = time == 0 ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
new SuDatabaseHelper(this).addPolicy(policy);
}
}
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
public SocketManager(Activity context) {
super(context);
}
@Override
protected Boolean doInBackground(Void... params) {
try {
socket = new LocalSocket();
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
DataInputStream is = new DataInputStream(socket.getInputStream());
ContentValues payload = new ContentValues();
for (int i = 0; i < SU_PROTOCOL_PARAM_MAX; i++) {
int nameLen = is.readInt();
if (nameLen > SU_PROTOCOL_NAME_MAX)
throw new IllegalArgumentException("name length too long: " + nameLen);
byte[] nameBytes = new byte[nameLen];
is.readFully(nameBytes);
String name = new String(nameBytes);
if (TextUtils.equals(name, "eof"))
break;
int dataLen = is.readInt();
if (dataLen > SU_PROTOCOL_VALUE_MAX)
throw new IllegalArgumentException(name + " data length too long: " + dataLen);
byte[] dataBytes = new byte[dataLen];
is.readFully(dataBytes);
String data = new String(dataBytes);
payload.put(name, data);
}
if (payload.getAsInteger("uid") == null)
return false;
uid = payload.getAsInteger("uid");
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
if (!result) {
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();
}
}
}
}

View File

@@ -1,340 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.OpenableColumns;
import android.util.Log;
import android.widget.Toast;
import com.topjohnwu.magisk.InstallFragment;
import com.topjohnwu.magisk.MagiskHideFragment;
import com.topjohnwu.magisk.ModulesFragment;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ReposFragment;
import com.topjohnwu.magisk.StatusFragment;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Async {
public abstract static class RootTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
@SafeVarargs
public final void exec(Params... params) {
if (!Shell.rootAccess()) return;
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);
}
}
public abstract static class NormalTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
@SafeVarargs
public final void exec(Params... params) {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
}
public static final String UPDATE_JSON = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/updates/magisk_update.json";
public static final String MAGISK_HIDE_PATH = "/magisk/.core/magiskhide/";
public static final String TMP_FOLDER_PATH = "/dev/tmp";
public static class CheckUpdates extends NormalTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
try {
JSONObject json = new JSONObject(jsonStr);
JSONObject magisk = json.getJSONObject("magisk");
StatusFragment.remoteMagiskVersion = magisk.getDouble("versionCode");
StatusFragment.magiskLink = magisk.getString("link");
StatusFragment.releaseNoteLink = magisk.getString("note");
} catch (JSONException ignored) {}
return null;
}
@Override
protected void onPostExecute(Void v) {
StatusFragment.updateCheckDone.trigger();
}
}
public static void checkSafetyNet(Context context) {
new SafetyNetHelper(context) {
@Override
public void handleResults(int i) {
StatusFragment.SNCheckResult = i;
StatusFragment.safetyNetDone.trigger();
}
}.requestTest();
}
public static class LoadModules extends RootTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
ModuleHelper.createModuleMap();
return null;
}
@Override
protected void onPostExecute(Void v) {
ModulesFragment.moduleLoadDone.trigger();
}
}
public static class LoadRepos extends NormalTask<Void, Void, Void> {
private Context mContext;
public LoadRepos(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... voids) {
ModuleHelper.createRepoMap(mContext);
return null;
}
@Override
protected void onPostExecute(Void v) {
ReposFragment.repoLoadDone.trigger();
}
}
public static class LoadApps extends RootTask<Void, Void, LoadApps.Result> {
private PackageManager pm;
public LoadApps(PackageManager packageManager) {
pm = packageManager;
}
@Override
protected Result doInBackground(Void... voids) {
List<ApplicationInfo> listApps = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (Iterator<ApplicationInfo> i = listApps.iterator(); i.hasNext(); ) {
ApplicationInfo info = i.next();
if (MagiskHideFragment.BLACKLIST.contains(info.packageName) || !info.enabled)
i.remove();
}
Collections.sort(listApps, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
List<String> hideList = Shell.su(Async.MAGISK_HIDE_PATH + "list");
return new Result(listApps, hideList);
}
@Override
protected void onPostExecute(Result result) {
MagiskHideFragment.packageLoadDone.trigger(result);
}
public static class Result {
public final List<ApplicationInfo> listApps;
public final List<String> hideList;
Result(List<ApplicationInfo> listApps, List<String> hideList) {
this.listApps = listApps;
this.hideList = hideList;
}
}
}
public static class FlashZIP extends RootTask<Void, String, Integer> {
protected Uri mUri;
protected File mCachedFile;
private String mFilename;
protected ProgressDialog progress;
private Context mContext;
public FlashZIP(Context context, Uri uri, String filename) {
mContext = context;
mUri = uri;
mFilename = filename;
}
public FlashZIP(Context context, Uri uri) {
mContext = context;
mUri = uri;
// Try to get the filename ourselves
Cursor c = mContext.getContentResolver().query(uri, null, null, null, null);
if (c != null) {
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
c.moveToFirst();
if (nameIndex != -1) {
mFilename = c.getString(nameIndex);
}
c.close();
}
if (mFilename == null) {
int idx = uri.getPath().lastIndexOf('/');
mFilename = uri.getPath().substring(idx + 1);
}
}
protected void preProcessing() throws Throwable {}
protected void copyToCache() throws Throwable {
publishProgress(mContext.getString(R.string.copying_msg));
try {
InputStream in = mContext.getContentResolver().openInputStream(mUri);
mCachedFile = new File(mContext.getCacheDir().getAbsolutePath() + "/install.zip");
if (mCachedFile.exists() && !mCachedFile.delete()) {
throw new IOException();
}
OutputStream outputStream = new FileOutputStream(mCachedFile);
byte buffer[] = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath());
in.close();
} catch (FileNotFoundException e) {
Log.e(Logger.TAG, "FlashZip: Invalid Uri");
throw e;
} catch (IOException e) {
Log.e(Logger.TAG, "FlashZip: Error in creating file");
throw e;
}
}
protected boolean unzipAndCheck() {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
return Utils.readFile(mCachedFile.getParent() + "/META-INF/com/google/android/updater-script")
.get(0).contains("#MAGISK");
}
@Override
protected void onPreExecute() {
progress = new ProgressDialog(mContext);
progress.setTitle(R.string.zip_install_progress_title);
progress.show();
}
@Override
protected void onProgressUpdate(String... values) {
progress.setMessage(values[0]);
}
@Override
protected Integer doInBackground(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename);
List<String> ret;
try {
preProcessing();
copyToCache();
} catch (Throwable e) {
e.printStackTrace();
return -1;
}
if (!unzipAndCheck()) return 0;
publishProgress(mContext.getString(R.string.zip_install_progress_msg, mFilename));
ret = Shell.su(
"BOOTMODE=true sh " + mCachedFile.getParent() +
"/META-INF/com/google/android/update-binary dummy 1 " + mCachedFile.getPath(),
"if [ $? -eq 0 ]; then echo true; else echo false; fi"
);
Logger.dev("FlashZip: Console log:");
for (String line : ret) {
Logger.dev(line);
}
Shell.su(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + TMP_FOLDER_PATH
);
if (Boolean.parseBoolean(ret.get(ret.size() - 1))) {
return 1;
}
return -1;
}
// -1 = error, manual install; 0 = invalid zip; 1 = success
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
progress.dismiss();
switch (result) {
case -1:
Toast.makeText(mContext, mContext.getString(R.string.install_error), Toast.LENGTH_LONG).show();
Toast.makeText(mContext, mContext.getString(R.string.manual_install_1, mUri.getPath()), Toast.LENGTH_LONG).show();
Toast.makeText(mContext, mContext.getString(R.string.manual_install_2), Toast.LENGTH_LONG).show();
break;
case 0:
Toast.makeText(mContext, mContext.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
break;
case 1:
onSuccess();
break;
}
}
protected void onSuccess() {
StatusFragment.updateCheckDone.trigger();
new LoadModules().exec();
Utils.getAlertDialogBuilder(mContext)
.setTitle(R.string.reboot_title)
.setMessage(R.string.reboot_msg)
.setPositiveButton(R.string.reboot, (dialogInterface1, i) -> Shell.sh("su -c reboot"))
.setNegativeButton(R.string.no_thanks, null)
.show();
}
}
public static class MagiskHide extends RootTask<Object, Void, Void> {
@Override
protected Void doInBackground(Object... params) {
boolean add = (boolean) params[0];
String packageName = (String) params[1];
Shell.su(MAGISK_HIDE_PATH + (add ? "add " : "rm ") + packageName);
return null;
}
public void add(CharSequence packageName) {
exec(true, packageName);
}
public void rm(CharSequence packageName) {
exec(false, packageName);
}
}
public static class GetBootBlocks extends RootTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
if (Shell.rootAccess()) {
InstallFragment.blockList = Shell.su("ls /dev/block | grep mmc");
if (InstallFragment.bootBlock == null) {
InstallFragment.bootBlock = Utils.detectBootImage();
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
InstallFragment.blockDetectionDone.trigger();
}
}
}

View File

@@ -0,0 +1,18 @@
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;
}
}

View File

@@ -0,0 +1,50 @@
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);
}
}

View File

@@ -1,58 +0,0 @@
package com.topjohnwu.magisk.utils;
import java.util.HashMap;
import java.util.HashSet;
public class CallbackHandler {
private static HashMap<Event, HashSet<EventListener>> listeners = new HashMap<>();
public static void register(Event event, EventListener listener) {
HashSet<EventListener> list = listeners.get(event);
if (list == null) {
list = new HashSet<>();
listeners.put(event, list);
}
list.add(listener);
}
public static void unRegister(Event event, EventListener listener) {
HashSet<EventListener> list = listeners.get(event);
if (list != null) {
list.remove(listener);
}
}
private static void triggerCallback(Event event) {
HashSet<EventListener> list = listeners.get(event);
if (list != null) {
for (EventListener listener : list) {
listener.onTrigger(event);
}
}
}
public static class Event {
public boolean isTriggered = false;
private Object result;
public void trigger() {
trigger(null);
}
public void trigger(Object result) {
this.result = result;
isTriggered = true;
triggerCallback(this);
}
public Object getResult() {
return result;
}
}
public interface EventListener {
void onTrigger(Event event);
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk;
package com.topjohnwu.magisk.utils;
import android.content.Context;
@@ -23,9 +23,10 @@ import java.util.List;
* FabMenu in a Coordinator Layout.
*
* Remember to use the correct namespace for the fab:
* xmlns:fab="http://schemas.android.com/apk/res-auto"
* xmlns:app="http://schemas.android.com/apk/res-auto"
*/
public class FABBehavior extends CoordinatorLayout.Behavior {
public class FABBehavior extends CoordinatorLayout.Behavior<View> {
private float mTranslationY;
public FABBehavior(Context context, AttributeSet attrs) {
@@ -74,11 +75,11 @@ public class FABBehavior extends CoordinatorLayout.Behavior {
private float getTranslationY(CoordinatorLayout parent, View child) {
float minOffset = 0.0F;
List dependencies = parent.getDependencies(child);
List<View> dependencies = parent.getDependencies(child);
int i = 0;
for (int z = dependencies.size(); i < z; ++i) {
View view = (View) dependencies.get(i);
View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight());
}

View File

@@ -2,36 +2,38 @@ package com.topjohnwu.magisk.utils;
import android.util.Log;
import com.topjohnwu.magisk.MagiskManager;
public class Logger {
public static final String TAG = "Magisk";
public static final String DEV_TAG = "Magisk: DEV";
public static final String DEBUG_TAG = "Magisk: DEBUG";
public static boolean logShell, devLog;
public static void debug(String msg) {
Log.d(DEBUG_TAG, msg);
Log.d(TAG, "DEBUG: " + msg);
}
public static void error(String msg) {
Log.e(TAG, "ERROR: " + msg);
}
public static void dev(String msg, Object... args) {
if (devLog) {
if (MagiskManager.devLogging) {
if (args.length == 1 && args[0] instanceof Throwable) {
Log.d(DEV_TAG, msg, (Throwable) args[0]);
Log.d(TAG, "DEV: " + msg, (Throwable) args[0]);
} else {
Log.d(DEV_TAG, String.format(msg, args));
Log.d(TAG, "DEV: " + String.format(msg, args));
}
}
}
public static void dev(String msg) {
if (devLog) {
Log.d(DEV_TAG, msg);
if (MagiskManager.devLogging) {
Log.d(TAG, "DEV: " + msg);
}
}
public static void shell(boolean root, String msg) {
if (logShell) {
if (MagiskManager.shellLogging) {
Log.d(root ? "SU" : "SH", msg);
}
}

View File

@@ -1,203 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class ModuleHelper {
private static final String MAGISK_PATH = "/magisk";
public static final String FILE_KEY = "RepoMap";
private static final String REPO_KEY = "repomap";
public static final String VERSION_KEY = "version";
public static final String ETAG_KEY = "ETag";
private static final int DATABASE_VER = 1;
private static ValueSortedMap<String, Repo> repoMap = new ValueSortedMap<>();
private static ValueSortedMap<String, Module> moduleMap = new ValueSortedMap<>();
public static void createModuleMap() {
Logger.dev("ModuleHelper: Loading modules");
moduleMap.clear();
for (String path : Utils.getModList(MAGISK_PATH)) {
Logger.dev("ModuleHelper: Adding modules from " + path);
Module module;
try {
module = new Module(path);
moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
}
Logger.dev("ModuleHelper: Module load done");
}
public static void createRepoMap(Context context) {
Logger.dev("ModuleHelper: Loading repos");
SharedPreferences prefs = context.getSharedPreferences(FILE_KEY, Context.MODE_PRIVATE);
repoMap.clear();
Gson gson = new Gson();
String jsonString;
int cachedVersion = prefs.getInt(VERSION_KEY, 0);
if (cachedVersion != DATABASE_VER) {
// Ignore incompatible cached database
jsonString = null;
} else {
jsonString = prefs.getString(REPO_KEY, null);
}
ValueSortedMap<String, Repo> cached = null;
if (jsonString != null) {
cached = gson.fromJson(jsonString, new TypeToken<ValueSortedMap<String, Repo>>(){}.getType());
}
if (cached == null) {
cached = new ValueSortedMap<>();
}
// Get cached ETag to add in the request header
String etag = prefs.getString(ETAG_KEY, "");
HashMap<String, String> header = new HashMap<>();
header.put("If-None-Match", etag);
// Making a request to main URL for repo info
jsonString = WebService.request(
context.getString(R.string.url_main), WebService.GET, null, header, false);
if (!jsonString.isEmpty()) {
try {
JSONArray jsonArray = new JSONArray(jsonString);
// If it gets to this point, the response is valid, update ETag
etag = WebService.getLastResponseHeader().get(ETAG_KEY).get(0);
// Maybe bug in Android build tools, sometimes the ETag has crap in it...
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
// Update repo info
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;
try {
updatedDate = format.parse(lastUpdate);
} catch (ParseException e) {
continue;
}
Repo repo = cached.get(id);
try {
if (repo == null) {
Logger.dev("ModuleHelper: Create new repo " + id);
repo = new Repo(context, name, updatedDate);
} else {
Logger.dev("ModuleHelper: Update cached repo " + id);
repo.update(updatedDate);
}
if (repo.getId() != null) {
repoMap.put(id, repo);
}
} catch (BaseModule.CacheModException ignored) {}
}
} catch (JSONException e) {
e.printStackTrace();
}
} else {
// Use cached if no internet or no updates
Logger.dev("ModuleHelper: No updates, use cached");
repoMap.putAll(cached);
}
prefs.edit()
.putInt(VERSION_KEY, DATABASE_VER)
.putString(REPO_KEY, gson.toJson(repoMap))
.putString(ETAG_KEY, etag)
.apply();
Logger.dev("ModuleHelper: Repo load done");
}
public static void getModuleList(List<Module> moduleList) {
moduleList.clear();
moduleList.addAll(moduleMap.values());
}
public static void getRepoLists(List<Repo> update, List<Repo> installed, List<Repo> others) {
update.clear();
installed.clear();
others.clear();
for (Repo repo : repoMap.values()) {
Module module = moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode()) {
update.add(repo);
} else {
installed.add(repo);
}
} else {
others.add(repo);
}
}
}
private static class ValueSortedMap<K, V extends Comparable > extends HashMap<K, V> {
private List<V> sorted = new ArrayList<>();
@NonNull
@Override
public Collection<V> values() {
if (sorted.isEmpty()) {
sorted.addAll(super.values());
Collections.sort(sorted);
}
return sorted;
}
@Override
public V put(K key, V value) {
sorted.clear();
return super.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
sorted.clear();
super.putAll(m);
}
@Override
public V remove(Object key) {
sorted.clear();
return super.remove(key);
}
}
}

View File

@@ -15,16 +15,15 @@ public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus;
private static boolean isInit = false;
private static Process rootShell;
private static DataOutputStream rootSTDIN;
private static StreamGobbler rootSTDOUT;
private static List<String> rootOutList = new ArrayList<>();
private static List<String> rootOutList = Collections.synchronizedList(new ArrayList<String>());
static {
init();
}
public static void init() {
private static void init() {
isInit = true;
try {
rootShell = Runtime.getRuntime().exec("su");
@@ -41,7 +40,7 @@ public class Shell {
// Setup umask and PATH
su("umask 022");
su("PATH=/data/busybox:$PATH");
su("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || echo /data/busybox`:$PATH");
List<String> ret = su("echo -BOC-", "id");
@@ -64,7 +63,7 @@ public class Shell {
}
public static boolean rootAccess() {
return rootStatus > 0;
return isInit && rootStatus > 0;
}
public static List<String> sh(String... commands) {
@@ -120,7 +119,12 @@ public class Shell {
DataOutputStream STDIN;
StreamGobbler STDOUT;
if (!rootAccess()) {
// Create the default shell if not init
if (!newShell && !isInit) {
init();
}
if (!newShell && !rootAccess()) {
return null;
}
@@ -130,6 +134,13 @@ public class Shell {
process = Runtime.getRuntime().exec("su");
STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(process.getInputStream(), res);
// Run the new shell with busybox and proper umask
STDIN.write(("umask 022\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || " +
"echo /data/busybox`:$PATH\n").getBytes("UTF-8"));
STDIN.flush();
} catch (IOException err) {
return null;
}
@@ -170,6 +181,7 @@ public class Shell {
try {
// Process terminated, it means the interactive shell has some issues
process.exitValue();
rootStatus = -1;
return null;
} catch (IllegalThreadStateException e) {
// Process still running, gobble output until done
@@ -183,17 +195,22 @@ public class Shell {
break;
}
}
try { STDOUT.join(100); } catch (InterruptedException err) { return null; }
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;
}

View File

@@ -1,66 +1,63 @@
package com.topjohnwu.magisk.utils;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.List;
public class Utils {
public static boolean isDownloading = false;
public static boolean isDarkTheme;
public static boolean itemExist(String path) {
return itemExist(true, path);
}
public static boolean itemExist(boolean root, String path) {
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
if (Shell.rootAccess() && root) {
return Boolean.parseBoolean(Shell.su(command).get(0));
} else {
return new File(path).exists();
}
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static boolean commandExists(String s) {
List<String> ret;
String command = "if [ -z $(which " + s + ") ]; then echo false; else echo true; fi";
ret = Shell.sh(command);
return Boolean.parseBoolean(ret.get(0));
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 command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null; if [ -f \"" + path + "\" ]; then echo true; else echo false; fi";
return Shell.rootAccess() && Boolean.parseBoolean(Shell.su(command).get(0));
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static boolean removeItem(String path) {
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
return Shell.rootAccess() && Boolean.parseBoolean(Shell.su(command).get(0));
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static List<String> getModList(String path) {
List<String> ret;
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
ret = Shell.su(command);
return ret;
return Shell.su(command);
}
public static List<String> readFile(String path) {
@@ -75,9 +72,8 @@ public class Utils {
}
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
if (isDownloading) {
if (isDownloading)
return;
}
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
@@ -95,10 +91,12 @@ public class Utils {
isDownloading = true;
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
request.setDestinationUri(Uri.fromFile(file));
receiver.setDownloadID(downloadManager.enqueue(request));
if (link != null) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
request.setDestinationUri(Uri.fromFile(file));
receiver.setDownloadID(downloadManager.enqueue(request));
}
receiver.setFilename(filename);
context.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@@ -118,35 +116,73 @@ public class Utils {
"echo \"${BOOTIMAGE##*/}\""
};
List<String> ret = Shell.su(commands);
if (!ret.isEmpty()) {
if (isValidShellResponse(ret)) {
return ret.get(0);
}
return null;
}
public static AlertDialog.Builder getAlertDialogBuilder(Context context) {
if (isDarkTheme) {
return new AlertDialog.Builder(context, R.style.AlertDialog_dh);
} else {
return new AlertDialog.Builder(context);
}
}
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
}
public static 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;
public static boolean isValidShellResponse(List<String> list) {
if (list != null && list.size() != 0) {
// Check if all empty
for (String res : list) {
if (!TextUtils.isEmpty(res)) return true;
}
}
return false;
}
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
}
public static MagiskManager getMagiskManager(Context context) {
return (MagiskManager) context.getApplicationContext();
}
public static void checkSafetyNet(MagiskManager magiskManager) {
new SafetyNetHelper(magiskManager) {
@Override
public void handleResults(int i) {
magiskManager.SNCheckResult = i;
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) {
String name = null;
try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
if (c != null) {
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (nameIndex != -1) {
c.moveToFirst();
name = c.getString(nameIndex);
}
}
}
if (name == null) {
int idx = uri.getPath().lastIndexOf('/');
name = uri.getPath().substring(idx + 1);
}
return name;
}
public static void showUriSnack(Activity activity, Uri uri) {
SnackbarMaker.make(activity, activity.getString(R.string.internal_storage,
"/MagiskManager/" + Utils.getNameFromUri(activity, uri)),
Snackbar.LENGTH_LONG)
.setAction(R.string.ok, (v)->{}).show();
}
}

View File

@@ -0,0 +1,43 @@
package com.topjohnwu.magisk.utils;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ValueSortedMap<K, V extends Comparable<? super V>> extends HashMap<K, V> {
private List<V> sorted = new ArrayList<>();
@NonNull
@Override
public Collection<V> values() {
if (sorted.isEmpty()) {
sorted.addAll(super.values());
Collections.sort(sorted);
}
return sorted;
}
@Override
public V put(K key, V value) {
sorted.clear();
return super.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
sorted.clear();
super.putAll(m);
}
@Override
public V remove(Object key) {
sorted.clear();
return super.remove(key);
}
}

View File

@@ -77,10 +77,11 @@ public class WebService {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (first)
if (first) {
first = false;
else
} else {
result.append("&");
}
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
result.append("=");

View File

@@ -1,32 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class WebWindow {
public WebWindow(String title, String url, Context context) {
AlertDialog.Builder alert = Utils.getAlertDialogBuilder(context);
alert.setTitle(title);
Logger.dev("WebView: URL = " + url);
WebView wv = new WebView(context);
wv.loadUrl(url);
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(url);
return true;
}
});
alert.setView(wv);
alert.setNegativeButton("Close", (dialog, id) -> dialog.dismiss());
alert.show();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.6)
add_library(zipadjust SHARED
jni_glue.c
zipadjust.c)
find_library(libz z)
find_library(liblog log)
target_link_libraries(zipadjust ${libz} ${liblog})

View File

@@ -0,0 +1,60 @@
//
// Java entry point
//
#include <jni.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.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
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust__Ljava_lang_String_2(JNIEnv *env, jclass type, jstring name) {
const char *filename = (*env)->GetStringUTFChars(env, name, NULL);
int fd = open(filename, O_RDONLY);
if (fd < 0)
return;
// Load the file to memory
insize = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
fin = malloc(insize);
read(fd, fin, insize);
zipadjust(0);
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);
}

View File

@@ -1,35 +1,10 @@
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <zlib.h>
#include <android/log.h>
#include "zipadjust.h"
#define LOG_TAG "zipadjust"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
size_t insize, outsize = 0, alloc = 0;
unsigned char *fin, *fout;
int zipadjust(int decompress) ;
JNIEXPORT jbyteArray JNICALL
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust(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;
}
size_t insize = 0, outsize = 0, alloc = 0;
unsigned char *fin = NULL, *fout = NULL;
#pragma pack(1)
struct local_header_struct {

View File

@@ -0,0 +1,16 @@
#ifndef MAGISKMANAGER_ZIPADJUST_H_H
#define MAGISKMANAGER_ZIPADJUST_H_H
#include <android/log.h>
int zipadjust(int decompress);
extern size_t insize, outsize, alloc;
extern unsigned char *fin, *fout;
#define LOG_TAG "zipadjust"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif //MAGISKMANAGER_ZIPADJUST_H_H

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="#000000"
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
<path
android:pathData="M0-.75h24v24H0z" />
</vector>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#757575"
android:fillColor="#000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFd32f2f"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

View File

@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
</vector>

View File

@@ -7,7 +7,6 @@
<include layout="@layout/toolbar"/>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
@@ -57,35 +56,35 @@
android:textAppearance="@style/TextAppearance.AppCompat.Headline"/>
</LinearLayout>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/app_version_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_info_outline"
app:text="@string/app_version"/>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/app_changelog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_history"
app:text="@string/app_changelog"/>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/app_developers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_person"
app:text="@string/app_developers"/>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/app_translators"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_language"
app:text="@string/app_translators"/>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/app_source_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -110,14 +109,14 @@
android:layout_marginBottom="8dp"
android:orientation="vertical">
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/support_thread"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:icon="@drawable/ic_xda"
app:text="@string/support_thread"/>
<com.topjohnwu.magisk.AboutCardRow
<com.topjohnwu.magisk.components.AboutCardRow
android:id="@+id/donation"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -0,0 +1,117 @@
<?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:id="@+id/su_popup"
tools:context=".superuser.RequestActivity"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:minWidth="350dp"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:text="@string/su_request_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/request_title"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:layout_marginBottom="5dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_gravity="center_horizontal">
<ImageView
android:id="@+id/app_icon"
android:layout_weight="0"
android:layout_marginEnd="10dp"
android:layout_height="50dp"
android:layout_width="50dp"
android:layout_marginStart="5dp"
android:layout_gravity="center_vertical" />
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_weight="1"
android:layout_gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
android:maxWidth="300dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="?android:textColorPrimary"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/app_name" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="200dp"
android:maxWidth="300dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="?android:textColorSecondary"
android:id="@+id/package_name" />
</LinearLayout>
</LinearLayout>
<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/timeout"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_su_warning"
android:textColor="?android:textColorSecondary"
android:text="@string/su_warning"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:drawablePadding="10dp" />
<LinearLayout
style="?android:buttonBarStyle"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:paddingLeft="30dp"
android:paddingRight="30dp">
<Button
style="?android:buttonBarButtonStyle"
android:text="@string/deny_with_str"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deny_btn"
android:layout_weight="1" />
<Button
style="?android:buttonBarButtonStyle"
android:text="@string/grant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/grant_btn"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/message_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="ifContentScrolls"
android:paddingBottom="12dip"
android:paddingEnd="20dip"
android:paddingStart="20dip"
android:paddingTop="12dip">
<TextView
android:id="@+id/message"
style="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dip" />
</ScrollView>
</LinearLayout>
<ViewStub
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/custom_view"/>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:id="@+id/button_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
android:measureWithLargestChild="true"
android:orientation="horizontal"
android:paddingEnd="2dip"
android:paddingStart="2dip"
android:paddingTop="4dip">
<Button
android:id="@+id/negative"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_weight="1"
android:maxLines="2" />
<Button
android:id="@+id/neutral"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
android:maxLines="2" />
<Button
android:id="@+id/positive"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1"
android:maxLines="2" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,251 @@
<?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>

View File

@@ -0,0 +1,28 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="?attr/actionBarSize"
tools:context="com.topjohnwu.magisk.LogFragment">
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:id="@+id/tab"
app:tabPaddingEnd="20dp"
app:tabPaddingStart="20dp"
android:elevation="4dp"
android:visibility="gone">
</android.support.design.widget.TabLayout>
<android.support.v4.view.ViewPager
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/container"/>
</LinearLayout>

View File

@@ -1,7 +1,6 @@
<?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"
xmlns:fab="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
@@ -11,8 +10,6 @@
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -25,7 +22,7 @@
android:layout_height="match_parent"
android:clipToPadding="false"
android:dividerHeight="@dimen/card_divider_space"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
fab:layoutManager="android.support.v7.widget.LinearLayoutManager" />
<TextView
android:id="@+id/empty_rv"
@@ -47,16 +44,15 @@
android:layout_gravity="bottom|center_horizontal"
android:layout_marginEnd="113dp"
android:layout_marginBottom="10dp"
app:layout_behavior=".FABBehavior"
fab:layout_behavior=".utils.FABBehavior"
fab:menu_fab_size="normal"
fab:menu_showShadow="true"
fab:menu_shadowColor="#66000000"
fab:menu_shadowRadius="4dp"
fab:menu_shadowXOffset="1dp"
fab:menu_shadowYOffset="3dp"
fab:menu_colorNormal="#cddc39"
fab:menu_colorPressed="#9e9d24"
fab:menu_colorRipple="#99FFFFFF"
fab:menu_colorNormal="?android:colorAccent"
fab:menu_colorPressed="?attr/colorAccentFallback"
fab:menu_animationDelayPerItem="50"
fab:menu_icon="@drawable/ic_add"
fab:menu_buttonSpacing="0dp"
@@ -67,6 +63,8 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:src="@drawable/ic_archive"
fab:fab_colorNormal="?android:colorAccent"
fab:fab_colorPressed="?attr/colorAccentFallback"
fab:fab_size="mini"
fab:fab_label="@string/fab_flash_zip" />

View File

@@ -8,8 +8,7 @@
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"

View File

@@ -0,0 +1,28 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.topjohnwu.magisk.SuLogFragment">
<TextView
android:id="@+id/empty_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/log_is_empty"
android:textSize="20sp"
android:textStyle="italic"
android:visibility="gone" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="@dimen/card_divider_space"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More