Compare commits

...

275 Commits

Author SHA1 Message Date
topjohnwu
773c24b7fc Bump version 2017-09-28 03:55:53 +08:00
topjohnwu
8f926c7ca9 Load scripts in memory 2017-09-28 03:33:56 +08:00
topjohnwu
c562cbc2bb Update zip and magisk installation 2017-09-26 20:46:58 +08:00
topjohnwu
3fbbb0865a Update trad. Chinese 2017-09-26 02:13:39 +08:00
Naboleo
7d5f612a48 Update strings.xml 2017-09-26 03:07:55 +09:00
linar10
4a5a36440b Update strings.xml 2017-09-26 03:07:41 +09:00
Dmitry Val'd
43dd5cfea1 Update RU translation
Added new or missing lines
2017-09-26 03:07:33 +09:00
dark-basic #DarkBasic BasicHD
7b5fec1842 Update strings.xml 2017-09-26 03:07:20 +09:00
topjohnwu
5762ded601 Properly detect hosts file 2017-09-25 17:55:40 +08:00
topjohnwu
a3abb86daa Only place files in de on FDE enabled devices 2017-09-24 21:29:01 +08:00
topjohnwu
4f5c656b05 Update uninstall method 2017-09-16 03:53:13 +08:00
topjohnwu
a31cddbe7b Prevent NPE 2017-09-16 02:41:24 +08:00
topjohnwu
b4ecd93f1c Proper FBE support: place files in DE 2017-09-15 18:03:25 +08:00
topjohnwu
0acc23e058 Allow dialog to popup 2017-09-15 13:55:36 +08:00
topjohnwu
cdd5f9b628 Fix busybox installation 2017-09-15 13:34:53 +08:00
topjohnwu
4c9f5f4655 Support patching second slot 2017-09-15 13:03:10 +08:00
topjohnwu
b80ba13cb4 Fix strings 2017-09-15 03:47:18 +08:00
Santiago Pintos
8260bdc09c Update translations into spanish
Add two strings: "zip_download_title" and "zip_download_msg"
2017-09-13 10:12:13 -05:00
RoySchutte
24f856e02b Update strings.xml 2017-09-13 10:12:03 -05:00
Mevlüt TOPÇU
3aa619b928 Update
Merge please

Thank you
2017-09-13 10:11:53 -05:00
Taras Korzhak
4cb5e98d94 Update Ukrainian translation 2017-09-13 10:11:25 -05:00
Primokorn
272910575e Update FR strings.xml
Stupid typo
Unhide Magisk Manager should not be translated
2017-09-13 10:09:37 -05:00
topjohnwu
a15a62f4bc Move logic to external script file 2017-09-13 23:07:59 +08:00
topjohnwu
53cf11db8c Fix failure if MagiskManager folder doesn't exist 2017-09-13 23:07:59 +08:00
Dmitry Val'd
01052fbe47 Update strings.xml 2017-09-07 10:45:27 +08:00
dark-basic #DarkBasic BasicHD
a5e1e075c7 Update Strings (6-9-17)
Small Update
New Line Added.
2017-09-07 10:45:12 +08:00
c727
6be32ac688 update german strings
small improvements for new strings
also unified some strings

@topjohnwu:
what do you thing about calling the hidden Magsik Manager also "Magisk Manager" instead of "Unhide Magisk Manager"
The hidden status could be symbolized by an incognito style version of the app icon
advantages:
-same position in app drawer
-no need to translate it
2017-09-07 10:45:02 +08:00
topjohnwu
b362c0ef38 Bump version 2017-09-06 23:06:18 +08:00
topjohnwu
bba9969e31 Fix install button hiding 2017-09-06 23:05:51 +08:00
Primokorn
007ba24809 Update FR strings.xml 2017-09-06 22:33:04 +08:00
topjohnwu
df21539311 Some versioning fixes 2017-09-06 22:32:40 +08:00
topjohnwu
2592cb6019 Show Install button after update check done 2017-09-06 16:28:24 +08:00
topjohnwu
f7df17a7ed Small fix 2017-09-06 15:42:45 +08:00
dark-basic #DarkBasic BasicHD
62f42b72f8 Update Strings.xml (05-09-17)
New lines added.
2017-09-06 14:42:22 +08:00
topjohnwu
a1ba4fda6f Improve install Magisk 2017-09-06 14:41:59 +08:00
topjohnwu
1c06b04c45 Use GNU tar format 2017-09-06 13:39:29 +08:00
topjohnwu
2ee22fd374 Add restore stock image feature 2017-09-05 17:43:13 +08:00
topjohnwu
4c230d9e61 Root shell workaround 2017-09-05 13:46:54 +08:00
topjohnwu
727294fbbe Disable D8, dex not compatible with Android 5.0 2017-09-05 02:57:30 +08:00
Dmitry Val'd
478c43969b Update strings.xml
Added missing/new lines
2017-09-05 02:50:36 +08:00
Jens Lody
79b5303350 Update german translation 2017-09-05 02:50:20 +08:00
topjohnwu
ce4b742b25 Support .img.tar as input 2017-09-04 01:57:45 +08:00
topjohnwu
a9dc15bda5 Update TW translations 2017-09-04 01:14:38 +08:00
topjohnwu
ba6387ff5c Resource cleanup! 2017-09-04 00:58:39 +08:00
linar10
8fa98508b7 Update strings.xml 2017-09-03 23:18:12 +08:00
Dmitry Val'd
decdbaecf9 Update strings.xml
Added missing lines
2017-09-03 23:18:02 +08:00
gh2923
6d87cf9be0 Update Simplified Chinese Translation 2017-09-03 23:17:53 +08:00
Leonidas P
94f434c4a6 Translate Update Channel Strings 2017-09-03 23:17:36 +08:00
dark-basic #DarkBasic BasicHD
7ba867c30b Update Strings - (New Update 03-09-17) 2017-09-03 23:17:18 +08:00
topjohnwu
3424395e10 Calculate offset for unhide 2017-09-03 23:00:54 +08:00
topjohnwu
926c7359a2 Merge download and process repo modules 2017-09-03 22:10:54 +08:00
topjohnwu
ec0af99a2e Fix locale settings 2017-09-03 21:12:09 +08:00
topjohnwu
b4d948886c Fix unzip issues 2017-09-03 21:05:57 +08:00
topjohnwu
4d8d79372a Update strings 2017-09-03 18:28:46 +08:00
topjohnwu
04a589722c Support .img.tar format for ODIN 2017-09-03 17:46:00 +08:00
topjohnwu
d4a10e2873 Various adjustments 2017-09-03 17:46:00 +08:00
topjohnwu
4998ad6c7e Show Manager updates in dialogs 2017-09-03 14:58:21 +08:00
topjohnwu
a07ca5ff50 Slightly change busybox handling 2017-09-03 03:26:01 +08:00
topjohnwu
f07e7571ab Change block detection method 2017-09-03 02:45:43 +08:00
topjohnwu
834c16485c Reduce unnecessary code 2017-09-03 02:34:23 +08:00
topjohnwu
04a4265ef3 Show correct message 2017-09-03 00:17:42 +08:00
topjohnwu
0ec473195d Update install Magisk method 2017-09-03 00:10:14 +08:00
topjohnwu
0bf09256b0 Update Android Studio and Gradle 2017-09-02 19:12:03 +08:00
topjohnwu
db8fd2c913 Add boot image file patch 2017-08-31 03:07:33 +08:00
topjohnwu
dbe6e5b3d7 Simplify app startup 2017-08-30 02:28:24 +08:00
topjohnwu
cc81cd446b Extract ExpandableView code into interface 2017-08-29 04:10:04 +08:00
topjohnwu
439c7118f1 Proper runtime permission implementation 2017-08-29 03:08:09 +08:00
topjohnwu
d8154a5815 Update deprecate code 2017-08-29 01:56:43 +08:00
topjohnwu
4e3787bc0d Add beta update channel 2017-08-29 01:34:42 +08:00
topjohnwu
02e0955924 Fix settings crash 2017-08-29 00:37:52 +08:00
topjohnwu
a78950e822 Reduce boilerplate 2017-08-28 00:27:10 +08:00
topjohnwu
1ce1a94a35 Update translations 2017-08-27 01:38:05 +08:00
gh2923
977b6d9f67 Update Simplified Chinese Translation 2017-08-27 01:09:49 +08:00
Igor Sorocean
b5e6dbd797 update romanian translation 2017-08-27 01:09:41 +08:00
Taras
833e6688f1 Added Ukrainian translation 2017-08-27 01:09:33 +08:00
Dmitry Val'd
bc22c9f84f Update strings.xml
Added missing strings
2017-08-27 01:08:25 +08:00
Mevlüt TOPÇU
2149a7d116 Update
Merge please
2017-08-27 01:08:14 +08:00
dark-basic #DarkBasic BasicHD
29175d2c17 Update Strings.xml 2017-08-27 01:07:45 +08:00
Leonidas P
803454d5c8 Update Greek Strings 2017-08-27 01:07:26 +08:00
topjohnwu
36cf32dc42 Change unhide app temp location 2017-08-27 01:04:55 +08:00
topjohnwu
657f4ab303 Add hide Magisk Manager feature 2017-08-22 03:01:54 +08:00
topjohnwu
ea6552615d Bump version 2017-08-13 01:50:20 +08:00
Generator
4bf3287fce update pt_PT 2017-08-13 01:20:04 +08:00
Mevlüt TOPÇU
832c2034c2 Update
Hi,

Update, translations and typo fix

Merge please

Thank you
2017-08-13 01:19:48 +08:00
RJ Trujillo
b0aa26e1f1 More string updates
* A few grammatical corrections were made
* Everything looks cleaner now
2017-08-13 01:19:27 +08:00
dark-basic #DarkBasic BasicHD
e52baeb967 Update Strings.xml 2017-08-13 01:19:15 +08:00
Leonidas P
8268eb9a83 Update strings.xml 2017-08-13 01:18:55 +08:00
topjohnwu
3cc458abd9 Always use global mount namespace 2017-08-12 17:07:28 +08:00
topjohnwu
337b4c4268 Upgrade Android Studio 2017-08-12 15:54:14 +08:00
topjohnwu
001f8657f6 Use global Magisk native busybox for Magisk Manager 2017-08-12 02:25:55 +08:00
topjohnwu
ea884e7fa1 Re-organize application startup 2017-08-12 01:31:34 +08:00
topjohnwu
1b1394cf5d Improve Markdown support
Close #259
2017-08-08 16:12:49 +08:00
topjohnwu
1eef930dbb Move OnClickListener to Butterknife 2017-08-08 16:09:45 +08:00
topjohnwu
1e175e74ed Prevent crashes 2017-08-07 00:15:46 +08:00
John Wu
75a46c365e Update README.md 2017-08-04 00:23:14 +08:00
topjohnwu
8e7b8825f5 Rename callbackevents to topic/subscribers 2017-08-04 00:17:31 +08:00
topjohnwu
2ecbca303b Update Shell 2017-08-03 23:33:08 +08:00
topjohnwu
8195a4d616 Don't ignore libbusybox.so, we want it removed 2017-08-01 23:54:45 +08:00
topjohnwu
7ba40f925f Remove busybox in APK, download from internet 2017-08-01 23:52:39 +08:00
topjohnwu
345cd1795f Update WebService 2017-08-01 23:08:34 +08:00
topjohnwu
959aaee045 Fix FlashZip crash when fails 2017-07-31 01:19:43 +08:00
topjohnwu
53477f0f59 Improve locale settings 2017-07-31 00:44:38 +08:00
topjohnwu
5716218f41 Update busybox version and bug fixes 2017-07-31 00:21:18 +08:00
topjohnwu
9df6b9d5c0 Remove external files from git
These files should be copied to the correct place by Magisk's build script
2017-07-30 23:17:39 +08:00
topjohnwu
ec46031d36 Update Android Studio 2017-07-30 14:41:22 +08:00
RJ Trujillo
55b84d166a Improve dialog strings
* A space should never follow a question mark or any form of punctuation
* Multiple exclamation marks are not needed
2017-07-30 01:36:25 -05:00
Silvered99
34ae8bacec Update strings.xml 2017-07-30 01:36:16 -05:00
RoySchutte
cb4e5ca0f7 Update strings.xml 2017-07-30 01:36:07 -05:00
Leonidas P
0ba45468c4 Fix typos
these pesky little buggers, you never find them...
2017-07-30 01:35:57 -05:00
Frieder Bluemle
710502784e Update Gradle wrapper to 4.1-rc-1 2017-07-30 01:35:46 -05:00
topjohnwu
0275a8558d Fix locale settings duplicate 2017-07-24 18:37:13 +08:00
topjohnwu
58acc75cf6 Fix SuLog UI 2017-07-24 13:15:05 +08:00
topjohnwu
874ababb9f Fix strings.xml 2017-07-24 02:08:58 +08:00
gh2923
3771e6b0cd Update Simplified Chinese Translation 2017-07-24 01:38:55 +08:00
Sopor
33eaefa966 Add Swedish translation 2017-07-24 01:38:43 +08:00
RoySchutte
cd7e236d57 Update strings.xml 2017-07-24 01:38:18 +08:00
Andrei Conache
54c0b7c7d5 update italian translation 2017-07-24 01:38:02 +08:00
zertyuiop
a2177daec2 Update strings.xml 2017-07-24 01:37:42 +08:00
dark-basic #DarkBasic BasicHD
628386b453 Update Spanish strings.xml 2017-07-24 01:37:23 +08:00
Leonidas P
b222bfb3e0 Update Greek translation 2017-07-24 01:36:09 +08:00
topjohnwu
ab199d883d Change su logs time granularity 2017-07-24 01:26:56 +08:00
topjohnwu
356065d1ee Rewrite SuLogAdapter 2017-07-24 01:26:56 +08:00
topjohnwu
76e7c5623d Simplify ApplicationAdapter filter 2017-07-24 01:26:56 +08:00
topjohnwu
085fba050a Introduce self-written SectionedAdapter 2017-07-24 01:26:45 +08:00
topjohnwu
295334d3ac Preserve toolbar elevation when restart activity 2017-07-23 00:47:54 +08:00
topjohnwu
36124ddca4 Update CallbackEvents 2017-07-23 00:39:38 +08:00
topjohnwu
bd6585765e Add locale settings 2017-07-23 00:33:24 +08:00
topjohnwu
c325deb4ed Random changes 2017-07-22 17:39:34 +08:00
topjohnwu
73bb0b10ee Prevent memory leak in CallbackEvent 2017-07-21 05:18:24 +08:00
topjohnwu
72820b162c Code cleanups 2017-07-21 05:08:39 +08:00
topjohnwu
89e5b8d057 Switch to official BouncyCastle 2017-07-21 03:56:48 +08:00
topjohnwu
da4f53ebbb Don't store multiple repo copies in memory 2017-07-21 02:46:19 +08:00
topjohnwu
8458553b74 Update database helper 2017-07-21 02:10:00 +08:00
topjohnwu
55ecc41d06 Bump version 2017-07-20 03:20:17 +08:00
#DarkBasic - BasicHD
28fcdf2cbb Update strings.xml
Delele Translate "Magisk Modo Sólo Núcleo". (After several hours (Days :v ). I thought it was best left in its original form .Magisk Hide, should also be translated if it were the case, it was better to leave it that way so as not to confuse the users.)
Fix translation error
Translations Updates and added new line
2017-07-20 03:19:58 +08:00
topjohnwu
24087679a8 Update uninstaller 2017-07-20 02:56:36 +08:00
topjohnwu
5ac6a8cb4a Small minor updates 2017-07-20 02:54:34 +08:00
topjohnwu
668d85d14e Improve notification support 2017-07-20 01:44:32 +08:00
topjohnwu
c11a3dc95c Fix Magisk Manager freezing issue 2017-07-20 00:51:30 +08:00
topjohnwu
56f57c20a2 Update AsyncTasks to prevent memory leak 2017-07-19 18:01:22 +08:00
topjohnwu
240d14779a Minor cleanup in check updates 2017-07-19 16:10:17 +08:00
topjohnwu
3550d1e61c Bump version 2017-07-19 00:38:25 +08:00
topjohnwu
6513ad249c Fix string.xml 2017-07-19 00:36:54 +08:00
killer7mod
50297b1880 update strings.xml portuguese brazil 2017-07-19 00:25:36 +08:00
#DarkBasic - BasicHD
f189b78b9e #DBC01 - Translation update 2017-07-19 00:25:23 +08:00
zertyuiop
5c0250f495 Fix too long string
checking_safetyNet_status string is too long.
2017-07-19 00:24:47 +08:00
pavlaras
2093f726e9 Update strings.xml
corrected Greek translation
2017-07-19 00:24:36 +08:00
topjohnwu
10efe3859d Update repo fragment and adapter 2017-07-18 23:18:57 +08:00
topjohnwu
6933bcf7bb Merge shells 2017-07-18 17:14:42 +08:00
topjohnwu
2ea046cd80 Add flashing screen 2017-07-18 17:14:42 +08:00
topjohnwu
f4097a372b Root shell with no outputs 2017-07-18 01:06:05 +08:00
topjohnwu
87ea2a2bef Rewrite root shell 2017-07-16 03:00:01 +08:00
JpegXguy
cc14a1c361 Fix untranslated strings 2017-07-15 01:23:59 +08:00
topjohnwu
bcdface60d Fix crashing when installing modules 2017-07-15 01:22:00 +08:00
topjohnwu
4dc9419d2e Bump version 2017-07-14 02:31:29 +08:00
topjohnwu
d2bcac813e Fix update notifications on Android O 2017-07-14 02:27:02 +08:00
topjohnwu
080c37a7f6 Remove busybox from strings 2017-07-14 01:18:20 +08:00
topjohnwu
f9a3838db6 Fix strings 2017-07-13 15:37:00 +08:00
JpegXguy
1e61db104b Added Greek Language 2017-07-13 15:22:53 +08:00
Generator
30a9c7718d Added (European) Portuguese
Split Portuguese into pt_BR and pt_PT
2017-07-13 15:22:40 +08:00
Dmitry Val'd
34b052b5d3 Update strings.xml
Full and correct translation to russian language
2017-07-13 15:21:27 +08:00
topjohnwu
aaa12853ad Prevent crashing when requesting SN check while checking
Fixed #208, fixed #212
2017-07-13 15:12:43 +08:00
topjohnwu
b0ab55b0bf Only show one notification at a time 2017-07-13 14:51:12 +08:00
topjohnwu
d2f8496f4e Update dependency 2017-07-13 14:47:47 +08:00
topjohnwu
1a69b16d36 Bump version 2017-07-11 01:11:10 +08:00
topjohnwu
b5e8673e62 Fix small UI bug 2017-07-11 01:09:40 +08:00
topjohnwu
264c6a50b6 Update uninstallation 2017-07-11 00:55:53 +08:00
topjohnwu
493642eb38 Minor translation update 2017-07-11 00:55:44 +08:00
gh2923
28d42b9164 fix some expressions 2017-07-08 11:17:41 -05:00
Jens Lody
42f29062ca Fix timeout of temporary granted su-rights. 2017-07-08 11:17:07 -05:00
topjohnwu
c4377ed6c2 Bump version 2017-07-03 01:08:54 +08:00
topjohnwu
7d283ed65f Optimize imports 2017-07-01 18:09:34 +08:00
topjohnwu
bf1f941e50 Adapt to Android O new broadcast limitations 2017-07-01 18:09:34 +08:00
topjohnwu
789fef34ba Fix crash on Android O 2017-07-01 18:09:34 +08:00
topjohnwu
1daf5a611c MagiskHide now defaults to enabled 2017-07-01 17:38:33 +08:00
topjohnwu
6aed1db67e Update Android Studio 2017-07-01 15:57:49 +08:00
gh2923
cf68854770 Update Simplified Chinese Translation 2017-06-20 21:46:36 +08:00
linar10
711392c73b Update Strings PL 2017-06-20 21:45:46 +08:00
c727
9573c32481 update strings.de 2017-06-20 21:45:38 +08:00
RoySchutte
a15f80f79d Create strings.xml 2017-06-20 21:45:28 +08:00
Igor Sorocean
23e7475f06 update romanian translation 2017-06-20 21:45:11 +08:00
topjohnwu
1eb571b787 Proper handle policy changes 2017-06-20 18:33:50 +08:00
topjohnwu
dd3b716d85 Extract expandable viewholder 2017-06-20 17:57:17 +08:00
topjohnwu
28649c07e3 SU policy DB bug fix 2017-06-20 17:57:17 +08:00
topjohnwu
961e02be0d Update Android Studio 2017-06-20 17:54:40 +08:00
topjohnwu
a161491bfd Disable shrinkResources due to buildtool bug 2017-06-16 15:25:22 +08:00
topjohnwu
e0b4d1c1e4 Bump version 2017-06-16 04:07:10 +08:00
topjohnwu
fd4aaab137 Rewrite zip signing 2017-06-16 03:12:57 +08:00
topjohnwu
42d14d5ca2 Update to new build tools, target API 26 2017-06-16 03:06:22 +08:00
topjohnwu
d3ff482c9b Bump version 2017-06-08 22:55:48 +08:00
topjohnwu
f682368eeb Update strings 2017-06-08 22:49:26 +08:00
topjohnwu
4a5d033efb Store data in intent for OTA 2017-06-08 22:35:30 +08:00
topjohnwu
343161b195 Add mount namespace options 2017-06-08 22:27:24 +08:00
topjohnwu
bc576a9659 Update uninstall script 2017-06-08 04:28:55 +08:00
topjohnwu
19e407fcc4 Update translations 2017-06-08 04:23:17 +08:00
RoySchutte
bc7327d004 Update strings.xml 2017-06-08 04:14:12 +08:00
ROBERTO
666fa1c797 Update Italian translation 2017-06-08 04:14:01 +08:00
Igor Sorocean
0eda4a7821 Update romanian translation 2017-06-08 04:13:44 +08:00
topjohnwu
862058fd2b Bump version 2017-06-08 03:20:04 +08:00
topjohnwu
69e5bcd57d Simple OTA implementation 2017-06-07 02:21:58 +08:00
topjohnwu
efeddda328 Use Java synchronize instead serial tasks 2017-06-06 03:21:52 +08:00
topjohnwu
ff6938280e Switch to DB based su configs 2017-06-01 03:18:41 +08:00
RoySchutte
1e4425b30f Update strings-nl.xml 2017-05-31 11:45:02 -05:00
Igor Sorocean
b5d1d8cdad Update romanian translation 2017-05-31 11:44:37 -05:00
gh2923
029be5ccca Update Simplified Chinese Translation 2017-05-31 11:44:17 -05:00
gh2923
29c2d785b5 Update Simplified Chinese Translation 2017-05-31 11:44:04 -05:00
Exalm
abda8cfa32 Updated russian translation 2017-05-31 11:43:48 -05:00
topjohnwu
44e7d79d4c Add Arabic translation
Credits to @xx6600xx
2017-06-01 00:41:36 +08:00
topjohnwu
9a1dc8ee0e Refactor su database 2017-06-01 00:26:36 +08:00
topjohnwu
27879c3f01 Improve Logger 2017-05-31 17:43:55 +08:00
topjohnwu
29096eb5d7 Monitor package (un)install events 2017-05-31 16:31:33 +08:00
topjohnwu
a573baea03 Simplify SU requests, binary should be much superior now 2017-05-30 01:27:10 +08:00
topjohnwu
5af07c4531 Update Traditional Chinese translate 2017-05-28 01:44:29 +08:00
topjohnwu
44e36feb09 Improve multiuser settings and notification 2017-05-28 01:31:19 +08:00
topjohnwu
2a7d996881 Add multiuser support 2017-05-27 02:41:24 +08:00
topjohnwu
738f943a68 Several UI tweaks 2017-05-26 18:20:53 +08:00
dvdandroid
47e62a5681 Small code cleanup 2017-05-24 21:21:15 +02:00
dvdandroid
1ecbfd7590 Adjust theme in about and settings activities 2017-05-24 20:55:47 +02:00
topjohnwu
67c139a04b Fix theme changing glitch 2017-05-24 00:37:15 +08:00
RoySchutte
31cc008249 Update strings.xml
2 small changes to make strings more similar.
2017-05-23 19:47:38 +08:00
topjohnwu
9cb026439d Update translations 2017-05-23 17:02:05 +08:00
topjohnwu
e6f10176c6 Network check 2017-05-23 17:01:38 +08:00
RoySchutte
0917c79470 Update strings.xml
Added and translated new strings.
2017-05-22 23:53:59 +08:00
ROBERTO
597baa986d Updated Italian language 2017-05-22 23:53:43 +08:00
topjohnwu
75cc4b4843 Merge install and status 2017-05-21 12:16:38 +08:00
topjohnwu
aac088d496 Update strings.xml 2017-05-20 03:17:37 +08:00
RoySchutte
a822e5bbc5 Update strings.xml
Fixed many Dutch translations which were gramatically incorrect. Added translations (up-to-date).
Hopefully these translations will make it to the next release, because the current translations aren't pretty *_*.
2017-05-20 03:08:22 +08:00
Igor Sorocean
c527249c21 Add romanian translation 2017-05-20 03:08:14 +08:00
topjohnwu
9ef798f534 Update SafetyNet check UI 2017-05-20 03:04:14 +08:00
topjohnwu
e69b99f089 Update status UI 2017-05-19 08:37:57 -07:00
topjohnwu
55b8079e86 Update MagiskHide method 2017-05-12 23:11:28 +08:00
topjohnwu
e272dbe9af Include busybox binary and remove busybox toggle 2017-05-12 04:05:21 +08:00
topjohnwu
962f8354ac Use new version detection method 2017-05-12 02:25:07 +08:00
topjohnwu
20e4a960f7 Fix strings 2017-05-10 22:54:17 +08:00
ROBERTO
82249cb50a Italian language update 2017-04-28 23:45:41 +08:00
gh2923
fad417e553 Update Simplified Chinese Translation 2017-04-28 15:41:59 +08:00
lindwurm
5ba692f50c l10n: Update Japanese Translations
* Fixed more strings!

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-28 15:41:50 +08:00
topjohnwu
907e01e524 Use stable build tools + retrolambda 2017-04-26 19:04:06 +08:00
lindwurm
b8ed23efa7 l10n: Update Japanese Translations
Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-26 19:03:14 +08:00
topjohnwu
2b3bbf7e67 Bump version 2017-04-26 00:59:56 +08:00
topjohnwu
464fe627a3 Swap tabs 2017-04-26 00:27:55 +08:00
topjohnwu
6a9e39c470 Support unlimited amount of repos 2017-04-26 00:15:53 +08:00
topjohnwu
7fec9a3cc6 Fix string.xml errors 2017-04-24 22:26:40 +08:00
Primokorn
008f6ef462 Update french strings.xml
Better translation.
2017-04-24 21:54:34 +08:00
lilymaniac
2440c108ca Update values-ko/strings.xml 2017-04-24 21:54:22 +08:00
linar10
430baad8a4 Update strings pl 2017-04-24 21:54:05 +08:00
Nosi
51132e74b4 Changes Spanish 2017-04-24 21:53:48 +08:00
killer7Mod
a4f33e106a Update Portuguese translation 2017-04-24 21:53:21 +08:00
SakuraSa233
baba3190e0 Add Japanese Translation 2017-04-24 21:53:05 +08:00
topjohnwu
47b13aa5ea Use stock FAB; Log monospace; Fixes 2017-04-24 21:52:23 +08:00
topjohnwu
ae88d3054d Finally, official Java 8 support 2017-04-05 17:02:18 +08:00
topjohnwu
411b600e14 Screw that Jack compiler, use retrolambda 2017-03-31 16:04:12 +08:00
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
149 changed files with 8690 additions and 5819 deletions

6
.gitignore vendored
View File

@@ -3,6 +3,10 @@
/local.properties
.idea/
/build
app/app-release.apk
app/release
*.hprof
app/.externalNativeBuild/
*.sh
public.certificate.x509.pem
private.key.pk8
*.apk

View File

@@ -1,4 +1,7 @@
# Magisk Manager
The project should be built with Android Studio version 2.2.0+
I use Java 8 features, which requires Jack compiler and it's only available in 2.2.0+
Also, you need to install CMake and NDK to build the zipadjust library for zip preprocessing
This is one of the submodules used in Magisk. The project is licensed under GPL v3 (or newer).
More info are written in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk)
## Building Notes
You need to install CMake and NDK to build the zipadjust library.
There are several files required to let Magisk Manager work properly, and they can be copied by using the build script in the [Magisk Main Repo](https://github.com/topjohnwu/Magisk). These files are: `magisk_uninstaller.sh`, `util_functions.sh`, `public.certificate.x509.pem`, and `private.key.pk8` under the `app/src/main/assets` folder.

View File

@@ -1,28 +1,30 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.topjohnwu.magisk"
minSdkVersion 21
targetSdkVersion 25
versionCode 23
versionName "4.2"
jackOptions {
enabled true
jackInProcess true
}
targetSdkVersion 26
versionCode 55
versionName "5.3.5"
ndk {
moduleName 'zipadjust'
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
javaCompileOptions {
annotationProcessorOptions {
argument('butterknife.debuggable', 'false')
}
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
@@ -31,34 +33,36 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
dexOptions {
preDexLibraries = true
preDexLibraries true
javaMaxHeapSize "2g"
}
externalNativeBuild {
cmake {
path 'src/main/jni/CMakeLists.txt'
}
}
lintOptions {
disable 'MissingTranslation'
}
}
repositories {
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://maven.google.com" }
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:recyclerview-v7:25.1.1'
compile 'com.android.support:cardview-v7:25.1.1'
compile 'com.android.support:design:25.1.1'
compile 'com.android.support:support-v4:25.1.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.5.1'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':resource')
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support:cardview-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:support-v4:26.1.0'
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.atlassian.commonmark:commonmark:0.9.0'
implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'
implementation 'org.kamranzafar:jtar:2.3'
implementation 'com.google.android.gms:play-services-safetynet:9.0.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

View File

@@ -16,10 +16,10 @@
# public *;
#}
-keep class android.support.v7.internal.** { *; }
-keep interface android.support.v7.internal.** { *; }
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
# BouncyCastle
-keep class org.bouncycastle.** { *; }
-dontwarn javax.naming.**
-dontwarn android.content.**
-dontwarn android.animation.**
# SpongyCastle
-keep class org.spongycastle.** {*;}

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.topjohnwu.magisk"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<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.VIBRATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".MagiskManager"
@@ -17,12 +17,12 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
android:directBootAware="true"
tools:ignore="UnusedAttribute">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true"/>
android:exported="true" />
<activity
android:name=".SplashActivity"
android:configChanges="orientation|screenSize"
@@ -30,16 +30,22 @@
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:theme="@style/AppTheme.Transparent"/>
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".SettingsActivity"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".FlashActivity"
android:screenOrientation="nosensor"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppTheme.Transparent" />
<activity
android:name=".superuser.RequestActivity"
android:excludeFromRecents="true"
@@ -52,21 +58,27 @@
android:taskAffinity="internal.superuser"
android:theme="@style/SuRequest" />
<receiver
android:name=".superuser.SuReceiver" />
<receiver android:name=".superuser.SuReceiver" />
<receiver android:name=".receivers.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.PackageReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<service android:name=".services.BootupIntentService" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.ManagerUpdate" />
<service android:name=".services.OnBootIntentService" />
<service
android:name=".services.UpdateCheckService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<provider
android:name="android.support.v4.content.FileProvider"
@@ -84,4 +96,4 @@
</application>
</manifest>
</manifest>

View File

@@ -1,150 +0,0 @@
#!/system/bin/sh
[ -z $BOOTMODE ] && BOOTMODE=false
TMPDIR=/tmp
($BOOTMODE) && TMPDIR=/dev/tmp
BINDIR=/data/magisk
CHROMEDIR=$BINDIR/chromeos
NEWBOOT=$TMPDIR/boottmp/new-boot.img
UNPACKDIR=$TMPDIR/boottmp/bootunpack
RAMDISK=$TMPDIR/boottmp/ramdisk
SYSTEMLIB=/system/lib
[ -d /system/lib64 ] && SYSTEMLIB=/system/lib64
ui_print() {
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
}
unpack_boot() {
rm -rf $UNPACKDIR $RAMDISK 2>/dev/null
mkdir -p $UNPACKDIR
mkdir -p $RAMDISK
cd $UNPACKDIR
LD_LIBRARY_PATH=$SYSTEMLIB $BINDIR/bootimgtools --extract $1
cd $RAMDISK
$BINDIR/busybox gunzip -c < $UNPACKDIR/ramdisk.gz | cpio -i
}
repack_boot() {
cd $RAMDISK
find . | cpio -o -H newc 2>/dev/null | gzip -9 > $UNPACKDIR/ramdisk.gz
cd $UNPACKDIR
LD_LIBRARY_PATH=$SYSTEMLIB $BINDIR/bootimgtools --repack $BOOTIMAGE
if [ -f chromeos ]; then
echo " " > config
echo " " > bootloader
LD_LIBRARY_PATH=$SYSTEMLIB $CHROMEDIR/futility vbutil_kernel --pack new-boot.img.signed --keyblock $CHROMEDIR/kernel.keyblock --signprivate $CHROMEDIR/kernel_data_key.vbprivk --version 1 --vmlinuz new-boot.img --config config --arch arm --bootloader bootloader --flags 0x1
rm -f new-boot.img
mv new-boot.img.signed new-boot.img
fi
if ($SAMSUNG); then
SAMSUNG_CHECK=$(cat new-boot.img | grep SEANDROIDENFORCE)
if [ $? -ne 0 ]; then
echo -n "SEANDROIDENFORCE" >> new-boot.img
fi
fi
if ($LGE_G); then
# Prevent secure boot error on LG G2/G3.
# Just for know, It's a pattern which bootloader verifies at boot. Thanks to LG hackers.
echo -n -e "\x41\xa9\xe4\x67\x74\x4d\x1d\x1b\xa4\x29\xf2\xec\xea\x65\x52\x79" >> new-boot.img
fi
mv new-boot.img $NEWBOOT
}
# Set permissions
chmod -R 755 $CHROMEDIR/futility $BINDIR
# Find the boot image
find_boot_image
if [ -z "$BOOTIMAGE" ]; then
ui_print "! Unable to detect boot image"
exit 1
fi
ui_print "- Found Boot Image: $BOOTIMAGE"
# Detect special vendors
SAMSUNG=false
SAMSUNG_CHECK=$(cat /system/build.prop | grep "ro.build.fingerprint=" | grep -i "samsung")
if [ $? -eq 0 ]; then
SAMSUNG=true
fi
LGE_G=false
RBRAND=$(grep_prop ro.product.brand)
RMODEL=$(grep_prop ro.product.device)
if [ "$RBRAND" = "lge" ] || [ "$RBRAND" = "LGE" ]; then
if [ "$RMODEL" = "*D80*" ] ||
[ "$RMODEL" = "*S98*" ] ||
[ "$RMODEL" = "*D85*" ] ||
[ "$RMODEL" = "*F40*" ]; then
LGE_G=true
ui_print "! Bump device detected"
fi
fi
# First unpack the boot image
unpack_boot $BOOTIMAGE
SUPERSU=false
[ -f sbin/launch_daemonsu.sh ] && SUPERSU=true
if ($SUPERSU); then
ui_print "- SuperSU patched image detected"
rm -f magisk sbin/init.magisk.rc sbin/magic_mask.sh
repack_boot
else
if [ -f /data/stock_boot.img ]; then
ui_print "- Boot image backup found!"
NEWBOOT=/data/stock_boot.img
else
ui_print "! Boot image backup unavailable"
if [ -d ".backup" ]; then
ui_print "- Restoring ramdisk with backup"
cp -af .backup/. .
fi
rm -f magisk sbin/init.magisk.rc sbin/magic_mask.sh
repack_boot
fi
fi
chmod 644 $NEWBOOT
ui_print "- Flashing stock/reverted image"
[ ! -L "$BOOTIMAGE" ] && dd if=/dev/zero of=$BOOTIMAGE bs=4096 2>/dev/null
dd if=$NEWBOOT of=$BOOTIMAGE bs=4096
ui_print "- 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.

View File

@@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
-----END CERTIFICATE-----

View File

@@ -3,7 +3,6 @@ package com.topjohnwu.magisk;
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.AlertDialog;
@@ -13,13 +12,11 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
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 java.io.IOException;
import java.io.InputStream;
@@ -29,7 +26,7 @@ import butterknife.ButterKnife;
public class AboutActivity extends Activity {
private static final String DONATION_URL = "http://topjohnwu.github.io/donate";
private static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
@@ -45,10 +42,8 @@ public class AboutActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", "");
Logger.dev("AboutActivity: Theme is " + theme);
if (getApplicationContext().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
if (getMagiskManager().isDarkTheme) {
setTheme(R.style.AppTheme_Transparent_Dark);
}
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
@@ -135,18 +130,4 @@ public class AboutActivity extends Activity {
setFloating();
}
public void setFloating() {
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
params.alpha = 1.0f;
params.dimAmount = 0.6f;
params.flags |= 2;
getWindow().setAttributes(params);
setFinishOnTouchOutside(true);
}
}
}

View File

@@ -0,0 +1,151 @@
package com.topjohnwu.magisk;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.asyncs.InstallMagisk;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class FlashActivity extends Activity {
public static final String SET_ACTION = "action";
public static final String SET_BOOT = "boot";
public static final String SET_ENC = "enc";
public static final String SET_VERITY = "verity";
public static final String FLASH_ZIP = "flash";
public static final String PATCH_BOOT = "patch";
public static final String FLASH_MAGISK = "magisk";
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.flash_logs) RecyclerView flashLogs;
@BindView(R.id.button_panel) LinearLayout buttonPanel;
@BindView(R.id.reboot) Button reboot;
@OnClick(R.id.no_thanks)
public void dismiss() {
finish();
}
@OnClick(R.id.reboot)
public void reboot() {
getShell().su_raw("reboot");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash);
ButterKnife.bind(this);
AdaptiveList<String> rootShellOutput = new AdaptiveList<>(flashLogs);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(R.string.flashing);
}
setFloating();
if (!Shell.rootAccess())
reboot.setVisibility(View.GONE);
flashLogs.setAdapter(new FlashLogAdapter(rootShellOutput));
// We must receive a Uri of the target zip
Intent intent = getIntent();
Uri uri = intent.getData();
boolean keepEnc = intent.getBooleanExtra(SET_ENC, false);
boolean keepVerity = intent.getBooleanExtra(SET_VERITY, false);
switch (getIntent().getStringExtra(SET_ACTION)) {
case FLASH_ZIP:
new FlashZip(this, uri, rootShellOutput)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
break;
case PATCH_BOOT:
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, (Uri) intent.getParcelableExtra(SET_BOOT))
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
break;
case FLASH_MAGISK:
String boot = intent.getStringExtra(SET_BOOT);
if (getMagiskManager().remoteMagiskVersionCode < 1370) {
// Use legacy installation method
getShell().su_raw(
"echo \"BOOTIMAGE=" + boot + "\" > /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + keepEnc + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + keepVerity + "\" >> /dev/.magisk"
);
new FlashZip(this, uri, rootShellOutput)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
} else {
// Use new installation method
new InstallMagisk(this, rootShellOutput, uri, keepEnc, keepVerity, boot)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
}
break;
}
}
@Override
public void onBackPressed() {
// Prevent user accidentally press back button
}
private static class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
private List<String> mList;
FlashLogAdapter(List<String> list) {
mList = list;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_flashlog, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.text.setText(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.textView) TextView text;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}

View File

@@ -1,189 +0,0 @@
package com.topjohnwu.magisk;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.v7.widget.CardView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.ProcessMagiskZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.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.Unbinder;
public class InstallFragment extends Fragment implements CallbackEvent.Listener<Void> {
private static final String UNINSTALLER = "magisk_uninstaller.sh";
private Unbinder unbinder;
@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.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.fragment_install, container, false);
unbinder = ButterKnife.bind(this, v);
detectButton.setOnClickListener(v1 -> toAutoDetect());
if (getApplication().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" + getApplication().magiskVersionString));
installTitle.setText(getString(R.string.install_magisk_title, "v" + String.format(Locale.US, "%.1f", getApplication().remoteMagiskVersion)));
flashButton.setOnClickListener(v1 -> {
String bootImage;
if (getApplication().bootBlock != null) {
if (spinner.getSelectedItemPosition() > 0)
bootImage = getApplication().blockList.get(spinner.getSelectedItemPosition() - 1);
else
bootImage = getApplication().bootBlock;
} else {
bootImage = getApplication().blockList.get(spinner.getSelectedItemPosition());
}
String filename = "Magisk-v" + getApplication().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(R.string.install, (dialogInterface, i) -> Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
private String boot = bootImage;
private boolean enc = keepEncChkbox.isChecked();
private boolean verity = keepVerityChkbox.isChecked();
@Override
public void onDownloadDone(Uri uri) {
new ProcessMagiskZip(getActivity(), uri, boot, enc, verity).exec();
}
},
getApplication().magiskLink,
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.release_notes, (dialog, which) -> {
getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getApplication().releaseNoteLink)));
})
.setNegativeButton(R.string.no_thanks, null)
.show();
});
if (getApplication().magiskVersion < 10.3) {
uninstallButton.setVisibility(View.GONE);
} else {
uninstallButton.setOnClickListener(vi -> {
new AlertDialogBuilder(getActivity())
.setTitle(R.string.uninstall_magisk_title)
.setMessage(R.string.uninstall_magisk_msg)
.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
try {
InputStream in = getActivity().getAssets().open(UNINSTALLER);
File uninstaller = new File(getActivity().getCacheDir().getAbsolutePath() + "/" + 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, "cp -af " + uninstaller + " /cache/" + UNINSTALLER,
"reboot");
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
})
.setNegativeButton(R.string.no_thanks, null)
.show();
});
}
if (getApplication().blockDetectionDone.isTriggered) {
updateUI();
}
return v;
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
updateUI();
}
private void updateUI() {
List<String> items = new ArrayList<>(getApplication().blockList);
if (getApplication().bootBlock != null)
items.add(0, getString(R.string.auto_detect, getApplication().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 (getApplication().bootBlock != null) {
spinner.setSelection(0);
}
}
@Override
public void onStart() {
super.onStart();
getActivity().setTitle(R.string.install);
getApplication().blockDetectionDone.register(this);
}
@Override
public void onStop() {
getApplication().blockDetectionDone.unRegister(this);
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@@ -29,9 +29,9 @@ public class LogFragment extends Fragment {
View v = inflater.inflate(R.layout.fragment_log, container, false);
unbinder = ButterKnife.bind(this, v);
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
((MainActivity) getActivity()).toolbar.setElevation(0);
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
if (getApplication().isSuClient) {
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
@@ -39,6 +39,8 @@ public class LogFragment extends Fragment {
tab.setVisibility(View.VISIBLE);
}
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
viewPager.setAdapter(adapter);
return v;

View File

@@ -0,0 +1,339 @@
package com.topjohnwu.magisk;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.CardView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.components.ExpandableView;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
public class MagiskFragment extends Fragment
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
private Container expandableContainer = new Container();
private MagiskManager mm;
private Unbinder unbinder;
private static boolean shownDialog = false;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.magisk_update_card) CardView magiskUpdateCard;
@BindView(R.id.magisk_update_icon) ImageView magiskUpdateIcon;
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
@BindView(R.id.magisk_update_progress) ProgressBar magiskUpdateProgress;
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
@BindView(R.id.magisk_version) TextView magiskVersionText;
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
@BindView(R.id.root_status) TextView rootStatusText;
@BindView(R.id.safetyNet_card) CardView safetyNetCard;
@BindView(R.id.safetyNet_refresh) ImageView safetyNetRefreshIcon;
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
@BindView(R.id.expand_layout) LinearLayout expandLayout;
@BindView(R.id.cts_status_icon) ImageView ctsStatusIcon;
@BindView(R.id.cts_status) TextView ctsStatusText;
@BindView(R.id.basic_status_icon) ImageView basicStatusIcon;
@BindView(R.id.basic_status) TextView basicStatusText;
@BindView(R.id.bootimage_card) CardView bootImageCard;
@BindView(R.id.block_spinner) Spinner spinner;
@BindView(R.id.detect_bootimage) Button detectButton;
@BindView(R.id.install_option_card) CardView installOptionCard;
@BindView(R.id.keep_force_enc) CheckBox keepEncChkbox;
@BindView(R.id.keep_verity) CheckBox keepVerityChkbox;
@BindView(R.id.install_button) CardView installButton;
@BindView(R.id.install_text) TextView installText;
@BindView(R.id.uninstall_button) CardView uninstallButton;
@BindColor(R.color.red500) int colorBad;
@BindColor(R.color.green500) int colorOK;
@BindColor(R.color.yellow500) int colorWarn;
@BindColor(R.color.grey500) int colorNeutral;
@BindColor(R.color.blue500) int colorInfo;
@OnClick(R.id.safetyNet_title)
void safetyNet() {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetRefreshIcon.setVisibility(View.GONE);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
Utils.checkSafetyNet(getActivity());
collapse();
}
@OnClick(R.id.install_button)
void install() {
shownDialog = true;
// Show Manager update first
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
Utils.showManagerInstallDialog(getActivity());
return;
}
((NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
Utils.showMagiskInstallDialog(this,
keepEncChkbox.isChecked(), keepVerityChkbox.isChecked());
}
@OnClick(R.id.uninstall_button)
void uninstall() {
Utils.showUninstallDialog(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_magisk, container, false);
unbinder = ButterKnife.bind(this, v);
getActivity().setTitle(R.string.magisk);
mm = getApplication();
expandableContainer.expandLayout = expandLayout;
setupExpandable();
mSwipeRefreshLayout.setOnRefreshListener(this);
updateUI();
return v;
}
@Override
public void onRefresh() {
mm.getMagiskInfo();
updateUI();
magiskUpdateText.setText(R.string.checking_for_updates);
magiskUpdateProgress.setVisibility(View.VISIBLE);
magiskUpdateIcon.setVisibility(View.GONE);
safetyNetStatusText.setText(R.string.safetyNet_check_text);
mm.safetyNetDone.hasPublished = false;
mm.updateCheckDone.hasPublished = false;
mm.remoteMagiskVersionString = null;
mm.remoteMagiskVersionCode = -1;
collapse();
shownDialog = false;
// Trigger state check
if (Utils.checkNetworkStatus(mm)) {
new CheckUpdates(getActivity()).exec();
} else {
mSwipeRefreshLayout.setRefreshing(false);
}
}
@Override
public void onTopicPublished(Topic topic) {
if (topic == mm.updateCheckDone) {
updateCheckUI();
} else if (topic == mm.safetyNetDone) {
updateSafetyNetUI();
}
}
@Override
public Topic[] getSubscription() {
return new Topic[] { mm.updateCheckDone, mm.safetyNetDone };
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override
public Container getContainer() {
return expandableContainer;
}
public String getSelectedBootImage() {
if (Shell.rootAccess()) {
if (mm.bootBlock != null) {
return mm.bootBlock;
} else {
int idx = spinner.getSelectedItemPosition();
if (idx > 0) {
return mm.blockList.get(idx - 1);
} else {
SnackbarMaker.make(getActivity(),
R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
return null;
}
}
} else {
return null;
}
}
private void updateUI() {
((MainActivity) getActivity()).checkHideSection();
boolean hasNetwork = Utils.checkNetworkStatus(getActivity());
boolean hasRoot = Shell.rootAccess();
boolean isUpToDate = mm.magiskVersionCode > 1300;
magiskUpdateCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
safetyNetCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
bootImageCard.setVisibility(hasNetwork && hasRoot ? View.VISIBLE : View.GONE);
installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE);
uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE);
int image, color;
if (mm.magiskVersionCode < 0) {
color = colorBad;
image = R.drawable.ic_cancel;
magiskVersionText.setText(R.string.magisk_version_error);
} else {
color = colorOK;
image = R.drawable.ic_check_circle;
magiskVersionText.setText(getString(R.string.current_magisk_title, "v" + mm.magiskVersionString));
}
magiskStatusIcon.setImageResource(image);
magiskStatusIcon.setColorFilter(color);
switch (Shell.rootStatus) {
case 0:
color = colorBad;
image = R.drawable.ic_cancel;
rootStatusText.setText(R.string.not_rooted);
break;
case 1:
if (mm.suVersion != null) {
color = colorOK;
image = R.drawable.ic_check_circle;
rootStatusText.setText(mm.suVersion);
break;
}
case -1:
default:
color = colorNeutral;
image = R.drawable.ic_help;
rootStatusText.setText(R.string.root_error);
}
rootStatusIcon.setImageResource(image);
rootStatusIcon.setColorFilter(color);
List<String> items = new ArrayList<>();
if (mm.bootBlock != null) {
items.add(getString(R.string.auto_detect, mm.bootBlock));
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
if (mm.blockList != null)
items.addAll(mm.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
private void updateCheckUI() {
int image, color;
if (mm.remoteMagiskVersionCode < 0) {
color = colorNeutral;
image = R.drawable.ic_help;
magiskUpdateText.setText(R.string.cannot_check_updates);
} else {
color = colorOK;
image = R.drawable.ic_check_circle;
magiskUpdateText.setText(getString(R.string.install_magisk_title, "v" + mm.remoteMagiskVersionString));
}
installButton.setVisibility(View.VISIBLE);
if (mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
installText.setText(getString(R.string.update, getString(R.string.app_name)));
} else if (mm.magiskVersionCode > 0 && mm.remoteMagiskVersionCode > mm.magiskVersionCode) {
installText.setText(getString(R.string.update, getString(R.string.magisk)));
} else {
installText.setText(R.string.install);
}
if (!shownDialog && (mm.remoteMagiskVersionCode > mm.magiskVersionCode
|| mm.remoteManagerVersionCode > BuildConfig.VERSION_CODE)) {
install();
}
magiskUpdateIcon.setImageResource(image);
magiskUpdateIcon.setColorFilter(color);
magiskUpdateIcon.setVisibility(View.VISIBLE);
magiskUpdateProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
private void updateSafetyNetUI() {
int image, color;
safetyNetProgress.setVisibility(View.GONE);
safetyNetRefreshIcon.setVisibility(View.VISIBLE);
if (mm.SNCheckResult.failed) {
safetyNetStatusText.setText(mm.SNCheckResult.errmsg);
collapse();
} else {
safetyNetStatusText.setText(R.string.safetyNet_check_success);
if (mm.SNCheckResult.ctsProfile) {
color = colorOK;
image = R.drawable.ic_check_circle;
} else {
color = colorBad;
image = R.drawable.ic_cancel;
}
ctsStatusText.setText("ctsProfile: " + mm.SNCheckResult.ctsProfile);
ctsStatusIcon.setImageResource(image);
ctsStatusIcon.setColorFilter(color);
if (mm.SNCheckResult.basicIntegrity) {
color = colorOK;
image = R.drawable.ic_check_circle;
} else {
color = colorBad;
image = R.drawable.ic_cancel;
}
basicStatusText.setText("basicIntegrity: " + mm.SNCheckResult.basicIntegrity);
basicStatusIcon.setImageResource(image);
basicStatusIcon.setColorFilter(color);
expand();
}
}
}

View File

@@ -1,12 +1,9 @@
package com.topjohnwu.magisk;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -15,16 +12,14 @@ import android.view.ViewGroup;
import android.widget.SearchView;
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
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 com.topjohnwu.magisk.utils.Topic;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class MagiskHideFragment extends Fragment implements CallbackEvent.Listener<Void> {
public class MagiskHideFragment extends Fragment implements Topic.Subscriber {
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@@ -46,13 +41,12 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
unbinder = ButterKnife.bind(this, view);
PackageManager packageManager = getActivity().getPackageManager();
lastFilter = "";
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setOnRefreshListener(() -> new MagiskHide(getActivity()).list());
mSwipeRefreshLayout.setOnRefreshListener(() -> appAdapter.refresh());
appAdapter = new ApplicationAdapter(packageManager);
appAdapter = new ApplicationAdapter(getActivity());
recyclerView.setAdapter(appAdapter);
searchListener = new SearchView.OnQueryTextListener() {
@@ -71,8 +65,7 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
}
};
if (getApplication().magiskHideDone.isTriggered)
onTrigger(getApplication().magiskHideDone);
getActivity().setTitle(R.string.magiskhide);
return view;
}
@@ -80,23 +73,10 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_magiskhide, menu);
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.app_search));
SearchView search = (SearchView) menu.findItem(R.id.app_search).getActionView();
search.setOnQueryTextListener(searchListener);
}
@Override
public void onStart() {
super.onStart();
getActivity().setTitle(R.string.magiskhide);
getApplication().magiskHideDone.register(this);
}
@Override
public void onStop() {
getApplication().magiskHideDone.unRegister(this);
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -104,12 +84,13 @@ public class MagiskHideFragment extends Fragment implements CallbackEvent.Listen
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("MagiskHideFragment: UI refresh");
appAdapter.setLists(getApplication().appList, getApplication().magiskHideList);
public void onTopicPublished(Topic topic) {
mSwipeRefreshLayout.setRefreshing(false);
if (!TextUtils.isEmpty(lastFilter)) {
appAdapter.filter(lastFilter);
}
appAdapter.filter(lastFilter);
}
@Override
public Topic[] getSubscription() {
return new Topic[] { getApplication().magiskHideDone };
}
}

View File

@@ -2,15 +2,10 @@ 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;
@@ -24,7 +19,7 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.SerialTask;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
@@ -34,7 +29,6 @@ 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;
@@ -45,25 +39,18 @@ 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);
setHasOptionsMenu(true);
txtLog.setTextIsSelectable(true);
@@ -97,13 +84,14 @@ public class MagiskLogFragment extends Fragment {
@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();
Utils.runWithPermission(getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> new LogManager().save());
return true;
case R.id.menu_clear:
new LogManager().clear();
@@ -113,24 +101,14 @@ public class MagiskLogFragment extends Fragment {
}
}
@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();
}
private class LogManager extends ParallelTask<Object, Void, Object> {
private int mode;
private File targetFile;
LogManager() {
super(MagiskLogFragment.this.getActivity());
}
}
public class LogManager extends SerialTask<Object, Void, Object> {
int mode;
File targetFile;
@SuppressLint("DefaultLocale")
@Override
@@ -138,33 +116,16 @@ public class MagiskLogFragment extends Fragment {
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 "";
StringBuildingList logList = new StringBuildingList();
getShell().su(logList, "cat " + MAGISK_LOG);
return logList.toString();
case 1:
Shell.su("echo > " + MAGISK_LOG);
getShell().su_raw("echo -n > " + MAGISK_LOG);
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
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",
@@ -172,25 +133,21 @@ public class MagiskLogFragment extends Fragment {
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);
targetFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs())
|| (targetFile.exists() && !targetFile.delete()))
|| (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;
try (FileWriter out = new FileWriter(targetFile)) {
FileWritingList fileWritingList = new FileWritingList(out);
getShell().su(fileWritingList, "cat " + MAGISK_LOG);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
return null;
}
@@ -198,12 +155,10 @@ public class MagiskLogFragment extends Fragment {
@Override
protected void onPostExecute(Object o) {
if (o == null) return;
boolean bool;
String llog;
switch (mode) {
case 0:
case 1:
llog = (String) o;
String llog = (String) o;
progressBar.setVisibility(View.GONE);
if (TextUtils.isEmpty(llog))
txtLog.setText(R.string.log_is_empty);
@@ -213,26 +168,64 @@ public class MagiskLogFragment extends Fragment {
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();
boolean bool = (boolean) o;
if (bool) {
getMagiskManager().toast(targetFile.toString(), Toast.LENGTH_LONG);
} else {
getMagiskManager().toast(R.string.logs_save_failed, Toast.LENGTH_LONG);
}
break;
}
}
public void read() {
void read() {
exec(0);
}
public void clear() {
void clear() {
exec(1);
}
public void save() {
void save() {
exec(2);
}
}
private static class StringBuildingList extends Shell.AbstractList<String> {
StringBuilder builder;
StringBuildingList() {
builder = new StringBuilder();
}
@Override
public boolean add(String s) {
builder.append(s).append("\n");
return true;
}
@Override
public String toString() {
return builder.toString();
}
}
private static class FileWritingList extends Shell.AbstractList<String> {
private FileWriter writer;
FileWritingList(FileWriter out) {
writer = out;
}
@Override
public boolean add(String s) {
try {
writer.write(s + "\n");
} catch (IOException ignored) {}
return true;
}
}
}

View File

@@ -1,81 +1,189 @@
package com.topjohnwu.magisk;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.SparseArray;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.DownloadBusybox;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.asyncs.UpdateRepos;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
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.services.UpdateCheckService;
import com.topjohnwu.magisk.superuser.SuReceiver;
import com.topjohnwu.magisk.superuser.SuRequestActivity;
import com.topjohnwu.magisk.utils.SafetyNetHelper;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
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 MAGISK_HOST_FILE = "/magisk/.core/hosts";
public static final String TMP_FOLDER_PATH = "/dev/tmp";
public static final String MAGISK_PATH = "/magisk";
public static final String INTENT_SECTION = "section";
public static final String INTENT_VERSION = "version";
public static final String INTENT_LINK = "link";
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
public static final String DISABLE_INDICATION_PROP = "ro.magisk.disable";
public static final String NOTIFICATION_CHANNEL = "magisk_update_notice";
public static final String BUSYBOXPATH = "/dev/magisk/bin";
public static final int UPDATE_SERVICE_ID = 1;
// Events
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<>();
// Topics
public final Topic magiskHideDone = new Topic();
public final Topic reloadActivity = new Topic();
public final Topic moduleLoadDone = new Topic();
public final Topic repoLoadDone = new Topic();
public final Topic updateCheckDone = new Topic();
public final Topic safetyNetDone = new Topic();
public final Topic localeDone = new Topic();
// Info
public double magiskVersion;
public String magiskVersionString;
public double remoteMagiskVersion = -1;
public int magiskVersionCode = -1;
public String remoteMagiskVersionString;
public int remoteMagiskVersionCode = -1;
public String magiskLink;
public String releaseNoteLink;
public int SNCheckResult = -1;
public String remoteManagerVersionString;
public int remoteManagerVersionCode = -1;
public String managerLink;
public SafetyNetHelper.Result SNCheckResult;
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 Map<String, Module> moduleMap;
public List<String> blockList;
public List<ApplicationInfo> appList;
public List<String> magiskHideList;
public List<Locale> locales;
// Configurations
public static boolean shellLogging;
public static boolean devLogging;
public static Locale locale;
public static Locale defaultLocale;
public boolean magiskHide;
public boolean isDarkTheme;
public boolean updateNotification;
public boolean suReauth;
public int suRequestTimeout;
public int suLogTimeout = 14;
public int suAccessState;
public int multiuserMode;
public int suResponseType;
public int suNotificationType;
public int suNamespaceMode;
public String localeConfig;
public int updateChannel;
public String bootFormat;
// Global resources
public SharedPreferences prefs;
public SuDatabaseHelper suDB;
public RepoDatabaseHelper repoDB;
public Shell shell;
private static Handler mHandler = new Handler();
private boolean started = false;
private static class LoadLocale extends ParallelTask<Void, Void, Void> {
LoadLocale(Context context) {
super(context);
}
@Override
protected Void doInBackground(Void... voids) {
getMagiskManager().locales = Utils.getAvailableLocale(getMagiskManager());
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
getMagiskManager().localeDone.publish();
}
}
@Override
public void onCreate() {
super.onCreate();
prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists()) {
// Don't migrate yet, wait and check Magisk version
suDB = new SuDatabaseHelper(this);
} else {
suDB = new SuDatabaseHelper(Utils.getEncContext(this));
}
repoDB = new RepoDatabaseHelper(this);
defaultLocale = Locale.getDefault();
setLocale();
loadConfig();
}
public void setLocale() {
localeConfig = prefs.getString("locale", "");
if (localeConfig.isEmpty()) {
locale = defaultLocale;
} else {
locale = Locale.forLanguageTag(localeConfig);
}
Resources res = getBaseContext().getResources();
Configuration config = new Configuration(res.getConfiguration());
config.setLocale(locale);
res.updateConfiguration(config, res.getDisplayMetrics());
}
public void loadConfig() {
isDarkTheme = prefs.getBoolean("dark_theme", false);
if (BuildConfig.DEBUG) {
devLogging = prefs.getBoolean("developer_logging", false);
shellLogging = prefs.getBoolean("shell_logging", false);
} else {
devLogging = false;
shellLogging = false;
}
// su
suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", SuRequestActivity.PROMPT);
suNotificationType = Utils.getPrefsInt(prefs, "su_notification", SuReceiver.TOAST);
suReauth = prefs.getBoolean("su_reauth", false);
suAccessState = suDB.getSettings(SuDatabaseHelper.ROOT_ACCESS, SuDatabaseHelper.ROOT_ACCESS_APPS_AND_ADB);
multiuserMode = suDB.getSettings(SuDatabaseHelper.MULTIUSER_MODE, SuDatabaseHelper.MULTIUSER_MODE_OWNER_ONLY);
suNamespaceMode = suDB.getSettings(SuDatabaseHelper.MNT_NS, SuDatabaseHelper.NAMESPACE_MODE_REQUESTER);
updateNotification = prefs.getBoolean("notification", true);
updateChannel = Utils.getPrefsInt(prefs, "update_channel", CheckUpdates.STABLE_CHANNEL);
bootFormat = prefs.getString("boot_format", ".img");
}
public void toast(String msg, int duration) {
@@ -86,82 +194,138 @@ public class MagiskManager extends Application {
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);
// Always start a new root shell manually, just for safety
Shell.init();
updateMagiskInfo();
initSuAccess();
initSuConfigs();
// Initialize prefs
public void startup() {
if (started)
return;
started = true;
boolean hasNetwork = Utils.checkNetworkStatus(this);
getMagiskInfo();
// Check if we need to migrate suDB
if (getDatabasePath(SuDatabaseHelper.DB_NAME).exists() && Utils.useFDE(this)) {
if (magiskVersionCode >= 1410) {
suDB.close();
Context de = createDeviceProtectedStorageContext();
de.moveDatabaseFrom(this, SuDatabaseHelper.DB_NAME);
suDB = new SuDatabaseHelper(de);
}
}
new LoadLocale(this).exec();
// Root actions
if (Shell.rootAccess()) {
if (hasNetwork && !Utils.itemExist(shell, BUSYBOXPATH + "/busybox")) {
try {
// Force synchronous, make sure we have busybox to use
new DownloadBusybox(this).exec().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
try (InputStream in = getAssets().open(Utils.UTIL_FUNCTIONS)) {
shell.loadInputStream(in);
} catch (IOException e) {
e.printStackTrace();
}
shell.su_raw(
"export PATH=" + BUSYBOXPATH + ":$PATH",
"mount_partitions",
"BOOTIMAGE=",
"find_boot_image",
"migrate_boot_backup"
);
List<String> res = shell.su("echo \"$BOOTIMAGE\"");
if (Utils.isValidShellResponse(res)) {
bootBlock = res.get(0);
} else {
blockList = shell.su("find /dev/block -type b | grep -vE 'dm|ram|loop'");
}
}
// Write back default values
prefs.edit()
.putBoolean("dark_theme", isDarkTheme)
.putBoolean("magiskhide", magiskHide)
.putBoolean("busybox", Utils.commandExists("busybox"))
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
.putBoolean("disable", Utils.itemExist(MAGISK_DISABLE_FILE))
.putBoolean("notification", updateNotification)
.putBoolean("hosts", Utils.itemExist(shell, MAGISK_HOST_FILE))
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
.putBoolean("su_reauth", suReauth)
.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))
.putString("multiuser_mode", String.valueOf(multiuserMode))
.putString("mnt_ns", String.valueOf(suNamespaceMode))
.putString("update_channel", String.valueOf(updateChannel))
.putString("locale", localeConfig)
.putString("boot_format", bootFormat)
.apply();
// Create notification channel on Android O
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL,
getString(R.string.magisk_updates), NotificationManager.IMPORTANCE_DEFAULT);
getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
LoadModules loadModuleTask = new LoadModules(this);
// Start update check job
if (hasNetwork) {
ComponentName service = new ComponentName(this, UpdateCheckService.class);
JobInfo jobInfo = new JobInfo.Builder(UPDATE_SERVICE_ID, service)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setPeriodic(8 * 60 * 60 * 1000)
.build();
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo);
loadModuleTask.setCallBack(() -> new UpdateRepos(this).exec());
}
// Fire asynctasks
loadModuleTask.exec();
}
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");
public void getMagiskInfo() {
List<String> ret;
Shell.getShell(this);
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");
ret = shell.sh("magisk -v");
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 magisk.version");
if (Utils.isValidShellResponse(ret)) {
try {
magiskVersionString = ret.get(0);
magiskVersionCode = (int) Double.parseDouble(ret.get(0)) * 10;
} catch (NumberFormatException ignored) {}
}
} else {
magiskVersionString = ret.get(0).split(":")[0];
ret = shell.sh("magisk -V");
try {
magiskVersionCode = Integer.parseInt(ret.get(0));
} catch (NumberFormatException ignored) {}
}
ret = Shell.sh("getprop ro.magisk.disable");
ret = shell.sh("getprop " + DISABLE_INDICATION_PROP);
try {
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
disabled = false;
}
ret = Shell.sh("getprop persist.magisk.hide");
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
try {
magiskHideStarted = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
magiskHideStarted = false;
}
if (!magiskHide && magiskHideStarted)
magiskHide = true;
}
}
}

View File

@@ -1,15 +1,11 @@
package com.topjohnwu.magisk;
import android.Manifest;
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.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
@@ -20,16 +16,15 @@ import android.view.MenuItem;
import android.view.View;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends Activity
implements NavigationView.OnNavigationItemSelectedListener, CallbackEvent.Listener<Void> {
public static final String SECTION = "section";
implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber {
private final Handler mDrawerHandler = new Handler();
private SharedPreferences prefs;
@@ -43,24 +38,20 @@ public class MainActivity extends Activity
@Override
protected void onCreate(final Bundle savedInstanceState) {
getMagiskManager().startup();
prefs = getApplicationContext().prefs;
prefs = getMagiskManager().prefs;
if (getApplicationContext().isDarkTheme) {
if (getMagiskManager().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
setSupportActionBar(toolbar);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
@@ -79,10 +70,9 @@ public class MainActivity extends Activity
toggle.syncState();
if (savedInstanceState == null)
navigate(getIntent().getStringExtra(SECTION));
navigate(getIntent().getStringExtra(MagiskManager.INTENT_SECTION));
navigationView.setNavigationItemSelectedListener(this);
getApplicationContext().reloadMainActivity.register(this);
}
@@ -92,30 +82,15 @@ public class MainActivity extends Activity
checkHideSection();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
navigate(savedInstanceState.getInt(SECTION, R.id.status));
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SECTION, mDrawerItem);
}
@Override
protected void onDestroy() {
getApplicationContext().reloadMainActivity.unRegister(this);
super.onDestroy();
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(navigationView))
if (drawer.isDrawerOpen(navigationView)) {
drawer.closeDrawer(navigationView);
else
} else if (mDrawerItem != R.id.magisk) {
navigate(R.id.magisk);
} else {
finish();
}
}
@Override
@@ -127,32 +102,35 @@ public class MainActivity extends Activity
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
public void onTopicPublished(Topic topic) {
recreate();
}
private void checkHideSection() {
@Override
public Topic[] getSubscription() {
return new Topic[] { getMagiskManager().reloadActivity };
}
public void checkHideSection() {
Menu menu = navigationView.getMenu();
if (Shell.rootAccess()) {
menu.findItem(R.id.magiskhide).setVisible(
getApplicationContext().magiskVersion >= 8 && prefs.getBoolean("magiskhide", false));
menu.findItem(R.id.modules).setVisible(getApplicationContext().magiskVersion >= 4);
menu.findItem(R.id.downloads).setVisible(getApplicationContext().magiskVersion >= 4);
menu.findItem(R.id.log).setVisible(true);
menu.findItem(R.id.superuser).setVisible(getApplicationContext().isSuClient);
}
menu.findItem(R.id.install).setVisible(getApplicationContext().remoteMagiskVersion > 0);
menu.findItem(R.id.magiskhide).setVisible(
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 1300
&& prefs.getBoolean("magiskhide", false));
menu.findItem(R.id.modules).setVisible(
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
menu.findItem(R.id.downloads).setVisible(Utils.checkNetworkStatus(this) &&
Shell.rootAccess() && getMagiskManager().magiskVersionCode >= 0);
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
menu.findItem(R.id.superuser).setVisible(
Shell.rootAccess() && getMagiskManager().isSuClient);
}
public void navigate(String item) {
int itemId = R.id.status;
int itemId = R.id.magisk;
if (item != null) {
switch (item) {
case "status":
itemId = R.id.status;
break;
case "install":
itemId = R.id.install;
case "magisk":
itemId = R.id.magisk;
break;
case "superuser":
itemId = R.id.superuser;
@@ -181,14 +159,12 @@ public class MainActivity extends Activity
}
public void navigate(int itemId) {
int bak = mDrawerItem;
mDrawerItem = itemId;
navigationView.setCheckedItem(itemId);
switch (itemId) {
case R.id.status:
displayFragment(new StatusFragment(), "status", true);
break;
case R.id.install:
displayFragment(new InstallFragment(), "install", true);
case R.id.magisk:
displayFragment(new MagiskFragment(), "magisk", true);
break;
case R.id.superuser:
displayFragment(new SuperuserFragment(), "superuser", true);
@@ -207,9 +183,11 @@ public class MainActivity extends Activity
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));
mDrawerItem = bak;
break;
}
}

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
@@ -12,23 +11,22 @@ import android.view.View;
import android.view.ViewGroup;
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.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Topic;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
public class ModulesFragment extends Fragment implements CallbackEvent.Listener<Void> {
public class ModulesFragment extends Fragment implements Topic.Subscriber {
private static final int FETCH_ZIP_CODE = 2;
@@ -36,7 +34,12 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.fab) FloatingActionButton fabio;
@OnClick(R.id.fab)
public void selectFile() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("application/zip");
startActivityForResult(intent, FETCH_ZIP_CODE);
}
private List<Module> listModules = new ArrayList<>();
@@ -46,12 +49,6 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
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);
intent.setType("application/zip");
startActivityForResult(intent, FETCH_ZIP_CODE);
});
mSwipeRefreshLayout.setOnRefreshListener(() -> {
recyclerView.setVisibility(View.GONE);
new LoadModules(getActivity()).exec();
@@ -69,40 +66,30 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener<
}
});
if (getApplication().moduleLoadDone.isTriggered) {
updateUI();
}
getActivity().setTitle(R.string.modules);
return view;
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
public void onTopicPublished(Topic topic) {
Logger.dev("ModulesFragment: UI refresh triggered");
updateUI();
}
@Override
public Topic[] getSubscription() {
return new Topic[] { getApplication().moduleLoadDone };
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
final Uri uri = data.getData();
new FlashZip(getActivity(), uri).exec();
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(data.getData()).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
startActivity(intent);
}
}
@Override
public void onStart() {
super.onStart();
getApplication().moduleLoadDone.register(this);
getActivity().setTitle(R.string.modules);
}
@Override
public void onStop() {
getApplication().moduleLoadDone.unRegister(this);
super.onStop();
}
@Override

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -14,39 +13,23 @@ import android.widget.SearchView;
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.asyncs.UpdateRepos;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import com.topjohnwu.magisk.utils.Topic;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class ReposFragment extends Fragment implements CallbackEvent.Listener<Void> {
public class ReposFragment extends Fragment implements Topic.Subscriber {
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
private List<Repo> mUpdateRepos = new ArrayList<>();
private List<Repo> mInstalledRepos = new ArrayList<>();
private List<Repo> mOthersRepos = new ArrayList<>();
private List<Repo> fUpdateRepos = new ArrayList<>();
private List<Repo> fInstalledRepos = new ArrayList<>();
private List<Repo> fOthersRepos = new ArrayList<>();
private SimpleSectionedRecyclerViewAdapter mSectionedAdapter;
private SearchView.OnQueryTextListener searchListener;
private ReposAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -60,24 +43,40 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
View view = inflater.inflate(R.layout.fragment_repos, container, false);
unbinder = ButterKnife.bind(this, view);
mSectionedAdapter = new SimpleSectionedRecyclerViewAdapter(R.layout.section,
R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos));
recyclerView.setAdapter(mSectionedAdapter);
adapter = new ReposAdapter(getApplication().repoDB, getApplication().moduleMap);
recyclerView.setAdapter(adapter);
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setOnRefreshListener(() -> {
recyclerView.setVisibility(View.GONE);
new LoadRepos(getActivity()).exec();
new UpdateRepos(getActivity()).exec();
});
if (getApplication().repoLoadDone.isTriggered) {
reloadRepos();
updateUI();
}
getActivity().setTitle(R.string.downloads);
searchListener = new SearchView.OnQueryTextListener() {
return view;
}
@Override
public void onTopicPublished(Topic topic) {
Logger.dev("ReposFragment: UI refresh triggered");
mSwipeRefreshLayout.setRefreshing(false);
adapter.notifyDBChanged();
recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
@Override
public Topic[] getSubscription() {
return new Topic[] { getApplication().repoLoadDone };
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_repo, menu);
SearchView search = (SearchView) menu.findItem(R.id.repo_search).getActionView();
search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
@@ -85,39 +84,10 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
@Override
public boolean onQueryTextChange(String newText) {
new FilterApps().exec(newText);
adapter.filter(newText);
return false;
}
};
return view;
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
Logger.dev("ReposFragment: UI refresh triggered");
reloadRepos();
updateUI();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_repo, menu);
SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.repo_search));
search.setOnQueryTextListener(searchListener);
}
@Override
public void onStart() {
super.onStart();
getApplication().repoLoadDone.register(this);
getActivity().setTitle(R.string.downloads);
}
@Override
public void onStop() {
getApplication().repoLoadDone.unRegister(this);
super.onStop();
});
}
@Override
@@ -125,91 +95,4 @@ public class ReposFragment extends Fragment implements CallbackEvent.Listener<Vo
super.onDestroyView();
unbinder.unbind();
}
private void reloadRepos() {
mUpdateRepos.clear();
mInstalledRepos.clear();
mOthersRepos.clear();
for (Repo repo : getApplication().repoMap.values()) {
Module module = getApplication().moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode())
mUpdateRepos.add(repo);
else
mInstalledRepos.add(repo);
} else {
mOthersRepos.add(repo);
}
}
fUpdateRepos.clear();
fInstalledRepos.clear();
fOthersRepos.clear();
fUpdateRepos.addAll(mUpdateRepos);
fInstalledRepos.addAll(mInstalledRepos);
fOthersRepos.addAll(mOthersRepos);
}
private void updateUI() {
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
if (!fUpdateRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0, getString(R.string.update_available)));
}
if (!fInstalledRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size(), getString(R.string.installed)));
}
if (!fOthersRepos.isEmpty()) {
sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size() + fInstalledRepos.size(), getString(R.string.not_installed)));
}
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
mSectionedAdapter.setSections(array);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
mSwipeRefreshLayout.setRefreshing(false);
}
private class FilterApps extends ParallelTask<String, Void, Void> {
@Override
protected Void doInBackground(String... strings) {
String newText = strings[0];
fUpdateRepos.clear();
fInstalledRepos.clear();
fOthersRepos.clear();
for (Repo repo: mUpdateRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fUpdateRepos.add(repo);
}
}
for (Repo repo: mInstalledRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fInstalledRepos.add(repo);
}
}
for (Repo repo: mOthersRepos) {
if (repo.getName().toLowerCase().contains(newText.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(newText.toLowerCase())
|| repo.getDescription().toLowerCase().contains(newText.toLowerCase())
) {
fOthersRepos.add(repo);
}
}
return null;
}
@Override
protected void onPostExecute(Void v) {
updateUI();
}
}
}

View File

@@ -1,8 +1,11 @@
package com.topjohnwu.magisk;
import android.Manifest;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
@@ -10,32 +13,34 @@ import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.WindowManager;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.asyncs.SerialTask;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.HideManager;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SettingsActivity extends Activity {
public class SettingsActivity extends Activity implements Topic.Subscriber {
@BindView(R.id.toolbar) Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getApplicationContext().isDarkTheme) {
setTheme(R.style.AppTheme_Dark);
if (getMagiskManager().isDarkTheme) {
setTheme(R.style.AppTheme_Transparent_Dark);
}
setContentView(R.layout.activity_container);
setContentView(R.layout.activity_settings);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
@@ -56,31 +61,27 @@ public class SettingsActivity extends Activity {
}
public void setFloating() {
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
params.alpha = 1.0f;
params.dimAmount = 0.6f;
params.flags |= 2;
getWindow().setAttributes(params);
setFinishOnTouchOutside(true);
}
@Override
public void onTopicPublished(Topic topic) {
recreate();
}
@Override
public Topic[] getSubscription() {
return new Topic[] { getMagiskManager().reloadActivity };
}
public static class SettingsFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
implements SharedPreferences.OnSharedPreferenceChangeListener,
Topic.Subscriber {
private SharedPreferences prefs;
private PreferenceScreen prefScreen;
private ListPreference suAccess, autoRes, suNotification, requestTimeout;
private MagiskManager getApplication() {
return Utils.getMagiskManager(getActivity());
}
private ListPreference updateChannel, suAccess, autoRes, suNotification,
requestTimeout, multiuserMode, namespaceMode;
private MagiskManager mm;
private PreferenceCategory generalCatagory;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -88,39 +89,88 @@ public class SettingsActivity extends Activity {
addPreferencesFromResource(R.xml.app_settings);
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
prefScreen = getPreferenceScreen();
mm = Utils.getMagiskManager(getActivity());
SwitchPreference busybox = (SwitchPreference) findPreference("busybox");
SwitchPreference magiskHide = (SwitchPreference) findPreference("magiskhide");
SwitchPreference hosts = (SwitchPreference) findPreference("hosts");
generalCatagory = (PreferenceCategory) findPreference("general");
PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk");
PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser");
PreferenceCategory developer = (PreferenceCategory) findPreference("developer");
updateChannel = (ListPreference) findPreference("update_channel");
suAccess = (ListPreference) findPreference("su_access");
autoRes = (ListPreference) findPreference("su_auto_response");
requestTimeout = (ListPreference) findPreference("su_request_timeout");
suNotification = (ListPreference) findPreference("su_notification");
multiuserMode = (ListPreference) findPreference("multiuser_mode");
namespaceMode = (ListPreference) findPreference("mnt_ns");
SwitchPreference reauth = (SwitchPreference) findPreference("su_reauth");
Preference hideManager = findPreference("hide");
setSummary();
// Disable dangerous settings in user mode if selected owner manage
if (getActivity().getApplicationInfo().uid > 99999) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
generalCatagory.removePreference(hideManager);
}
// Remove re-authentication option on Android O, it will not work
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
suCategory.removePreference(reauth);
}
findPreference("clear").setOnPreferenceClickListener((pref) -> {
Utils.clearRepoCache(getActivity());
return true;
});
hideManager.setOnPreferenceClickListener((pref) -> {
Utils.runWithPermission(getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> new HideManager(getActivity()).exec());
return true;
});
if (!BuildConfig.DEBUG) {
prefScreen.removePreference(developer);
}
if (!Shell.rootAccess()) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
generalCatagory.removePreference(hideManager);
} else {
if (!getApplication().isSuClient)
if (!mm.isSuClient) {
prefScreen.removePreference(suCategory);
if (getApplication().magiskVersion < 11)
prefScreen.removePreference(magiskCategory);
if (getApplication().disabled) {
busybox.setEnabled(false);
magiskHide.setEnabled(false);
hosts.setEnabled(false);
}
if (mm.magiskVersionCode < 1300) {
prefScreen.removePreference(magiskCategory);
}
}
}
private void setLocalePreference(ListPreference lp) {
boolean isNew = lp == null;
if (isNew) {
lp = new ListPreference(getActivity());
}
CharSequence[] entries = new CharSequence[mm.locales.size() + 1];
CharSequence[] entryValues = new CharSequence[mm.locales.size() + 1];
entries[0] = getString(R.string.system_default);
entryValues[0] = "";
int i = 1;
for (Locale locale : mm.locales) {
entries[i] = locale.getDisplayName(locale);
entryValues[i++] = locale.toLanguageTag();
}
lp.setEntries(entries);
lp.setEntryValues(entryValues);
lp.setTitle(R.string.language);
lp.setKey("locale");
lp.setSummary(MagiskManager.locale.getDisplayName(MagiskManager.locale));
if (isNew) {
generalCatagory.addPreference(lp);
}
}
@@ -128,12 +178,14 @@ public class SettingsActivity extends Activity {
public void onResume() {
super.onResume();
prefs.registerOnSharedPreferenceChangeListener(this);
subscribeTopics();
}
@Override
public void onPause() {
super.onPause();
prefs.unregisterOnSharedPreferenceChangeListener(this);
unsubscribeTopics();
super.onPause();
}
@Override
@@ -144,111 +196,90 @@ public class SettingsActivity extends Activity {
switch (key) {
case "dark_theme":
enabled = prefs.getBoolean("dark_theme", false);
if (getApplication().isDarkTheme != enabled) {
getApplication().isDarkTheme = enabled;
getApplication().reloadMainActivity.trigger();
getActivity().finish();
getActivity().recreate();
if (mm.isDarkTheme != enabled) {
mm.reloadActivity.publish(false);
}
break;
case "disable":
enabled = prefs.getBoolean("disable", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... voids) {
if (enable) {
Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE);
}
return null;
}
}.exec();
if (enabled) {
Utils.createFile(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem(getShell(), MagiskManager.MAGISK_DISABLE_FILE);
}
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
break;
case "busybox":
enabled = prefs.getBoolean("busybox", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... voids) {
if (enable) {
Shell.su(
"setprop persist.magisk.busybox 1",
"sh /sbin/magic_mask.sh mount_busybox");
} else {
Shell.su(
"setprop persist.magisk.busybox 0",
"umount /system/xbin");
}
return null;
}
}.exec();
break;
case "magiskhide":
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();
Utils.enableMagiskHide(getShell());
} else {
Utils.disableMagiskHide(getShell());
}
break;
case "hosts":
enabled = prefs.getBoolean("hosts", false);
new SerialTask<Void, Void, Void>() {
private boolean enable = enabled;
@Override
protected Void doInBackground(Void... voids) {
if (enable) {
Shell.su("cp -af /system/etc/hosts /magisk/.core/hosts",
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
} else {
Shell.su("umount -l /system/etc/hosts",
"rm -f /magisk/.core/hosts");
}
return null;
}
}.exec();
if (enabled) {
getShell().su_raw(
"cp -af /system/etc/hosts " + MagiskManager.MAGISK_HOST_FILE,
"mount -o bind " + MagiskManager.MAGISK_HOST_FILE + " /system/etc/hosts");
} else {
getShell().su_raw(
"umount -l /system/etc/hosts",
"rm -f " + MagiskManager.MAGISK_HOST_FILE);
}
break;
case "su_access":
getApplication().suAccessState = Utils.getPrefsInt(prefs, "su_access", 0);
Shell.su("setprop persist.sys.root_access " + getApplication().suAccessState);
mm.suDB.setSettings(SuDatabaseHelper.ROOT_ACCESS, Utils.getPrefsInt(prefs, "su_access"));
break;
case "su_request_timeout":
getApplication().suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10);
case "multiuser_mode":
mm.suDB.setSettings(SuDatabaseHelper.MULTIUSER_MODE, Utils.getPrefsInt(prefs, "multiuser_mode"));
break;
case "su_auto_response":
getApplication().suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0);
case "mnt_ns":
mm.suDB.setSettings(SuDatabaseHelper.MNT_NS, Utils.getPrefsInt(prefs, "mnt_ns"));
break;
case "su_notification":
getApplication().suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1);
case "locale":
mm.setLocale();
mm.reloadActivity.publish(false);
break;
case "developer_logging":
MagiskManager.devLogging = prefs.getBoolean("developer_logging", false);
break;
case "shell_logging":
MagiskManager.shellLogging = prefs.getBoolean("shell_logging", false);
case "update_channel":
mm.updateChannel = Utils.getPrefsInt(prefs, "update_channel");
new CheckUpdates(mm, true).exec();
break;
}
mm.loadConfig();
setSummary();
}
private Shell getShell() {
return Shell.getShell(getActivity());
}
private void setSummary() {
updateChannel.setSummary(getResources()
.getStringArray(R.array.update_channel)[mm.updateChannel]);
suAccess.setSummary(getResources()
.getStringArray(R.array.su_access)[getApplication().suAccessState]);
.getStringArray(R.array.su_access)[mm.suAccessState]);
autoRes.setSummary(getResources()
.getStringArray(R.array.auto_response)[getApplication().suResponseType]);
.getStringArray(R.array.auto_response)[mm.suResponseType]);
suNotification.setSummary(getResources()
.getStringArray(R.array.su_notification)[getApplication().suNotificationType]);
.getStringArray(R.array.su_notification)[mm.suNotificationType]);
requestTimeout.setSummary(
getString(R.string.request_timeout_summary, prefs.getString("su_request_timeout", "10")));
multiuserMode.setSummary(getResources()
.getStringArray(R.array.multiuser_summary)[mm.multiuserMode]);
namespaceMode.setSummary(getResources()
.getStringArray(R.array.namespace_summary)[mm.suNamespaceMode]);
}
@Override
public void onTopicPublished(Topic topic) {
setLocalePreference((ListPreference) findPreference("locale"));
}
@Override
public Topic[] getSubscription() {
return new Topic[] { mm.localeDone };
}
}

View File

@@ -1,70 +1,24 @@
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.os.Bundle;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.GetBootBlocks;
import com.topjohnwu.magisk.asyncs.LoadApps;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.services.UpdateCheckService;
public class SplashActivity extends Activity{
private static final int UPDATE_SERVICE_ID = 1;
public class SplashActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MagiskManager magiskManager = getApplicationContext();
getMagiskManager().startup();
// Init the info and configs and root shell
magiskManager.init();
// Initialize the update check service, notify every 3 hours
if (!"install".equals(getIntent().getStringExtra(MainActivity.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);
Intent intent = new Intent(this, MainActivity.class);
String section = getIntent().getStringExtra(MagiskManager.INTENT_SECTION);
if (section != null) {
intent.putExtra(MagiskManager.INTENT_SECTION, section);
}
// Now fire all async tasks
new GetBootBlocks(this).exec();
if (magiskManager.magiskHide && !magiskManager.disabled &&
magiskManager.magiskVersion > 11 && !magiskManager.magiskHideStarted) {
new MagiskHide().enable();
}
new LoadModules(this) {
@Override
protected void onPostExecute(Void v) {
super.onPostExecute(v);
new LoadRepos(activity).exec();
}
}.exec();
new LoadApps(this).exec();
new CheckUpdates(this, false){
@Override
protected void onPostExecute(Void v) {
super.onPostExecute(v);
Intent intent = getIntent().setClass(magiskManager, MainActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}
}.exec();
startActivity(intent);
finish();
}
}

View File

@@ -1,274 +0,0 @@
package com.topjohnwu.magisk;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class StatusFragment extends Fragment implements CallbackEvent.Listener<Void> {
private static boolean noDialog = false;
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.magisk_status_container) View magiskStatusContainer;
@BindView(R.id.magisk_status_icon) ImageView magiskStatusIcon;
@BindView(R.id.magisk_version) TextView magiskVersionText;
@BindView(R.id.magisk_update_status) TextView magiskUpdateText;
@BindView(R.id.magisk_check_updates_progress) ProgressBar magiskCheckUpdatesProgress;
@BindView(R.id.root_status_container) View rootStatusContainer;
@BindView(R.id.root_status_icon) ImageView rootStatusIcon;
@BindView(R.id.root_status) TextView rootStatusText;
@BindView(R.id.root_info) TextView rootInfoText;
@BindView(R.id.safetyNet_container) View safetyNetContainer;
@BindView(R.id.safetyNet_icon) ImageView safetyNetIcon;
@BindView(R.id.safetyNet_status) TextView safetyNetStatusText;
@BindView(R.id.safetyNet_check_progress) ProgressBar safetyNetProgress;
@BindColor(R.color.red500) int colorBad;
@BindColor(R.color.green500) int colorOK;
@BindColor(R.color.yellow500) int colorWarn;
@BindColor(R.color.grey500) int colorNeutral;
@BindColor(R.color.blue500) int colorInfo;
@BindColor(android.R.color.transparent) int trans;
int defaultColor;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_status, container, false);
unbinder = ButterKnife.bind(this, v);
defaultColor = magiskUpdateText.getCurrentTextColor();
mSwipeRefreshLayout.setOnRefreshListener(() -> {
magiskStatusContainer.setBackgroundColor(trans);
magiskStatusIcon.setImageResource(0);
magiskUpdateText.setText(R.string.checking_for_updates);
magiskCheckUpdatesProgress.setVisibility(View.VISIBLE);
magiskUpdateText.setTextColor(defaultColor);
safetyNetProgress.setVisibility(View.GONE);
safetyNetContainer.setBackgroundColor(colorNeutral);
safetyNetIcon.setImageResource(R.drawable.ic_safetynet);
safetyNetStatusText.setText(R.string.safetyNet_check_text);
safetyNetStatusText.setTextColor(defaultColor);
getApplication().safetyNetDone.isTriggered = false;
noDialog = false;
updateUI();
new CheckUpdates(getActivity()).exec();
});
safetyNetContainer.setOnClickListener(view -> {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetContainer.setBackgroundColor(trans);
safetyNetIcon.setImageResource(0);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
Utils.checkSafetyNet(getApplication());
});
magiskStatusContainer.setOnClickListener(view -> {
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
try {
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
} catch (IllegalStateException ignored) {}
});
if (getApplication().magiskVersion < 0 && Shell.rootAccess() && !noDialog) {
noDialog = true;
new AlertDialogBuilder(getActivity())
.setTitle(R.string.no_magisk_title)
.setMessage(R.string.no_magisk_msg)
.setCancelable(true)
.setPositiveButton(R.string.goto_install, (dialogInterface, i) -> {
((MainActivity) getActivity()).navigationView.setCheckedItem(R.id.install);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
try {
transaction.replace(R.id.content_frame, new InstallFragment(), "install").commit();
} catch (IllegalStateException ignored) {}
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
updateUI();
if (getApplication().updateCheckDone.isTriggered)
updateCheckUI();
if (getApplication().safetyNetDone.isTriggered)
updateSafetyNetUI();
return v;
}
@Override
public void onTrigger(CallbackEvent<Void> event) {
if (event == getApplication().updateCheckDone) {
Logger.dev("StatusFragment: Update Check UI refresh triggered");
updateCheckUI();
} else if (event == getApplication().safetyNetDone) {
Logger.dev("StatusFragment: SafetyNet UI refresh triggered");
updateSafetyNetUI();
}
}
@Override
public void onStart() {
super.onStart();
getApplication().updateCheckDone.register(this);
getApplication().safetyNetDone.register(this);
getActivity().setTitle(R.string.status);
}
@Override
public void onStop() {
getApplication().updateCheckDone.unRegister(this);
getApplication().safetyNetDone.unRegister(this);
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
private void updateUI() {
int image, color;
getApplication().updateMagiskInfo();
if (getApplication().magiskVersion < 0) {
magiskVersionText.setText(R.string.magisk_version_error);
} else if (getApplication().disabled) {
magiskVersionText.setText(getString(R.string.magisk_version_disable, getApplication().magiskVersionString));
} else {
magiskVersionText.setText(getString(R.string.magisk_version, getApplication().magiskVersionString));
}
switch (Shell.rootStatus) {
case 0:
color = colorBad;
image = R.drawable.ic_cancel;
rootStatusText.setText(R.string.not_rooted);
rootInfoText.setText(R.string.root_info_warning);
break;
case 1:
if (getApplication().suVersion != null) {
color = colorOK;
image = R.drawable.ic_check_circle;
rootStatusText.setText(R.string.proper_root);
rootInfoText.setText(getApplication().suVersion);
break;
}
case -1:
default:
color = colorNeutral;
image = R.drawable.ic_help;
rootStatusText.setText(R.string.root_error);
rootInfoText.setText(R.string.root_info_warning);
}
rootStatusContainer.setBackgroundColor(color);
rootStatusText.setTextColor(color);
rootInfoText.setTextColor(color);
rootStatusIcon.setImageResource(image);
}
private void updateCheckUI() {
int image, color;
if (getApplication().remoteMagiskVersion < 0) {
color = colorNeutral;
image = R.drawable.ic_help;
magiskUpdateText.setText(R.string.cannot_check_updates);
} else if (getApplication().remoteMagiskVersion > getApplication().magiskVersion) {
color = colorInfo;
image = R.drawable.ic_update;
magiskUpdateText.setText(getString(R.string.magisk_update_available, getApplication().remoteMagiskVersion));
} else {
color = colorOK;
image = R.drawable.ic_check_circle;
magiskUpdateText.setText(getString(R.string.up_to_date, getString(R.string.magisk)));
}
if (getApplication().magiskVersion < 0) {
color = colorBad;
image = R.drawable.ic_cancel;
} else if (getApplication().disabled) {
color = colorNeutral;
image = R.drawable.ic_cancel;
}
magiskStatusContainer.setBackgroundColor(color);
magiskVersionText.setTextColor(color);
magiskUpdateText.setTextColor(color);
magiskStatusIcon.setImageResource(image);
magiskCheckUpdatesProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
private void updateSafetyNetUI() {
int image, color;
safetyNetProgress.setVisibility(View.GONE);
switch (getApplication().SNCheckResult) {
case -3:
color = colorNeutral;
image = R.drawable.ic_help;
safetyNetStatusText.setText(R.string.safetyNet_connection_suspended);
break;
case -2:
color = colorNeutral;
image = R.drawable.ic_help;
safetyNetStatusText.setText(R.string.safetyNet_connection_failed);
break;
case -1:
color = colorNeutral;
image = R.drawable.ic_help;
safetyNetStatusText.setText(R.string.safetyNet_error);
break;
case 0:
color = colorBad;
image = R.drawable.ic_cancel;
safetyNetStatusText.setText(R.string.safetyNet_fail);
break;
case 1:
default:
color = colorOK;
image = R.drawable.ic_check_circle;
safetyNetStatusText.setText(R.string.safetyNet_pass);
break;
}
safetyNetContainer.setBackgroundColor(color);
safetyNetStatusText.setTextColor(color);
safetyNetIcon.setImageResource(image);
}
}

View File

@@ -13,10 +13,6 @@ 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;
@@ -28,7 +24,8 @@ public class SuLogFragment extends Fragment {
@BindView(R.id.recyclerView) RecyclerView recyclerView;
private Unbinder unbinder;
private SuLogDatabaseHelper dbHelper;
private MagiskManager mm;
private SuLogAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -48,8 +45,9 @@ public class SuLogFragment extends Fragment {
// 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());
mm = getApplication();
adapter = new SuLogAdapter(mm.suDB);
recyclerView.setAdapter(adapter);
updateList();
@@ -57,13 +55,12 @@ public class SuLogFragment extends Fragment {
}
private void updateList() {
List<SuLogEntry> logs = dbHelper.getLogList();
adapter.notifyDBChanged();
if (logs.size() == 0) {
if (adapter.getSectionCount() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new SuLogAdapter(logs).getAdapter());
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
@@ -76,7 +73,7 @@ public class SuLogFragment extends Fragment {
updateList();
return true;
case R.id.menu_clear:
dbHelper.clearLogs();
mm.suDB.clearLogs();
updateList();
return true;
default:

View File

@@ -11,7 +11,6 @@ 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;
@@ -33,15 +32,15 @@ public class SuperuserFragment extends Fragment {
unbinder = ButterKnife.bind(this, view);
PackageManager pm = getActivity().getPackageManager();
MagiskManager mm = getApplication();
SuDatabaseHelper dbHelper = new SuDatabaseHelper(getActivity());
List<Policy> policyList = dbHelper.getPolicyList(pm);
List<Policy> policyList = mm.suDB.getPolicyList(pm);
if (policyList.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new PolicyAdapter(policyList, dbHelper, pm));
recyclerView.setAdapter(new PolicyAdapter(policyList, mm.suDB, pm));
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.adapters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.design.widget.Snackbar;
@@ -13,13 +14,16 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.MagiskHide;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import butterknife.BindView;
@@ -40,20 +44,19 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
private List<ApplicationInfo> mOriginalList, mList;
private List<String> mHideList;
private PackageManager packageManager;
private PackageManager pm;
private ApplicationFilter filter;
private Topic magiskHideDone;
private Shell shell;
public ApplicationAdapter(PackageManager packageManager) {
public ApplicationAdapter(Context context) {
mOriginalList = mList = Collections.emptyList();
mHideList = Collections.emptyList();
this.packageManager = packageManager;
filter = new ApplicationFilter();
}
public void setLists(List<ApplicationInfo> listApps, List<String> hideList) {
mOriginalList = mList = listApps;
mHideList = hideList;
notifyDataSetChanged();
pm = context.getPackageManager();
magiskHideDone = Utils.getMagiskManager(context).magiskHideDone;
shell = Shell.getShell(context);
new LoadApps().exec();
}
@Override
@@ -66,8 +69,8 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
public void onBindViewHolder(final ViewHolder holder, int position) {
ApplicationInfo info = mList.get(position);
holder.appIcon.setImageDrawable(info.loadIcon(packageManager));
holder.appName.setText(info.loadLabel(packageManager));
holder.appIcon.setImageDrawable(info.loadIcon(pm));
holder.appName.setText(info.loadLabel(pm));
holder.appPackage.setText(info.packageName);
// Remove all listeners
@@ -86,10 +89,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
holder.checkBox.setChecked(mHideList.contains(info.packageName));
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked) {
new MagiskHide().add(info.packageName);
Utils.addMagiskHide(shell, info.packageName);
mHideList.add(info.packageName);
} else {
new MagiskHide().rm(info.packageName);
Utils.rmMagiskHide(shell, info.packageName);
mHideList.remove(info.packageName);
}
});
@@ -105,6 +108,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
filter.filter(constraint);
}
public void refresh() {
new LoadApps().exec();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.app_icon) ImageView appIcon;
@@ -122,31 +129,47 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<ApplicationInfo> filteredApps;
if (constraint == null || constraint.length() == 0) {
filteredApps = mOriginalList;
mList = mOriginalList;
} else {
filteredApps = new ArrayList<>();
mList = new ArrayList<>();
String filter = constraint.toString().toLowerCase();
for (ApplicationInfo info : mOriginalList) {
if (Utils.lowercaseContains(info.loadLabel(packageManager), filter)
if (Utils.lowercaseContains(info.loadLabel(pm), filter)
|| Utils.lowercaseContains(info.packageName, filter)) {
filteredApps.add(info);
mList.add(info);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredApps;
results.count = filteredApps.size();
return results;
return null;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
mList = (List<ApplicationInfo>) results.values;
notifyDataSetChanged();
}
}
private class LoadApps extends ParallelTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
mOriginalList = pm.getInstalledApplications(0);
for (Iterator<ApplicationInfo> i = mOriginalList.iterator(); i.hasNext(); ) {
ApplicationInfo info = i.next();
if (ApplicationAdapter.BLACKLIST.contains(info.packageName) || !info.enabled) {
i.remove();
}
}
Collections.sort(mOriginalList, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
mHideList = Utils.listMagiskHide(shell);
return null;
}
@Override
protected void onPostExecute(Void v) {
magiskHideDone.publish(false);
}
}
}

View File

@@ -12,7 +12,6 @@ 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.Shell;
@@ -39,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
Shell rootShell = Shell.getShell(context);
final Module module = mList.get(position);
String version = module.getVersion();
@@ -53,44 +53,31 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
holder.checkBox.setOnCheckedChangeListener(null);
holder.checkBox.setChecked(module.isEnabled());
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> new SerialTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
if (isChecked) {
module.removeDisableFile();
} else {
module.createDisableFile();
}
return null;
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
int snack;
if (isChecked) {
module.removeDisableFile(rootShell);
snack = R.string.disable_file_removed;
} else {
module.createDisableFile(rootShell);
snack = R.string.disable_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
});
@Override
protected void onPostExecute(Void v) {
int snack = isChecked ? R.string.disable_file_removed : R.string.disable_file_created;
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
holder.delete.setOnClickListener(v -> {
boolean removed = module.willBeRemoved();
int snack;
if (removed) {
module.deleteRemoveFile(rootShell);
snack = R.string.remove_file_deleted;
} else {
module.createRemoveFile(rootShell);
snack = R.string.remove_file_created;
}
}.exec());
holder.delete.setOnClickListener(v -> new SerialTask<Void, Void, Void>() {
private final boolean removed = module.willBeRemoved();
@Override
protected Void doInBackground(Void... voids) {
if (removed) {
module.deleteRemoveFile();
} else {
module.createRemoveFile();
}
return null;
}
@Override
protected void onPostExecute(Void v) {
int snack = removed ? R.string.remove_file_deleted : R.string.remove_file_created;
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
updateDeleteButton(holder, module);
}
}.exec());
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
updateDeleteButton(holder, module);
});
if (module.isUpdated()) {
holder.notice.setVisibility(View.VISIBLE);

View File

@@ -1,21 +1,19 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.ExpandableView;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.Policy;
@@ -49,78 +47,72 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
@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.setExpanded(expandList.contains(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);
holder.itemView.setOnClickListener(view -> {
if (holder.isExpanded()) {
holder.collapse();
expandList.remove(policy);
} else {
holder.expand();
expandList.add(policy);
}
});
// Hide for now
holder.moreInfo.setVisibility(View.GONE);
holder.appName.setText(policy.appName);
holder.packageName.setText(policy.packageName);
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if ((isChecked && policy.policy == Policy.DENY) ||
(!isChecked && policy.policy == Policy.ALLOW)) {
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
});
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if ((isChecked && !policy.notification) ||
(!isChecked && policy.notification)) {
policy.notification = isChecked;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
});
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if ((isChecked && !policy.logging) ||
(!isChecked && policy.logging)) {
policy.logging = isChecked;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
});
holder.delete.setOnClickListener(v -> new AlertDialogBuilder((Activity) v.getContext())
.setTitle(R.string.su_revoke_title)
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
.setPositiveButton(R.string.yes, (dialog, which) -> {
policyList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, policyList.size());
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
Snackbar.LENGTH_SHORT).show();
dbHelper.deletePolicy(policy);
})
.setNegativeButton(R.string.no_thanks, null)
.setCancelable(true)
.show());
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
holder.notificationSwitch.setChecked(policy.notification);
holder.loggingSwitch.setChecked(policy.logging);
} catch (PackageManager.NameNotFoundException e) {
policyList.remove(position);
dbHelper.deletePolicy(policy.uid);
onBindViewHolder(holder, position);
}
// Hide for now
holder.moreInfo.setVisibility(View.GONE);
}
@Override
@@ -128,96 +120,31 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
return policyList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
static class ViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.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.expand_layout) ViewGroup expandLayout;
@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;
private Container container = new Container();
ViewHolder(View itemView) {
public 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;
}
});
container.expandLayout = expandLayout;
setupExpandable();
}
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);
@Override
public Container getContainer() {
return container;
}
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,9 +1,11 @@
package com.topjohnwu.magisk.adapters;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -12,99 +14,164 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.MarkDownWindow;
import com.topjohnwu.magisk.asyncs.ProcessRepoZip;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.MarkDownWindow;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.utils.Utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import butterknife.BindView;
import butterknife.ButterKnife;
public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> {
public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> {
private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos;
private Context mContext;
private static final int UPDATES = 0;
private static final int INSTALLED = 1;
private static final int OTHERS = 2;
public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) {
mUpdateRepos = update;
mInstalledRepos = installed;
mOthersRepos = others;
private Cursor repoCursor = null;
private Map<String, Module> moduleMap;
private RepoDatabaseHelper repoDB;
private List<Pair<Integer, List<Repo>>> repoPairs;
public ReposAdapter(RepoDatabaseHelper db, Map<String, Module> map) {
repoDB = db;
moduleMap = map;
repoPairs = new ArrayList<>();
}
@Override
public int getSectionCount() {
return repoPairs.size();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mContext = parent.getContext();
View v = LayoutInflater.from(mContext).inflate(R.layout.list_item_repo, parent, false);
return new ViewHolder(v);
public int getItemCount(int section) {
return repoPairs.get(section).second.size();
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Repo repo = getItem(position);
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
return new SectionHolder(v);
}
@Override
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
return new RepoHolder(v);
}
@Override
public void onBindSectionViewHolder(SectionHolder holder, int section) {
switch (repoPairs.get(section).first) {
case UPDATES:
holder.sectionText.setText(R.string.update_available);
break;
case INSTALLED:
holder.sectionText.setText(R.string.installed);
break;
case OTHERS:
holder.sectionText.setText(R.string.not_installed);
break;
}
}
@Override
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
Repo repo = repoPairs.get(section).second.get(position);
Context context = holder.itemView.getContext();
holder.title.setText(repo.getName());
holder.versionName.setText(repo.getVersion());
String author = repo.getAuthor();
holder.author.setText(TextUtils.isEmpty(author) ? null : mContext.getString(R.string.author, author));
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
holder.description.setText(repo.getDescription());
holder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), mContext));
holder.infoLayout.setOnClickListener(v ->
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
holder.downloadImage.setOnClickListener(v -> {
String filename = repo.getName() + "-" + repo.getVersion() + ".zip";
new AlertDialogBuilder(mContext)
.setTitle(mContext.getString(R.string.repo_install_title, repo.getName()))
.setMessage(mContext.getString(R.string.repo_install_msg, filename))
new AlertDialogBuilder((Activity) context)
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
.setMessage(context.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.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)))
.setPositiveButton(R.string.install, (d, i) ->
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
Utils.getLegalFilename(filename), true).exec()
)
.setNeutralButton(R.string.download, (d, i) ->
new ProcessRepoZip((Activity) context, repo.getZipUrl(),
Utils.getLegalFilename(filename), false).exec())
.setNegativeButton(R.string.no_thanks, null)
.show();
});
}
@Override
public int getItemCount() {
return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size();
public void notifyDBChanged() {
if (repoCursor != null)
repoCursor.close();
repoCursor = repoDB.getRepoCursor();
filter("");
}
private Repo getItem(int position) {
if (position >= mUpdateRepos.size()) {
position -= mUpdateRepos.size();
if (position >= mInstalledRepos.size()) {
position -= mInstalledRepos.size();
return mOthersRepos.get(position);
} else {
return mInstalledRepos.get(position);
public void filter(String s) {
List<Repo> updates = new ArrayList<>();
List<Repo> installed = new ArrayList<>();
List<Repo> others = new ArrayList<>();
repoPairs.clear();
while (repoCursor.moveToNext()) {
Repo repo = new Repo(repoCursor);
if (repo.getName().toLowerCase().contains(s.toLowerCase())
|| repo.getAuthor().toLowerCase().contains(s.toLowerCase())
|| repo.getDescription().toLowerCase().contains(s.toLowerCase())
) {
// Passed the repoFilter
Module module = moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode()) {
// Updates
updates.add(repo);
} else {
installed.add(repo);
}
} else {
others.add(repo);
}
}
} else {
return mUpdateRepos.get(position);
}
repoCursor.moveToFirst();
if (!updates.isEmpty())
repoPairs.add(new Pair<>(UPDATES, updates));
if (!installed.isEmpty())
repoPairs.add(new Pair<>(INSTALLED, installed));
if (!others.isEmpty())
repoPairs.add(new Pair<>(OTHERS, others));
notifyDataSetChanged();
}
static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.section_text) TextView sectionText;
SectionHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
static class RepoHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title) TextView title;
@BindView(R.id.version_name) TextView versionName;
@@ -113,7 +180,7 @@ public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder>
@BindView(R.id.info_layout) LinearLayout infoLayout;
@BindView(R.id.download) ImageView downloadImage;
ViewHolder(View itemView) {
RepoHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}

View File

@@ -0,0 +1,93 @@
package com.topjohnwu.magisk.adapters;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int SECTION_TYPE = Integer.MIN_VALUE;
@Override
final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == SECTION_TYPE)
return onCreateSectionViewHolder(parent);
return onCreateItemViewHolder(parent, viewType);
}
@Override
@SuppressWarnings("unchecked")
final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
PositionInfo info = getPositionInfo(position);
if (info.position == -1)
onBindSectionViewHolder((S) holder, info.section);
else
onBindItemViewHolder((C) holder, info.section, info.position);
}
@Override
final public int getItemCount() {
int size, sec;
size = sec = getSectionCount();
for (int i = 0; i < sec; ++i){
size += getItemCount(i);
}
return size;
}
@Override
final public int getItemViewType(int position) {
PositionInfo info = getPositionInfo(position);
if (info.position == -1)
return SECTION_TYPE;
else
return getItemViewType(info.section, info.position);
}
public int getItemViewType(int section, int position) {
return 0;
}
protected int getSectionPosition(int section) {
return getItemPosition(section, -1);
}
protected int getItemPosition(int section, int position) {
int realPosition = 0;
// Previous sections
for (int i = 0; i < section; ++i) {
realPosition += getItemCount(i) + 1;
}
// Current section
realPosition += position + 1;
return realPosition;
}
private PositionInfo getPositionInfo(int position) {
int section = 0;
while (true) {
if (position == 0)
return new PositionInfo(section, -1);
position -= 1;
if (position < getItemCount(section))
return new PositionInfo(section, position);
position -= getItemCount(section++);
}
}
private static class PositionInfo {
int section;
int position;
PositionInfo(int section, int position) {
this.section = section;
this.position = position;
}
}
public abstract int getSectionCount();
public abstract int getItemCount(int section);
public abstract S onCreateSectionViewHolder(ViewGroup parent);
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindSectionViewHolder(S holder, int section);
public abstract void onBindItemViewHolder(C holder, int section, int position);
}

View File

@@ -1,178 +0,0 @@
package com.topjohnwu.magisk.adapters;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Arrays;
import java.util.Comparator;
public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int SECTION_TYPE = 0;
private boolean mValid = true;
private int mSectionResourceId;
private int mTextResourceId;
private RecyclerView.Adapter mBaseAdapter;
private SparseArray<Section> mSections = new SparseArray<Section>();
public SimpleSectionedRecyclerViewAdapter(int sectionResourceId, int textResourceId,
RecyclerView.Adapter baseAdapter) {
mSectionResourceId = sectionResourceId;
mTextResourceId = textResourceId;
mBaseAdapter = baseAdapter;
mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mValid = mBaseAdapter.getItemCount()>0;
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mValid = mBaseAdapter.getItemCount()>0;
notifyItemRangeRemoved(positionStart, itemCount);
}
});
}
public static class SectionViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public SectionViewHolder(View view, int mTextResourceid) {
super(view);
title = (TextView) view.findViewById(mTextResourceid);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) {
if (typeView == SECTION_TYPE) {
View view = LayoutInflater.from(parent.getContext()).inflate(mSectionResourceId, parent, false);
return new SectionViewHolder(view,mTextResourceId);
}else{
return mBaseAdapter.onCreateViewHolder(parent, typeView -1);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) {
if (isSectionHeaderPosition(position)) {
((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title);
}else{
mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position));
}
}
@Override
public int getItemViewType(int position) {
return isSectionHeaderPosition(position)
? SECTION_TYPE
: mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ;
}
public static class Section {
int firstPosition;
int sectionedPosition;
CharSequence title;
public Section(int firstPosition, CharSequence title) {
this.firstPosition = firstPosition;
this.title = title;
}
public CharSequence getTitle() {
return title;
}
}
public void setSections(Section[] sections) {
mSections.clear();
Arrays.sort(sections, new Comparator<Section>() {
@Override
public int compare(Section o, Section o1) {
return (o.firstPosition == o1.firstPosition)
? 0
: ((o.firstPosition < o1.firstPosition) ? -1 : 1);
}
});
int offset = 0; // offset positions for the headers we're adding
for (Section section : sections) {
section.sectionedPosition = section.firstPosition + offset;
mSections.append(section.sectionedPosition, section);
++offset;
}
notifyDataSetChanged();
}
public int positionToSectionedPosition(int position) {
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).firstPosition > position) {
break;
}
++offset;
}
return position + offset;
}
public int sectionedPositionToPosition(int sectionedPosition) {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION;
}
int offset = 0;
for (int i = 0; i < mSections.size(); i++) {
if (mSections.valueAt(i).sectionedPosition > sectionedPosition) {
break;
}
--offset;
}
return sectionedPosition + offset;
}
public boolean isSectionHeaderPosition(int position) {
return mSections.get(position) != null;
}
@Override
public long getItemId(int position) {
return isSectionHeaderPosition(position)
? Integer.MAX_VALUE - mSections.indexOfKey(position)
: mBaseAdapter.getItemId(sectionedPositionToPosition(position));
}
@Override
public int getItemCount() {
return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0);
}
}

View File

@@ -1,154 +1,134 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.database.Cursor;
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.components.ExpandableView;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.ArrayList;
import java.util.Collections;
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 {
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
private ExpandableAdapter adapter;
private Set<SuLogEntry> expandList = new HashSet<>();
private List<List<Integer>> logEntryList;
private Set<Integer> itemExpanded, sectionExpanded;
private SuDatabaseHelper suDB;
private Cursor suLogCursor = null;
public SuLogAdapter(List<SuLogEntry> list) {
public SuLogAdapter(SuDatabaseHelper db) {
suDB = db;
logEntryList = Collections.emptyList();
sectionExpanded = new HashSet<>();
itemExpanded = new HashSet<>();
}
// Separate the logs with date
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);
@Override
public int getSectionCount() {
return logEntryList.size();
}
@Override
public int getItemCount(int section) {
return sectionExpanded.contains(section) ? logEntryList.get(section).size() : 0;
}
@Override
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
return new SectionHolder(v);
}
@Override
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
return new LogViewHolder(v);
}
@Override
public void onBindSectionViewHolder(SectionHolder holder, int section) {
suLogCursor.moveToPosition(logEntryList.get(section).get(0));
SuLogEntry entry = new SuLogEntry(suLogCursor);
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
holder.itemView.setOnClickListener(v -> {
RotateAnimation rotate;
if (sectionExpanded.contains(section)) {
holder.arrow.setRotation(0);
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
sectionExpanded.remove(section);
notifyItemRangeRemoved(getItemPosition(section, 0), logEntryList.get(section).size());
} else {
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
sectionExpanded.add(section);
notifyItemRangeInserted(getItemPosition(section, 0), logEntryList.get(section).size());
}
group.add(log);
}
// 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);
rotate.setDuration(300);
rotate.setFillAfter(true);
holder.arrow.setAnimation(rotate);
});
holder.date.setText(entry.getDateString());
}
public RecyclerView.Adapter getAdapter() {
return adapter;
@Override
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
int sqlPosition = logEntryList.get(section).get(position);
suLogCursor.moveToPosition(sqlPosition);
SuLogEntry entry = new SuLogEntry(suLogCursor);
holder.setExpanded(itemExpanded.contains(sqlPosition));
holder.itemView.setOnClickListener(view -> {
if (holder.isExpanded()) {
holder.collapse();
itemExpanded.remove(sqlPosition);
} else {
holder.expand();
itemExpanded.add(sqlPosition);
}
});
holder.appName.setText(entry.appName);
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
holder.command.setText(entry.command);
holder.fromPid.setText(String.valueOf(entry.fromPid));
holder.toUid.setText(String.valueOf(entry.toUid));
holder.time.setText(entry.getTimeString());
}
private class 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();
}
public void notifyDBChanged() {
if (suLogCursor != null)
suLogCursor.close();
suLogCursor = suDB.getLogCursor();
logEntryList = suDB.getLogStructure();
itemExpanded.clear();
sectionExpanded.clear();
sectionExpanded.add(0);
notifyDataSetChanged();
}
private class LogGroup extends ExpandableGroup<SuLogEntry> {
LogGroup(String title, List<SuLogEntry> items) {
super(title, items);
}
}
static class LogGroupViewHolder extends GroupViewHolder {
static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.date) TextView date;
@BindView(R.id.arrow) ImageView arrow;
public LogGroupViewHolder(View itemView) {
SectionHolder(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 {
static class LogViewHolder extends RecyclerView.ViewHolder implements ExpandableView {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.action) TextView action;
@@ -156,85 +136,20 @@ public class SuLogAdapter {
@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;
@BindView(R.id.expand_layout) ViewGroup expandLayout;
private ValueAnimator mAnimator;
private boolean mExpanded = false;
private static int expandHeight = 0;
private Container container = new Container();
public LogViewHolder(View itemView) {
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;
}
});
container.expandLayout = expandLayout;
setupExpandable();
}
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;
@Override
public Container getContainer() {
return container;
}
}
}

View File

@@ -1,15 +1,9 @@
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.MainActivity;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.SplashActivity;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
@@ -18,54 +12,65 @@ 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;
public static final int STABLE_CHANNEL = 0;
public static final int BETA_CHANNEL = 1;
private static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
private static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
private boolean showNotification = false;
public CheckUpdates(Context context, boolean b) {
this(context);
showNotification = b;
public CheckUpdates(Context context) {
super(context);
}
public CheckUpdates(Context context) {
magiskManager = Utils.getMagiskManager(context);
public CheckUpdates(Context context, boolean b) {
super(context);
showNotification = b;
}
@Override
protected Void doInBackground(Void... voids) {
String jsonStr = WebService.request(UPDATE_JSON, WebService.GET);
MagiskManager mm = getMagiskManager();
if (mm == null) return null;
String jsonStr;
switch (mm.updateChannel) {
case STABLE_CHANNEL:
jsonStr = WebService.getString(STABLE_URL);
break;
case BETA_CHANNEL:
jsonStr = WebService.getString(BETA_URL);
break;
default:
jsonStr = null;
}
try {
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) {
}
mm.remoteMagiskVersionString = magisk.getString("version");
mm.remoteMagiskVersionCode = magisk.getInt("versionCode");
mm.magiskLink = magisk.getString("link");
mm.releaseNoteLink = magisk.getString("note");
JSONObject manager = json.getJSONObject("app");
mm.remoteManagerVersionString = manager.getString("version");
mm.remoteManagerVersionCode = manager.getInt("versionCode");
mm.managerLink = manager.getString("link");
} catch (JSONException ignored) {}
return null;
}
@Override
protected void onPostExecute(Void v) {
if (magiskManager.magiskVersion < magiskManager.remoteMagiskVersion && showNotification) {
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(MainActivity.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 mm = getMagiskManager();
if (mm == null) return;
if (showNotification && mm.updateNotification) {
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
Utils.showManagerUpdateNotification(mm);
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
Utils.showMagiskUpdateNotification(mm);
}
}
magiskManager.updateCheckDone.trigger();
mm.updateCheckDone.publish();
super.onPostExecute(v);
}
}

View File

@@ -0,0 +1,65 @@
package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import android.os.Build;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.WebService;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class DownloadBusybox extends ParallelTask<Void, Void, Void> {
private static final String BUSYBOX_ARM = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-arm";
private static final String BUSYBOX_X86 = "https://github.com/topjohnwu/ndk-busybox/releases/download/1.27.2/busybox-x86";
private File busybox;
public DownloadBusybox(Context context) {
super(context);
busybox = new File(context.getCacheDir(), "busybox");
}
@Override
protected Void doInBackground(Void... voids) {
Context context = getMagiskManager();
Utils.removeItem(getShell(), context.getApplicationInfo().dataDir + "/busybox");
try {
FileOutputStream out = new FileOutputStream(busybox);
InputStream in = WebService.request(WebService.GET,
Build.SUPPORTED_32_BIT_ABIS[0].contains("x86") ?
BUSYBOX_X86 :
BUSYBOX_ARM,
null
);
if (in == null) throw new IOException();
BufferedInputStream bis = new BufferedInputStream(in);
byte[] buffer = new byte[4096];
int len;
while ((len = bis.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
if (busybox.exists()) {
getShell().su(
"rm -rf " + MagiskManager.BUSYBOXPATH,
"mkdir -p " + MagiskManager.BUSYBOXPATH,
"cp " + busybox + " " + MagiskManager.BUSYBOXPATH,
"chmod -R 755 " + MagiskManager.BUSYBOXPATH,
MagiskManager.BUSYBOXPATH + "/busybox --install -s " + MagiskManager.BUSYBOXPATH
);
busybox.delete();
}
return null;
}
}

View File

@@ -1,18 +1,17 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.widget.Toast;
import android.text.TextUtils;
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.AdaptiveList;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -21,134 +20,98 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class FlashZip extends SerialTask<Void, String, Integer> {
public class FlashZip extends ParallelTask<Void, Void, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
private File mCachedFile;
private AdaptiveList<String> mList;
private String mFilename;
private ProgressDialog progress;
public FlashZip(Activity context, Uri uri) {
public FlashZip(Activity context, Uri uri, AdaptiveList<String> list) {
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);
mList = list;
mCachedFile = new File(context.getCacheDir(), "install.zip");
}
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());
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", true);
List<String> ret = Utils.readFile(getShell(), new File(mCachedFile.getParentFile(), "updater-script").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();
// UI updates must run in the UI thread
mList.setCallback(this::publishProgress);
}
@Override
protected void onProgressUpdate(String... values) {
progress.setMessage(values[0]);
protected void onProgressUpdate(Void... values) {
mList.updateView();
}
@Override
protected Integer doInBackground(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename);
List<String> ret;
MagiskManager mm = getMagiskManager();
if (mm == null) return -1;
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);
mList.add("- Copying zip to temp directory");
} catch (Throwable e) {
mCachedFile.delete();
try (
InputStream in = mm.getContentResolver().openInputStream(mUri);
OutputStream out = new BufferedOutputStream(new FileOutputStream(mCachedFile))
) {
if (in == null) throw new FileNotFoundException();
InputStream buf= new BufferedInputStream(in);
byte buffer[] = new byte[4096];
int length;
while ((length = buf.read(buffer)) > 0)
out.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (IOException e) {
mList.add("! Cannot copy to cache");
throw e;
}
if (!unzipAndCheck()) return 0;
mList.add("- Installing " + Utils.getNameFromUri(mm, mUri));
getShell().su(mList,
"cd " + mCachedFile.getParent(),
"BOOTMODE=true sh update-binary dummy 1 " + mCachedFile +
" && echo 'Success!' || echo 'Failed!'"
);
if (TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
return 1;
} catch (Exception e) {
e.printStackTrace();
}
return cleanup(-1);
return -1;
}
// -1 = error, manual install; 0 = invalid zip; 1 = success
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
progress.dismiss();
MagiskManager mm = getMagiskManager();
if (mm == null) return;
getShell().su_raw(
"rm -rf " + mCachedFile.getParent(),
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);
switch (result) {
case -1:
Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show();
Utils.showUriSnack(activity, mUri);
mList.add(mm.getString(R.string.install_error));
Utils.showUriSnack(getActivity(), mUri);
break;
case 0:
Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show();
mList.add(mm.getString(R.string.invalid_zip));
break;
case 1:
onSuccess();
// Success
new LoadModules(mm).exec();
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();
super.onPostExecute(result);
}
}

View File

@@ -1,28 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class GetBootBlocks extends SerialTask<Void, Void, Void> {
public GetBootBlocks(Activity context) {
super(context);
}
@Override
protected Void doInBackground(Void... params) {
if (Shell.rootAccess()) {
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,74 @@
package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.File;
import java.util.List;
public class HideManager extends ParallelTask<Void, Void, Boolean> {
public HideManager(Context context) {
super(context);
}
@Override
protected void onPreExecute() {
getMagiskManager().toast(R.string.hide_manager_toast, Toast.LENGTH_SHORT);
}
@Override
protected Boolean doInBackground(Void... voids) {
MagiskManager mm = getMagiskManager();
if (mm == null)
return false;
// Generate a new unhide app with random package name
File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk");
unhideAPK.getParentFile().mkdirs();
String pkg = ZipUtils.generateUnhide(mm, unhideAPK);
// Install the application
List<String> ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false");
unhideAPK.delete();
if (!Utils.isValidShellResponse(ret) || !Boolean.parseBoolean(ret.get(0)))
return false;
try {
// Allow the application to gain root by default
PackageManager pm = mm.getPackageManager();
int uid = pm.getApplicationInfo(pkg, 0).uid;
Policy policy = new Policy(uid, pm);
policy.policy = Policy.ALLOW;
policy.notification = false;
policy.logging = false;
mm.suDB.addPolicy(policy);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
// Hide myself!
getShell().su_raw("pm hide " + mm.getPackageName());
return true;
}
@Override
protected void onPostExecute(Boolean b) {
MagiskManager mm = getMagiskManager();
if (mm == null)
return;
if (!b) {
mm.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG);
}
super.onPostExecute(b);
}
}

View File

@@ -0,0 +1,230 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.TarEntry;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import org.kamranzafar.jtar.TarInputStream;
import org.kamranzafar.jtar.TarOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
public class InstallMagisk extends ParallelTask<Void, Void, Boolean> {
private static final int PATCH_MODE = 0;
private static final int DIRECT_MODE = 1;
private Uri mBootImg, mZip;
private AdaptiveList<String> mList;
private String mBootLocation;
private boolean mKeepEnc, mKeepVerity;
private int mode;
private InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity) {
super(context);
mList = list;
mZip = zip;
mKeepEnc = enc;
mKeepVerity = verity;
}
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, Uri boot) {
this(context, list, zip, enc, verity);
mBootImg = boot;
mode = PATCH_MODE;
}
public InstallMagisk(Activity context, AdaptiveList<String> list, Uri zip, boolean enc, boolean verity, String boot) {
this(context, list, zip, enc, verity);
mBootLocation = boot;
mode = DIRECT_MODE;
}
@Override
protected void onPreExecute() {
// UI updates must run in the UI thread
mList.setCallback(this::publishProgress);
}
@Override
protected void onProgressUpdate(Void... values) {
mList.updateView();
}
@Override
protected Boolean doInBackground(Void... voids) {
MagiskManager mm = getMagiskManager();
if (mm == null) return false;
File install = new File(Utils.getEncContext(mm).getFilesDir().getParent(), "install");
getShell().sh_raw("rm -rf " + install);
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
String arch;
if (abis.contains("x86_64")) arch = "x64";
else if (abis.contains("arm64-v8a")) arch = "arm64";
else if (abis.contains("x86")) arch = "x86";
else arch = "arm";
mList.add("- Device platform: " + arch);
try {
// Unzip files
mList.add("- Extracting files");
try (InputStream in = mm.getContentResolver().openInputStream(mZip)) {
if (in == null) throw new FileNotFoundException();
BufferedInputStream buf = new BufferedInputStream(in);
buf.mark(Integer.MAX_VALUE);
ZipUtils.unzip(buf, install, arch + "/", true);
buf.reset();
ZipUtils.unzip(buf, install, "common/", true);
buf.reset();
ZipUtils.unzip(buf, install, "chromeos/", false);
buf.reset();
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (Exception e) {
mList.add("! Cannot unzip zip");
throw e;
}
File boot;
switch (mode) {
case PATCH_MODE:
boot = new File(install, "boot.img");
// Copy boot image to local
try (
InputStream in = mm.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot)
) {
InputStream source;
if (in == null) throw new FileNotFoundException();
if (Utils.getNameFromUri(mm, mBootImg).endsWith(".tar")) {
// Extract boot.img from tar
TarInputStream tar = new TarInputStream(new BufferedInputStream(in));
org.kamranzafar.jtar.TarEntry entry;
while ((entry = tar.getNextEntry()) != null) {
if (entry.getName().equals("boot.img"))
break;
}
source = tar;
} else {
// Direct copy raw image
source = new BufferedInputStream(in);
}
byte buffer[] = new byte[1024];
int length;
while ((length = source.read(buffer)) > 0)
out.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (IOException e) {
mList.add("! Copy failed");
throw e;
}
break;
case DIRECT_MODE:
boot = new File(mBootLocation);
break;
default:
return false;
}
mList.add("- Use boot image: " + boot);
Shell shell;
if (mode == PATCH_MODE && Shell.rootAccess()) {
// Force non-root shell
shell = Shell.getShell("sh");
} else {
shell = getShell();
}
// Patch boot image
shell.sh(mList,
"cd " + install,
"KEEPFORCEENCRYPT=" + mKeepEnc + " KEEPVERITY=" + mKeepVerity + " sh " +
"update-binary indep boot_patch.sh " + boot +
" && echo 'Success!' || echo 'Failed!'"
);
if (!TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
return false;
File patched_boot = new File(install, "new-boot.img");
mList.add("");
switch (mode) {
case PATCH_MODE:
File dest = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/patched_boot" + mm.bootFormat);
dest.getParentFile().mkdirs();
switch (mm.bootFormat) {
case ".img":
getShell().sh_raw("cp -f " + patched_boot + " " + dest);
break;
case ".img.tar":
TarOutputStream tar = new TarOutputStream(new BufferedOutputStream(new FileOutputStream(dest)));
tar.putNextEntry(new TarEntry(patched_boot, "boot.img"));
byte buffer[] = new byte[4096];
BufferedInputStream in = new BufferedInputStream(new FileInputStream(patched_boot));
int len;
while ((len = in.read(buffer)) != -1) {
tar.write(buffer, 0, len);
}
tar.flush();
tar.close();
in.close();
break;
}
mList.add("*********************************");
mList.add(" Patched Boot Image is placed in ");
mList.add(" " + dest + " ");
mList.add("*********************************");
break;
case DIRECT_MODE:
// Direct flash boot image
getShell().su(mList, "flash_boot_image " + patched_boot + " " + mBootLocation);
break;
default:
return false;
}
// Finals
getShell().sh_raw(
"cd " + install,
"mv bin/busybox busybox",
"rm -rf bin *.img update-binary",
"cd /");
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
}
}

View File

@@ -1,38 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.topjohnwu.magisk.adapters.ApplicationAdapter;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class LoadApps extends ParallelTask<Void, Void, Void> {
public LoadApps(Activity context) {
super(context);
}
@Override
protected Void doInBackground(Void... voids) {
PackageManager pm = magiskManager.getPackageManager();
List<ApplicationInfo> list = pm.getInstalledApplications(0);
for (Iterator<ApplicationInfo> i = list.iterator(); i.hasNext(); ) {
ApplicationInfo info = i.next();
if (ApplicationAdapter.BLACKLIST.contains(info.packageName) || !info.enabled)
i.remove();
}
Collections.sort(list, (a, b) -> a.loadLabel(pm).toString().toLowerCase()
.compareTo(b.loadLabel(pm).toString().toLowerCase()));
magiskManager.appList = Collections.unmodifiableList(list);
return null;
}
@Override
protected void onPostExecute(Void v) {
new MagiskHide(activity).list();
}
}

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.BaseModule;
@@ -9,24 +9,25 @@ 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 class LoadModules extends ParallelTask<Void, Void, Void> {
public LoadModules(Activity context) {
public LoadModules(Context context) {
super(context);
}
@Override
protected Void doInBackground(Void... voids) {
MagiskManager mm = getMagiskManager();
if (mm == null) return null;
Logger.dev("LoadModules: Loading modules");
magiskManager.moduleMap = new ValueSortedMap<>();
mm.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) {
for (String path : Utils.getModList(getShell(), MagiskManager.MAGISK_PATH)) {
Logger.dev("LoadModules: Adding modules from " + path);
Module module;
try {
module = new Module(path);
magiskManager.moduleMap.put(module.getId(), module);
Module module = new Module(getShell(), path);
mm.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
}
@@ -36,6 +37,9 @@ public class LoadModules extends SerialTask<Void, Void, Void> {
@Override
protected void onPostExecute(Void v) {
magiskManager.moduleLoadDone.trigger();
MagiskManager mm = getMagiskManager();
if (mm == null) return;
mm.moduleLoadDone.publish();
super.onPostExecute(v);
}
}

View File

@@ -1,129 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.module.BaseModule;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import com.topjohnwu.magisk.utils.WebService;
import org.json.JSONArray;
import org.json.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);
magiskManager.repoMap = new ValueSortedMap<>();
// 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();
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)
magiskManager.repoMap.put(id, repo);
} catch (BaseModule.CacheModException ignored) {}
}
} catch (JSONException e) {
e.printStackTrace();
}
} else {
// Use cached if no internet or no updates
Logger.dev("LoadRepos: No updates, use cached");
magiskManager.repoMap.putAll(cached);
cached.clear();
}
// Update the database
dbHelper.addRepoMap(magiskManager.repoMap);
// The leftover cached are those removed remote, cleanup db
dbHelper.removeRepo(cached);
// Update ETag
prefs.edit().putString(ETAG_KEY, etag).apply();
Logger.dev("LoadRepos: Repo load done");
return null;
}
@Override
protected void onPostExecute(Void v) {
magiskManager.repoLoadDone.trigger();
}
}

View File

@@ -1,58 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
public class MagiskHide extends SerialTask<Object, Void, Void> {
private boolean isList = false;
public MagiskHide() {}
public MagiskHide(Activity context) {
super(context);
}
@Override
protected Void doInBackground(Object... params) {
String command = (String) params[0];
List<String> ret = Shell.su(MagiskManager.MAGISK_HIDE_PATH + command);
if (isList)
magiskManager.magiskHideList = ret;
return null;
}
@Override
protected void onPostExecute(Void v) {
if (isList)
magiskManager.magiskHideDone.trigger();
}
public void add(CharSequence packageName) {
exec("add " + packageName);
}
public void rm(CharSequence packageName) {
exec("rm " + packageName);
}
public void enable() {
exec("enable; setprop persist.magisk.hide 1");
}
public void disable() {
exec("disable; setprop persist.magisk.hide 0");
}
public void list() {
isList = true;
if (magiskManager == null)
return;
exec("list");
}
}

View File

@@ -0,0 +1,47 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.support.v7.app.AlertDialog;
import android.webkit.WebView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.WebService;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
public class MarkDownWindow extends ParallelTask<Void, Void, String> {
private String mTitle, mUrl;
public MarkDownWindow(Activity context, String title, String url) {
super(context);
mTitle = title;
mUrl = url;
}
@Override
protected String doInBackground(Void... voids) {
String md = WebService.getString(mUrl);
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
Node doc = parser.parse(md);
return String.format(
"<link rel='stylesheet' type='text/css' href='file:///android_asset/%s.css'/> %s",
getMagiskManager().isDarkTheme ? "dark" : "light", renderer.render(doc));
}
@Override
protected void onPostExecute(String html) {
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setTitle(mTitle);
WebView wv = new WebView(getActivity());
wv.loadDataWithBaseURL("fake://", html, "text/html", "UTF-8", null);
alert.setView(wv);
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
alert.show();
}
}

View File

@@ -1,24 +1,59 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.lang.ref.WeakReference;
public abstract class ParallelTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Activity activity;
protected MagiskManager magiskManager;
private WeakReference<Activity> weakActivity;
private WeakReference<MagiskManager> weakMagiskManager;
private Runnable callback = null;
public ParallelTask() {}
public ParallelTask(Activity context) {
activity = context;
magiskManager = Utils.getMagiskManager(context);
public ParallelTask(Context context) {
weakMagiskManager = new WeakReference<>(Utils.getMagiskManager(context));
}
@SafeVarargs
public final void exec(Params... params) {
public ParallelTask(Activity context) {
this((Context) context);
weakActivity = new WeakReference<>(context);
}
protected Activity getActivity() {
return weakActivity.get();
}
protected MagiskManager getMagiskManager() {
return weakMagiskManager.get();
}
protected Shell getShell() {
MagiskManager magiskManager = getMagiskManager();
return magiskManager == null ? null : Shell.getShell(magiskManager);
}
@SuppressWarnings("unchecked")
public ParallelTask<Params, Progress, Result> exec(Params... params) {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
return this;
}
@Override
protected void onPostExecute(Result result) {
if (callback != null) callback.run();
}
public ParallelTask<Params, Progress, Result> setCallBack(Runnable next) {
callback = next;
return this;
}
}

View File

@@ -1,93 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.File;
public class ProcessMagiskZip extends ParallelTask<Void, Void, Boolean> {
private Uri mUri;
private ProgressDialog progressDialog;
private String mBoot;
private boolean mEnc, mVerity;
public ProcessMagiskZip(Activity context, Uri uri, String boot, boolean enc, boolean verity) {
super(context);
mUri = uri;
mBoot = boot;
mEnc = enc;
mVerity = verity;
}
@Override
protected void onPreExecute() {
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_process_title),
activity.getString(R.string.zip_unzip_msg));
}
@Override
protected Boolean doInBackground(Void... params) {
if (Shell.rootAccess()) {
try {
// We might not have busybox yet, unzip with Java
// We shall have complete busybox after Magisk installation
File tempdir = new File(magiskManager.getCacheDir(), "magisk");
ZipUtils.unzip(magiskManager.getContentResolver().openInputStream(mUri), tempdir);
// Running in parallel mode, open new shell
Shell.su(true,
"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

@@ -1,93 +1,99 @@
package com.topjohnwu.magisk.asyncs;
import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
import com.topjohnwu.magisk.FlashActivity;
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.WebService;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
private Uri mUri;
private ProgressDialog progressDialog;
private boolean mInstall;
private String mLink;
private File mFile;
public ProcessRepoZip(Activity context, Uri uri, boolean install) {
public ProcessRepoZip(Activity context, String link, String filename, boolean install) {
super(context);
mUri = uri;
mLink = link;
mFile = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", filename);
mFile.getParentFile().mkdirs();
mInstall = install;
}
@Override
protected void onPreExecute() {
Activity activity = getActivity();
progressDialog = ProgressDialog.show(activity,
activity.getString(R.string.zip_process_title),
activity.getString(R.string.zip_process_msg));
activity.getString(R.string.zip_download_title),
activity.getString(R.string.zip_download_msg));
}
@Override
protected void onProgressUpdate(Void... values) {
progressDialog.setTitle(R.string.zip_process_title);
progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg));
}
@Override
protected Boolean doInBackground(Void... params) {
FileInputStream in;
FileOutputStream out;
Activity activity = getActivity();
if (activity == null) return null;
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();
}
// Request zip from Internet
InputStream in = WebService.request(WebService.GET, mLink, null);
if (in == null) return false;
in = new BufferedInputStream(in);
out = new FileOutputStream(temp1);
// Temp files
File temp1 = new File(activity.getCacheDir(), "1.zip");
File temp2 = new File(temp1.getParentFile(), "2.zip");
temp1.getParentFile().mkdir();
// First remove top folder in Github source zip, Uri -> temp1
ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), out);
out.flush();
out.close();
// First remove top folder in Github source zip, Web -> temp1
ZipUtils.removeTopFolder(in, temp1);
out = new FileOutputStream(temp2);
publishProgress();
// Then sign the zip for the first time, temp1 -> temp2
ZipUtils.signZip(activity, temp1, out, false);
out.flush();
out.close();
ZipUtils.signZip(activity, temp1, temp2, false);
// Adjust the zip to prevent unzip issues, temp2 -> temp2
ZipUtils.adjustZip(temp2);
// Adjust the zip to prevent unzip issues, temp2 -> temp1
ZipUtils.zipAdjust(temp2.getPath(), temp1.getPath());
out = new FileOutputStream(temp1);
// Finally, sign the whole zip file again, temp1 -> temp2
ZipUtils.signZip(activity, temp1, temp2, true);
// Finally, sign the whole zip file again, temp2 -> temp1
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)) {
// Write it to the target zip, temp2 -> file
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(mFile));
InputStream source = new BufferedInputStream(new FileInputStream(temp2))
) {
byte[] buffer = new byte[4096];
int length;
if (target == null) throw new FileNotFoundException();
while ((length = in.read(buffer)) > 0)
target.write(buffer, 0, length);
while ((length = source.read(buffer)) > 0)
out.write(buffer, 0, length);
}
// Delete the temp file
// Delete temp files
temp1.delete();
temp2.delete();
@@ -101,15 +107,27 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
@Override
protected void onPostExecute(Boolean result) {
Activity activity = getActivity();
if (activity == null) return;
progressDialog.dismiss();
Uri uri = Uri.fromFile(mFile);
if (result) {
if (Shell.rootAccess() && mInstall)
new FlashZip(activity, mUri).exec();
else
Utils.showUriSnack(activity, mUri);
if (Shell.rootAccess() && mInstall) {
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(uri).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
activity.startActivity(intent);
} else {
Utils.showUriSnack(activity, uri);
}
} else {
Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show();
Utils.getMagiskManager(activity).toast(R.string.process_error, Toast.LENGTH_LONG);
}
super.onPostExecute(result);
}
@Override
public ParallelTask<Void, Void, Boolean> exec(Void... voids) {
Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> super.exec(voids));
return this;
}
}

View File

@@ -0,0 +1,40 @@
package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import android.widget.Toast;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
import java.util.List;
public class RestoreStockBoot extends ParallelTask<Void, Void, Boolean> {
private String mBoot;
public RestoreStockBoot(Context context, String boot) {
super(context);
mBoot = boot;
}
@Override
protected Boolean doInBackground(Void... voids) {
List<String> ret = getShell().su("cat /init.magisk.rc | grep STOCKSHA1");
if (!Utils.isValidShellResponse(ret))
return false;
String stock_boot = "/data/stock_boot_" + ret.get(0).substring(ret.get(0).indexOf('=') + 1) + ".img.gz";
if (!Utils.itemExist(getShell(), stock_boot))
return false;
getShell().su_raw("flash_boot_image " + stock_boot + " " + mBoot);
return true;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
getMagiskManager().toast(R.string.restore_done, Toast.LENGTH_SHORT);
} else {
getMagiskManager().toast(R.string.restore_fail, Toast.LENGTH_LONG);
}
}
}

View File

@@ -1,30 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.os.AsyncTask;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
/**
* This class is only used for running root commands
**/
public abstract class SerialTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Activity activity;
protected MagiskManager magiskManager;
public SerialTask() {}
public SerialTask(Activity context) {
activity = context;
magiskManager = Utils.getMagiskManager(context);
}
@SafeVarargs
public final void exec(Params... params) {
if (!Shell.rootAccess()) return;
executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);
}
}

View File

@@ -0,0 +1,190 @@
package com.topjohnwu.magisk.asyncs;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
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.WebService;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class UpdateRepos extends ParallelTask<Void, Void, Void> {
public static final String ETAG_KEY = "ETag";
private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
private static final String IF_NONE_MATCH = "If-None-Match";
private static final String LINK_KEY = "Link";
private static final int CHECK_ETAG = 0;
private static final int LOAD_NEXT = 1;
private static final int LOAD_PREV = 2;
private List<String> etags;
private List<String> cached;
private RepoDatabaseHelper repoDB;
private SharedPreferences prefs;
public UpdateRepos(Context context) {
super(context);
prefs = getMagiskManager().prefs;
repoDB = getMagiskManager().repoDB;
String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs";
// Legacy data cleanup
File old = new File(prefsPath, "RepoMap.xml");
if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) {
old.delete();
prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply();
repoDB.clearRepo();
}
etags = new ArrayList<>(
Arrays.asList(prefs.getString(ETAG_KEY, "").split(",")));
}
private void loadJSON(String jsonString) throws Exception {
JSONArray jsonArray = new JSONArray(jsonString);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonobject = jsonArray.getJSONObject(i);
String id = jsonobject.getString("description");
String name = jsonobject.getString("name");
String lastUpdate = jsonobject.getString("pushed_at");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
Date updatedDate = format.parse(lastUpdate);
Repo repo = repoDB.getRepo(id);
try {
Boolean updated;
if (repo == null) {
Logger.dev("UpdateRepos: Create new repo " + id);
repo = new Repo(name, updatedDate);
updated = true;
} else {
// Popout from cached
cached.remove(id);
updated = repo.update(updatedDate);
}
if (updated) {
repoDB.addRepo(repo);
}
} catch (BaseModule.CacheModException ignored) {}
}
}
private boolean loadPage(int page, String url, int mode) {
Logger.dev("UpdateRepos: Loading page: " + (page + 1));
Map<String, String> header = new HashMap<>();
if (mode == CHECK_ETAG && page < etags.size() && !TextUtils.isEmpty(etags.get(page))) {
Logger.dev("ETAG: " + etags.get(page));
header.put(IF_NONE_MATCH, etags.get(page));
}
if (url == null) {
url = String.format(Locale.US, REPO_URL, page + 1);
}
String jsonString = WebService.getString(url, header);
if (TextUtils.isEmpty(jsonString)) {
// At least check the pages we know
return page + 1 < etags.size() && loadPage(page + 1, null, CHECK_ETAG);
}
// The getString succeed, parse the new stuffs
try {
loadJSON(jsonString);
} catch (Exception e) {
e.printStackTrace();
return false;
}
// Update the ETAG
String newEtag = header.get(ETAG_KEY);
newEtag = newEtag.substring(newEtag.indexOf('\"'), newEtag.lastIndexOf('\"') + 1);
Logger.dev("New ETAG: " + newEtag);
if (page < etags.size()) {
etags.set(page, newEtag);
} else {
etags.add(newEtag);
}
String links = header.get(LINK_KEY);
if (links != null) {
if (mode == CHECK_ETAG || mode == LOAD_NEXT) {
// Try to check next page URL
url = null;
for (String s : links.split(", ")) {
if (s.contains("next")) {
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
break;
}
}
if (url != null) {
loadPage(page + 1, url, LOAD_NEXT);
}
}
if (mode == CHECK_ETAG || mode == LOAD_PREV) {
// Try to check prev page URL
url = null;
for (String s : links.split(", ")) {
if (s.contains("prev")) {
url = s.substring(s.indexOf("<") + 1, s.indexOf(">; "));
break;
}
}
if (url != null) {
loadPage(page - 1, url, LOAD_PREV);
}
}
}
return true;
}
@Override
protected Void doInBackground(Void... voids) {
Logger.dev("UpdateRepos: Loading repos");
cached = repoDB.getRepoIDList();
if (!loadPage(0, null, CHECK_ETAG)) {
Logger.dev("UpdateRepos: No updates, use DB");
return null;
}
// The leftover cached means they are removed from online repo
repoDB.removeRepo(cached);
// Update ETag
StringBuilder etagBuilder = new StringBuilder();
for (int i = 0; i < etags.size(); ++i) {
if (i != 0) etagBuilder.append(",");
etagBuilder.append(etags.get(i));
}
prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply();
Logger.dev("UpdateRepos: Done");
return null;
}
@Override
protected void onPostExecute(Void v) {
MagiskManager mm = getMagiskManager();
if (mm == null) return;
mm.repoLoadDone.publish();
super.onPostExecute(v);
}
}

View File

@@ -1,13 +1,79 @@
package com.topjohnwu.magisk.components;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
public class Activity extends AppCompatActivity {
@Override
public MagiskManager getApplicationContext() {
return (MagiskManager) getApplication();
private Runnable permissionGrantCallback;
public Activity() {
super();
Configuration configuration = new Configuration();
configuration.setLocale(MagiskManager.locale);
applyOverrideConfiguration(configuration);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (this instanceof Topic.Subscriber) {
((Topic.Subscriber) this).subscribeTopics();
}
}
@Override
protected void onDestroy() {
if (this instanceof Topic.Subscriber) {
((Topic.Subscriber) this).unsubscribeTopics();
}
super.onDestroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (permissionGrantCallback != null) {
permissionGrantCallback.run();
}
}
permissionGrantCallback = null;
}
public void setPermissionGrantCallback(Runnable callback) {
permissionGrantCallback = callback;
}
public MagiskManager getMagiskManager() {
return (MagiskManager) super.getApplicationContext();
}
public Shell getShell() {
return Shell.getShell(this);
}
protected void setFloating() {
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
params.alpha = 1.0f;
params.dimAmount = 0.6f;
params.flags |= 2;
getWindow().setAttributes(params);
setFinishOnTouchOutside(true);
}
}
}

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.components;
import android.content.Context;
import android.app.Activity;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -37,12 +37,12 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
private AlertDialog dialog;
public AlertDialogBuilder(@NonNull Context context) {
public AlertDialogBuilder(@NonNull Activity context) {
super(context);
setup();
}
public AlertDialogBuilder(@NonNull Context context, @StyleRes int themeResId) {
public AlertDialogBuilder(@NonNull Activity context, @StyleRes int themeResId) {
super(context, themeResId);
setup();
}
@@ -93,8 +93,9 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
positive.setText(text);
positiveListener = listener;
positive.setOnClickListener((v) -> {
if (positiveListener != null)
if (positiveListener != null) {
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
}
dialog.dismiss();
});
return this;
@@ -112,8 +113,9 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
negative.setText(text);
negativeListener = listener;
negative.setOnClickListener((v) -> {
if (negativeListener != null)
if (negativeListener != null) {
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
dialog.dismiss();
});
return this;
@@ -131,8 +133,9 @@ public class AlertDialogBuilder extends AlertDialog.Builder {
neutral.setText(text);
neutralListener = listener;
neutral.setOnClickListener((v) -> {
if (neutralListener != null)
if (neutralListener != null) {
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
}
dialog.dismiss();
});
return this;

View File

@@ -0,0 +1,84 @@
package com.topjohnwu.magisk.components;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
public interface ExpandableView {
class Container {
public ViewGroup expandLayout;
ValueAnimator expandAnimator, collapseAnimator;
boolean mExpanded = false;
int expandHeight = 0;
}
// Provide state info
Container getContainer();
default void setupExpandable() {
Container container = getContainer();
container.expandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (container.expandHeight == 0) {
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
container.expandLayout.measure(widthSpec, heightSpec);
container.expandHeight = container.expandLayout.getMeasuredHeight();
}
container.expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
container.expandLayout.setVisibility(View.GONE);
container.expandAnimator = slideAnimator(0, container.expandHeight);
container.collapseAnimator = slideAnimator(container.expandHeight, 0);
return true;
}
});
}
default boolean isExpanded() {
return getContainer().mExpanded;
}
default void setExpanded(boolean expanded) {
Container container = getContainer();
container.mExpanded = expanded;
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
layoutParams.height = expanded ? container.expandHeight : 0;
container.expandLayout.setLayoutParams(layoutParams);
container.expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
default void expand() {
Container container = getContainer();
if (container.mExpanded) return;
container.expandLayout.setVisibility(View.VISIBLE);
container.expandAnimator.start();
container.mExpanded = true;
}
default void collapse() {
Container container = getContainer();
if (!container.mExpanded) return;
container.collapseAnimator.start();
container.mExpanded = false;
}
default ValueAnimator slideAnimator(int start, int end) {
Container container = getContainer();
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(valueAnimator -> {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = container.expandLayout.getLayoutParams();
layoutParams.height = value;
container.expandLayout.setLayoutParams(layoutParams);
});
return animator;
}
}

View File

@@ -1,12 +1,53 @@
package com.topjohnwu.magisk.components;
import android.content.Intent;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
public class Fragment extends android.support.v4.app.Fragment {
private ActivityResultListener activityResultListener;
public MagiskManager getApplication() {
return Utils.getMagiskManager(getActivity());
}
public Shell getShell() {
return Shell.getShell(getActivity());
}
@Override
public void onResume() {
super.onResume();
if (this instanceof Topic.Subscriber) {
((Topic.Subscriber) this).subscribeTopics();
}
}
@Override
public void onPause() {
if (this instanceof Topic.Subscriber) {
((Topic.Subscriber) this).unsubscribeTopics();
}
super.onPause();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (activityResultListener != null)
activityResultListener.onActivityResult(requestCode, resultCode, data);
activityResultListener = null;
}
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
activityResultListener = listener;
super.startActivityForResult(intent, requestCode);
}
public interface ActivityResultListener {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
}

View File

@@ -1,31 +0,0 @@
package com.topjohnwu.magisk.components;
import android.content.Context;
import android.support.v7.app.AlertDialog;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import us.feras.mdv.MarkdownView;
public class MarkDownWindow {
public MarkDownWindow(String title, String url, Context context) {
MagiskManager magiskManager = Utils.getMagiskManager(context);
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(title);
Logger.dev("WebView: URL = " + url);
MarkdownView md = new MarkdownView(context);
md.loadMarkdownFile(url, "file:///android_asset/" +
(magiskManager.isDarkTheme ? "dark" : "light") + ".css");
alert.setView(md);
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
alert.show();
}
}

View File

@@ -3,12 +3,9 @@ package com.topjohnwu.magisk.components;
import android.app.Activity;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import butterknife.ButterKnife;
public class SnackbarMaker {
public static Snackbar make(Activity activity, CharSequence text, int duration) {
@@ -33,9 +30,8 @@ public class SnackbarMaker {
}
private static void setup(Snackbar snack) {
TextView text = ButterKnife.findById(snack.getView(), android.support.design.R.id.snackbar_text);
text.setMaxLines(2);
text.setEllipsize(TextUtils.TruncateAt.START);
TextView text = snack.getView().findViewById(android.support.design.R.id.snackbar_text);
text.setMaxLines(Integer.MAX_VALUE);
}
}

View File

@@ -5,19 +5,27 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.module.Repo;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.ValueSortedMap;
import com.topjohnwu.magisk.utils.Utils;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 1;
private static final int DATABASE_VER = 2;
private static final String TABLE_NAME = "repos";
private static final int MIN_TEMPLATE_VER = 3;
private SQLiteDatabase mDb;
private MagiskManager mm;
public RepoDatabaseHelper(Context context) {
super(context, "repo.db", null, DATABASE_VER);
mDb = getWritableDatabase();
mm = Utils.getMagiskManager(context);
}
@Override
@@ -33,44 +41,49 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
"(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++;
}
// No upgrades yet
}
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();
mDb.delete(TABLE_NAME, null, null);
}
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 void removeRepo(List<String> list) {
for (String id : list) {
Logger.dev("Remove from DB: " + id);
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
}
}
public ValueSortedMap<String, Repo> getRepoMap() {
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());
ret.put(repo.getId(), repo);
public void addRepo(Repo repo) {
mDb.replace(TABLE_NAME, null, repo.getContentValues());
}
public Repo getRepo(String id) {
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
if (c.moveToNext()) {
return new Repo(c);
}
}
return null;
}
public Cursor getRepoCursor() {
return mDb.query(TABLE_NAME, null, "template>=? AND template<=?", new String[] { String.valueOf(MIN_TEMPLATE_VER), String.valueOf(mm.magiskVersionCode) }, null, null, "name COLLATE NOCASE");
}
public List<String> getRepoIDList() {
LinkedList<String> ret = new LinkedList<>();
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
while (c.moveToNext()) {
ret.add(c.getString(c.getColumnIndex("id")));
}
}
db.close();
return ret;
}
}

View File

@@ -1,81 +1,265 @@
package com.topjohnwu.magisk.database;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
public class SuDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 1;
private static final String TABLE_NAME = "policies";
public static final String ROOT_ACCESS = "root_access";
public static final int ROOT_ACCESS_DISABLED = 0;
public static final int ROOT_ACCESS_APPS_ONLY = 1;
public static final int ROOT_ACCESS_ADB_ONLY = 2;
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
public static final String MULTIUSER_MODE = "multiuser_mode";
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
public static final int MULTIUSER_MODE_USER = 2;
public static final String MNT_NS = "mnt_ns";
public static final int NAMESPACE_MODE_GLOBAL = 0;
public static final int NAMESPACE_MODE_REQUESTER = 1;
public static final int NAMESPACE_MODE_ISOLATE = 2;
public static final String DB_NAME = "su.db";
private static final int DATABASE_VER = 3;
private static final String POLICY_TABLE = "policies";
private static final String LOG_TABLE = "logs";
private static final String SETTINGS_TABLE = "settings";
private MagiskManager mm;
private PackageManager pm;
private SQLiteDatabase mDb;
public SuDatabaseHelper(Context context) {
super(context, "su.db", null, DATABASE_VER);
super(context, DB_NAME, null, DATABASE_VER);
mm = Utils.getMagiskManager(context);
pm = context.getPackageManager();
mDb = getWritableDatabase();
cleanup();
}
@Override
public void onCreate(SQLiteDatabase db) {
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))");
onUpgrade(db, 0, DATABASE_VER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Currently new database, no upgrading
if (oldVersion == 0) {
createTables(db);
oldVersion = 2;
}
if (oldVersion == 1) {
// We're dropping column app_name, rename and re-construct table
db.execSQL("ALTER TABLE " + POLICY_TABLE + " RENAME TO " + POLICY_TABLE + "_old");
// Create the new tables
createTables(db);
// Migrate old data to new tables
db.execSQL(
"INSERT INTO " + POLICY_TABLE + " SELECT " +
"uid, package_name, policy, until, logging, notification " +
"FROM " + POLICY_TABLE + "_old");
db.execSQL("DROP TABLE " + POLICY_TABLE + "_old");
File oldDB = mm.getDatabasePath("sulog.db");
if (oldDB.exists()) {
migrateLegacyLogList(oldDB, db);
mm.deleteDatabase("sulog.db");
}
++oldVersion;
}
if (oldVersion == 2) {
db.execSQL("UPDATE " + LOG_TABLE + " SET time=time*1000");
++oldVersion;
}
}
public boolean deletePolicy(int uid) {
SQLiteDatabase db = getWritableDatabase();
db.delete(TABLE_NAME, "uid=?", new String[] { String.valueOf(uid) });
db.close();
return getPolicy(uid) == null;
private void createTables(SQLiteDatabase db) {
// Policies
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + POLICY_TABLE + " " +
"(uid INT, package_name TEXT, policy INT, " +
"until INT, logging INT, notification INT, " +
"PRIMARY KEY(uid))");
// Logs
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + LOG_TABLE + " " +
"(from_uid INT, package_name TEXT, app_name TEXT, from_pid INT, " +
"to_uid INT, action INT, time INT, command TEXT)");
// Settings
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + SETTINGS_TABLE + " " +
"(key TEXT, value INT, PRIMARY KEY(key))");
}
private void cleanup() {
// Clear outdated policies
mDb.delete(POLICY_TABLE, "until > 0 AND until < ?",
new String[] { String.valueOf(System.currentTimeMillis() / 1000) });
// Clear outdated logs
mDb.delete(LOG_TABLE, "time < ?", new String[] { String.valueOf(
System.currentTimeMillis() - mm.suLogTimeout * 86400000) });
}
public void deletePolicy(Policy policy) {
deletePolicy(policy.packageName);
}
public void deletePolicy(String pkg) {
mDb.delete(POLICY_TABLE, "package_name=?", new String[] { pkg });
}
public void deletePolicy(int uid) {
mDb.delete(POLICY_TABLE, "uid=?", new String[]{String.valueOf(uid)});
}
public Policy getPolicy(int uid) {
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);
try (Cursor c = mDb.query(POLICY_TABLE, null, "uid=?", new String[] { String.valueOf(uid) }, null, null, null)) {
if (c.moveToNext()) {
policy = new Policy(c, pm);
}
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(uid);
return null;
}
return policy;
}
public Policy getPolicy(String pkg) {
Policy policy = null;
try (Cursor c = mDb.query(POLICY_TABLE, null, "package_name=?", new String[] { pkg }, null, null, null)) {
if (c.moveToNext()) {
policy = new Policy(c, pm);
}
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(pkg);
return null;
}
db.close();
return policy;
}
public void addPolicy(Policy policy) {
SQLiteDatabase db = getWritableDatabase();
db.replace(TABLE_NAME, null, policy.getContentValues());
db.close();
mDb.replace(POLICY_TABLE, null, policy.getContentValues());
}
public void updatePolicy(Policy policy) {
mDb.update(POLICY_TABLE, policy.getContentValues(), "package_name=?",
new String[] { policy.packageName });
}
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")) {
try (Cursor c = mDb.query(POLICY_TABLE, null, null, null, null, null, null)) {
List<Policy> ret = new ArrayList<>(c.getCount());
while (c.moveToNext()) {
policy = new Policy(c);
// Package is uninstalled
if (pm.getPackagesForUid(policy.uid) == null) {
deletePolicy(policy.uid);
} else {
try {
Policy policy = new Policy(c, pm);
// The application changed UID for some reason, check user config
if (policy.info.uid != policy.uid) {
if (mm.suReauth) {
// Reauth required, remove from DB
deletePolicy(policy);
continue;
} else {
// No reauth, update to use the new UID
policy.uid = policy.info.uid;
updatePolicy(policy);
}
}
ret.add(policy);
} catch (PackageManager.NameNotFoundException e) {
// The app no longer exist, remove from DB
deletePolicy(c.getInt(c.getColumnIndex("uid")));
}
}
Collections.sort(ret);
return ret;
}
db.close();
return ret;
}
public List<List<Integer>> getLogStructure() {
try (Cursor c = mDb.query(LOG_TABLE, new String[] { "time" }, null, null, null, null, "time DESC")) {
List<List<Integer>> ret = new ArrayList<>();
List<Integer> list = null;
String dateString = null, newString;
while (c.moveToNext()) {
Date date = new Date(c.getLong(c.getColumnIndex("time")));
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
if (!TextUtils.equals(dateString, newString)) {
dateString = newString;
list = new ArrayList<>();
ret.add(list);
}
list.add(c.getPosition());
}
return ret;
}
}
public Cursor getLogCursor() {
return getLogCursor(mDb);
}
public Cursor getLogCursor(SQLiteDatabase db) {
return db.query(LOG_TABLE, null, null, null, null, null, "time DESC");
}
private void migrateLegacyLogList(File oldDB, SQLiteDatabase newDB) {
try (SQLiteDatabase oldDb = SQLiteDatabase.openDatabase(oldDB.getPath(), null, SQLiteDatabase.OPEN_READWRITE);
Cursor c = getLogCursor(oldDb)) {
while (c.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(c, values);
newDB.insert(LOG_TABLE, null, values);
}
}
}
public void addLog(SuLogEntry log) {
mDb.insert(LOG_TABLE, null, log.getContentValues());
}
public void clearLogs() {
mDb.delete(LOG_TABLE, null, null);
}
public void setSettings(String key, int value) {
ContentValues data = new ContentValues();
data.put("key", key);
data.put("value", value);
mDb.replace(SETTINGS_TABLE, null, data);
}
public int getSettings(String key, int defaultValue) {
int value = defaultValue;
try (Cursor c = mDb.query(SETTINGS_TABLE, null, "key=?",new String[] { key }, null, null, null)) {
if (c.moveToNext()) {
value = c.getInt(c.getColumnIndex("value"));
}
}
return value;
}
}

View File

@@ -1,72 +0,0 @@
package com.topjohnwu.magisk.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.ArrayList;
import java.util.List;
public class SuLogDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 1;
private static final String TABLE_NAME = "logs";
private MagiskManager magiskManager;
public SuLogDatabaseHelper(Context context) {
super(context, "sulog.db", null, DATABASE_VER);
magiskManager = (MagiskManager) context.getApplicationContext();
}
@Override
public void onCreate(SQLiteDatabase db) {
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)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 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

@@ -11,7 +11,7 @@ import java.util.List;
public abstract class BaseModule implements Comparable<BaseModule> {
private String mId, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = 0;
private int mVersionCode = 0, templateVersion = 0;
protected BaseModule() {}
@@ -22,6 +22,7 @@ public abstract class BaseModule implements Comparable<BaseModule> {
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()])); }
@@ -57,6 +58,10 @@ public abstract class BaseModule implements Comparable<BaseModule> {
case "description":
mDescription = prop[1];
break;
case "template":
try {
templateVersion = Integer.parseInt(prop[1]);
} catch (NumberFormatException ignored) {}
case "cacheModule":
if (Boolean.parseBoolean(prop[1]))
throw new CacheModException(mId);
@@ -99,6 +104,10 @@ public abstract class BaseModule implements Comparable<BaseModule> {
return mVersionCode;
}
public int getTemplateVersion() {
return templateVersion;
}
public static class CacheModException extends Exception {
public CacheModException(String id) {
Logger.error("Cache mods are no longer supported! id: " + id);

View File

@@ -1,6 +1,7 @@
package com.topjohnwu.magisk.module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class Module extends BaseModule {
@@ -8,9 +9,9 @@ public class Module extends BaseModule {
private String mRemoveFile, mDisableFile, mUpdateFile;
private boolean mEnable, mRemove, mUpdated;
public Module(String path) throws CacheModException {
public Module(Shell shell, String path) throws CacheModException {
parseProps(Utils.readFile(path + "/module.prop"));
parseProps(Utils.readFile(shell, path + "/module.prop"));
mRemoveFile = path + "/remove";
mDisableFile = path + "/disable";
@@ -21,34 +22,39 @@ public class Module extends BaseModule {
setId(path.substring(sep + 1));
}
if (getName() == null)
if (getName() == null) {
setName(getId());
}
Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(mDisableFile);
mRemove = Utils.itemExist(mRemoveFile);
mUpdated = Utils.itemExist(mUpdateFile);
mEnable = !Utils.itemExist(shell, mDisableFile);
mRemove = Utils.itemExist(shell, mRemoveFile);
mUpdated = Utils.itemExist(shell, mUpdateFile);
}
public void createDisableFile() {
mEnable = !Utils.createFile(mDisableFile);
public void createDisableFile(Shell shell) {
mEnable = false;
Utils.createFile(shell, mDisableFile);
}
public void removeDisableFile() {
mEnable = Utils.removeItem(mDisableFile);
public void removeDisableFile(Shell shell) {
mEnable = true;
Utils.removeItem(shell, mDisableFile);
}
public boolean isEnabled() {
return mEnable;
}
public void createRemoveFile() {
mRemove = Utils.createFile(mRemoveFile);
public void createRemoveFile(Shell shell) {
mRemove = true;
Utils.createFile(shell, mRemoveFile);
}
public void deleteRemoveFile() {
mRemove = !Utils.removeItem(mRemoveFile);
public void deleteRemoveFile(Shell shell) {
mRemove = false;
Utils.removeItem(shell, mRemoveFile);
}
public boolean willBeRemoved() {

View File

@@ -29,18 +29,19 @@ public class Repo extends BaseModule {
}
public void update() throws CacheModException {
Logger.dev("Repo: Re-fetch prop");
String props = WebService.request(getManifestUrl(), WebService.GET, true);
String props = WebService.getString(getManifestUrl());
String lines[] = props.split("\\n");
parseProps(lines);
Logger.dev("Repo: Fetching prop: " + getId());
}
public void update(Date lastUpdate) throws CacheModException {
Logger.dev("Repo: Local: " + mLastUpdate + " Remote: " + lastUpdate);
public boolean update(Date lastUpdate) throws CacheModException {
if (lastUpdate.after(mLastUpdate)) {
mLastUpdate = lastUpdate;
update();
return true;
}
return false;
}
public ContentValues getContentValues() {
@@ -53,6 +54,7 @@ public class Repo extends BaseModule {
values.put("description", getDescription());
values.put("repo_name", repoName);
values.put("last_update", mLastUpdate.getTime());
values.put("template", getTemplateVersion());
return values;
}
@@ -60,10 +62,6 @@ public class Repo extends BaseModule {
return String.format(ZIP_URL, repoName);
}
public String getLogUrl() {
return String.format(FILE_URL, repoName, "changelog.txt");
}
public String getManifestUrl() {
return String.format(FILE_URL, repoName, "module.prop");
}

View File

@@ -3,14 +3,26 @@ package com.topjohnwu.magisk.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import com.topjohnwu.magisk.services.BootupIntentService;
import com.topjohnwu.magisk.services.OnBootIntentService;
public class BootReceiver extends BroadcastReceiver {
private void startIntentService(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(new Intent(context, OnBootIntentService.class));
} else {
context.startService(new Intent(context, OnBootIntentService.class));
}
}
@Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, BootupIntentService.class));
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
// There is currently no need to start an IntentService onBoot
// startIntentService(context);
}
}
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.receivers;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -13,7 +12,6 @@ import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
public abstract class DownloadReceiver extends BroadcastReceiver {
public Activity activity;
public String mFilename;
long downloadID;
@@ -21,7 +19,6 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
activity = (Activity) context;
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
@@ -37,7 +34,7 @@ public abstract class DownloadReceiver extends BroadcastReceiver {
onDownloadDone(uri);
break;
default:
Toast.makeText(context, R.string.download_file_error, Toast.LENGTH_LONG).show();
Utils.getMagiskManager(context).toast(R.string.download_file_error, Toast.LENGTH_LONG);
break;
}
context.unregisterReceiver(this);

View File

@@ -0,0 +1,42 @@
package com.topjohnwu.magisk.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
public class ManagerUpdate extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Utils.dlAndReceive(
context,
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri content = FileProvider.getUriForFile(context,
"com.topjohnwu.magisk.provider", new File(uri.getPath()));
install.setData(content);
context.startActivity(install);
} else {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
}
},
intent.getStringExtra(MagiskManager.INTENT_LINK),
Utils.getLegalFilename("MagiskManager-v" +
intent.getStringExtra(MagiskManager.INTENT_VERSION) + ".apk"));
}
}

View File

@@ -0,0 +1,40 @@
package com.topjohnwu.magisk.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.utils.Utils;
public class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
MagiskManager magiskManager = Utils.getMagiskManager(context);
String pkg = intent.getData().getEncodedSchemeSpecificPart();
Policy policy = magiskManager.suDB.getPolicy(pkg);
if (policy == null)
return;
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_REPLACED:
// This will only work pre-O
if (magiskManager.suReauth) {
magiskManager.suDB.deletePolicy(policy);
} else {
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
// Update the UID if available
if (uid > 0) {
policy.uid = uid % 100000;
}
magiskManager.suDB.updatePolicy(policy);
}
break;
case Intent.ACTION_PACKAGE_FULLY_REMOVED:
magiskManager.suDB.deletePolicy(policy);
break;
}
}
}

View File

@@ -1,30 +0,0 @@
package com.topjohnwu.magisk.services;
import android.app.IntentService;
import android.content.Intent;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class BootupIntentService extends IntentService {
public BootupIntentService() {
super("BootupIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
MagiskManager magiskManager = Utils.getMagiskManager(this);
magiskManager.initSuAccess();
magiskManager.updateMagiskInfo();
if (magiskManager.prefs.getBoolean("magiskhide", false) &&
!magiskManager.disabled && !magiskManager.magiskHideStarted && magiskManager.magiskVersion > 11) {
magiskManager.toast(R.string.start_magiskhide, Toast.LENGTH_LONG);
Shell.su(true, MagiskManager.MAGISK_HIDE_PATH + "enable",
"setprop persist.magisk.hide 1");
}
}
}

View File

@@ -0,0 +1,34 @@
package com.topjohnwu.magisk.services;
import android.app.IntentService;
import android.content.Intent;
import android.os.Build;
import android.support.v7.app.NotificationCompat;
import com.topjohnwu.magisk.R;
public class OnBootIntentService extends IntentService {
private static final int ONBOOT_NOTIFICATION_ID = 3;
public OnBootIntentService() {
super("OnBootIntentService");
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_magisk)
.setContentTitle("onBoot")
.setContentText("Running onBoot operations...");
startForeground(ONBOOT_NOTIFICATION_ID, builder.build());
}
}
@Override
protected void onHandleIntent(Intent intent) {
// Currently nothing to do
}
}

View File

@@ -4,23 +4,15 @@ import android.app.job.JobParameters;
import android.app.job.JobService;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.utils.Utils;
public class UpdateCheckService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
new CheckUpdates(this, true){
@Override
protected Void doInBackground(Void... voids) {
magiskManager.updateMagiskInfo();
return super.doInBackground(voids);
}
@Override
protected void onPostExecute(Void v) {
jobFinished(params, false);
super.onPostExecute(v);
}
}.exec();
Utils.getMagiskManager(this).getMagiskInfo();
new CheckUpdates(this, true)
.setCallBack(() -> jobFinished(params, false)).exec();
return true;
}

View File

@@ -1,51 +1,57 @@
package com.topjohnwu.magisk.superuser;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.support.annotation.NonNull;
public class Policy {
public class Policy implements Comparable<Policy>{
public static final int INTERACTIVE = 0;
public static final int DENY = 1;
public static final int ALLOW = 2;
public int uid, policy;
public int uid, policy = INTERACTIVE;
public long until;
public boolean logging = true, notification = true;
public String packageName, appName;
public PackageInfo info;
public ApplicationInfo 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();
info = pm.getApplicationInfo(packageName, 0);
appName = info.loadLabel(pm).toString();
} else throw new PackageManager.NameNotFoundException();
}
public Policy(Cursor c) {
public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
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;
info = pm.getApplicationInfo(packageName, 0);
appName = info.loadLabel(pm).toString();
}
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;
}
@Override
public int compareTo(@NonNull Policy policy) {
return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
}
}

View File

@@ -17,7 +17,6 @@ public class RequestActivity extends Activity {
return;
}
getApplicationContext().initSuConfigs();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClass(this, SuRequestActivity.class);
startActivity(intent);
finish();

View File

@@ -2,15 +2,14 @@ package com.topjohnwu.magisk.superuser;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.magisk.MagiskManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class SuLogEntry implements Parcelable {
public class SuLogEntry {
public int fromUid, toUid, fromPid;
public String packageName, appName, command;
@@ -31,7 +30,7 @@ public class SuLogEntry implements Parcelable {
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);
date = new Date(c.getLong(c.getColumnIndex("time")));
}
public ContentValues getContentValues() {
@@ -43,56 +42,15 @@ public class SuLogEntry implements Parcelable {
values.put("command", command);
values.put("to_uid", toUid);
values.put("action", action ? 1 : 0);
values.put("time", date.getTime() / 1000);
values.put("time", date.getTime());
return values;
}
public String getDateString() {
return DateFormat.getDateInstance(DateFormat.MEDIUM).format(date);
return DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).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());
return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date);
}
}

View File

@@ -3,25 +3,27 @@ package com.topjohnwu.magisk.superuser;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Process;
import android.widget.Toast;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.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;
public static final int NO_NOTIFICATION = 0;
public static final int TOAST = 1;
private static final int NOTIFY_NORMAL_LOG = 0;
private static final int NOTIFY_USER_TOASTS = 1;
private static final int NOTIFY_USER_TO_OWNER = 2;
@Override
public void onReceive(Context context, Intent intent) {
int fromUid, toUid, pid;
int fromUid, toUid, pid, mode;
String command, action;
Policy policy;
@@ -29,6 +31,14 @@ public class SuReceiver extends BroadcastReceiver {
if (intent == null) return;
mode = intent.getIntExtra("mode", -1);
if (mode < 0) return;
if (mode == NOTIFY_USER_TO_OWNER) {
magiskManager.toast(R.string.multiuser_hint_owner_request, Toast.LENGTH_LONG);
return;
}
fromUid = intent.getIntExtra("from.uid", -1);
if (fromUid < 0) return;
if (fromUid == Process.myUid()) return; // Don't show anything if it's Magisk Manager
@@ -36,16 +46,16 @@ public class SuReceiver extends BroadcastReceiver {
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;
policy = magiskManager.suDB.getPolicy(fromUid);
if (policy == null) {
try {
policy = new Policy(fromUid, context.getPackageManager());
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return;
}
}
magiskManager.initSuConfigs();
SuLogEntry log = new SuLogEntry(policy);
String message;
@@ -62,10 +72,11 @@ public class SuReceiver extends BroadcastReceiver {
return;
}
if (policy.notification && magiskManager.suNotificationType == TOAST)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
if (policy.notification && magiskManager.suNotificationType == TOAST) {
magiskManager.toast(message, Toast.LENGTH_SHORT);
}
if (policy.logging) {
if (mode == NOTIFY_NORMAL_LOG && policy.logging) {
toUid = intent.getIntExtra("to.uid", -1);
if (toUid < 0) return;
pid = intent.getIntExtra("pid", -1);
@@ -76,8 +87,7 @@ public class SuReceiver extends BroadcastReceiver {
log.fromPid = pid;
log.command = command;
log.date = new Date();
SuLogDatabaseHelper logDbHelper = new SuLogDatabaseHelper(context);
logDbHelper.addLog(log);
magiskManager.suDB.addLog(log);
}
}
}

View File

@@ -21,8 +21,6 @@ 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;
@@ -30,16 +28,13 @@ import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SuRequestActivity extends Activity implements CallbackEvent.Listener<Policy> {
public class SuRequestActivity extends Activity {
public static final int PROMPT = 0;
public static final int AUTO_DENY = 1;
public static final int AUTO_ALLOW = 2;
private static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
private static final int 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;
@@ -54,11 +49,9 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
private PackageManager pm;
private MagiskManager magiskManager;
private int uid;
private boolean hasTimeout;
private Policy policy;
private CountDownTimer timer;
private CallbackEvent.Listener<Policy> self;
private CallbackEvent<Policy> event = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -66,18 +59,16 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
pm = getPackageManager();
magiskManager = getApplicationContext();
magiskManager = getMagiskManager();
Intent intent = getIntent();
socketPath = intent.getStringExtra("socket");
self = this;
hasTimeout = intent.getBooleanExtra("timeout", true);
new FileObserver(socketPath) {
@Override
public void onEvent(int fileEvent, String path) {
if (fileEvent == FileObserver.DELETE_SELF) {
if (event != null)
event.trigger();
finish();
}
}
@@ -86,8 +77,13 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
new SocketManager(this).exec();
}
void showRequest() {
private boolean cancelTimeout() {
timer.cancel();
deny_btn.setText(getString(R.string.deny));
return false;
}
private void showRequest() {
switch (magiskManager.suResponseType) {
case AUTO_DENY:
handleAction(Policy.DENY, 0);
@@ -99,10 +95,16 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
default:
}
// If not interactive, response directly
if (policy.policy != Policy.INTERACTIVE) {
handleAction();
return;
}
setContentView(R.layout.activity_request);
ButterKnife.bind(this);
appIcon.setImageDrawable(policy.info.applicationInfo.loadIcon(pm));
appIcon.setImageDrawable(policy.info.loadIcon(pm));
appNameView.setText(policy.appName);
packageNameView.setText(policy.packageName);
@@ -119,39 +121,49 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
@Override
public void onFinish() {
deny_btn.setText(getString(R.string.deny_with_str, "(0)"));
event.trigger();
handleAction(Policy.DENY);
}
};
grant_btn.setOnClickListener(v -> handleAction(Policy.ALLOW));
deny_btn.setOnClickListener(v -> handleAction(Policy.DENY));
suPopup.setOnClickListener((v) -> {
grant_btn.setOnClickListener(v -> {
handleAction(Policy.ALLOW);
timer.cancel();
deny_btn.setText(getString(R.string.deny));
});
timeout.setOnTouchListener((v, event) -> {
deny_btn.setOnClickListener(v -> {
handleAction(Policy.DENY);
timer.cancel();
deny_btn.setText(getString(R.string.deny));
return false;
});
suPopup.setOnClickListener(v -> cancelTimeout());
timeout.setOnTouchListener((v, event) -> cancelTimeout());
timer.start();
if (hasTimeout) {
timer.start();
} else {
cancelTimeout();
}
}
@Override
public void onBackPressed() {
event.trigger();
if (policy != null) {
handleAction(Policy.DENY);
}
finish();
}
@Override
public void onTrigger(CallbackEvent<Policy> event) {
Policy policy = event.getResult();
String response = "socket:DENY";
if (policy != null &&policy.policy == Policy.ALLOW )
void handleAction() {
String response;
if (policy.policy == Policy.ALLOW) {
response = "socket:ALLOW";
} else {
response = "socket:DENY";
}
try {
socket.getOutputStream().write((response).getBytes());
} catch (Exception ignored) {}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
finish();
}
@@ -161,60 +173,53 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
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);
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
magiskManager.suDB.addPolicy(policy);
}
handleAction();
}
private class SocketManager extends ParallelTask<Void, Void, Boolean> {
public SocketManager(Activity context) {
SocketManager(Activity context) {
super(context);
}
@Override
protected Boolean doInBackground(Void... params) {
try{
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++) {
while (true) {
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)
if (payload.getAsInteger("uid") == null) {
return false;
uid = payload.getAsInteger("uid");
}
} catch (IOException e) {
int uid = payload.getAsInteger("uid");
policy = magiskManager.suDB.getPolicy(uid);
if (policy == null) {
policy = new Policy(uid, pm);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
@@ -223,35 +228,10 @@ public class SuRequestActivity extends Activity implements CallbackEvent.Listene
@Override
protected void onPostExecute(Boolean result) {
if (!result) {
if (result) {
showRequest();
} else {
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

@@ -0,0 +1,36 @@
package com.topjohnwu.magisk.utils;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
public class AdaptiveList<E> extends ArrayList<E> {
private Runnable callback;
private RecyclerView mView;
public AdaptiveList(RecyclerView v) {
mView = v;
}
public void updateView() {
mView.getAdapter().notifyDataSetChanged();
mView.scrollToPosition(mView.getAdapter().getItemCount() - 1);
}
public void setCallback(Runnable cb) {
callback = cb;
}
public boolean add(E e) {
boolean ret = super.add(e);
if (ret) {
if (callback == null) {
updateView();
} else {
callback.run();
}
}
return ret;
}
}

View File

@@ -1,18 +0,0 @@
package com.topjohnwu.magisk.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayInOutStream extends ByteArrayOutputStream {
public ByteArrayInputStream getInputStream() {
ByteArrayInputStream in = new ByteArrayInputStream(buf, 0, count);
count = 0;
buf = new byte[32];
return in;
}
public void setBuffer(byte[] buffer) {
buf = buffer;
count = buffer.length;
}
}

View File

@@ -1,47 +0,0 @@
package com.topjohnwu.magisk.utils;
import java.util.HashSet;
import java.util.Set;
public class CallbackEvent<Result> {
public boolean isTriggered = false;
private Result result;
private Set<Listener<Result>> listeners;
public void register(Listener<Result> l) {
if (listeners == null)
listeners = new HashSet<>();
listeners.add(l);
}
public void unRegister() {
listeners = null;
}
public void unRegister(Listener<Result> l) {
if (listeners != null)
listeners.remove(l);
}
public void trigger() {
trigger(null);
}
public void trigger(Result r) {
result = r;
isTriggered = true;
if (listeners != null) {
for (Listener<Result> listener : listeners)
listener.onTrigger(this);
}
}
public Result getResult() {
return result;
}
public interface Listener<R> {
void onTrigger(CallbackEvent<R> event);
}
}

View File

@@ -1,114 +0,0 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.util.AttributeSet;
import android.view.View;
import com.github.clans.fab.FloatingActionMenu;
import java.util.List;
/**
* Created by Matteo on 08/08/2015.
*
* Floating Action Menu Behavior for Clans.FloatingActionButton
* https://github.com/Clans/FloatingActionButton/
*
* Use this behavior as your app:layout_behavior attribute in your Floating Action Menu to use the
* FabMenu in a Coordinator Layout.
*
* Remember to use the correct namespace for the fab:
* xmlns:app="http://schemas.android.com/apk/res-auto"
*/
public class FABBehavior extends CoordinatorLayout.Behavior<View> {
private float mTranslationY;
public FABBehavior(Context context, AttributeSet attrs) {
super();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateTranslation(parent, child);
}
return false;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
revertTranslation(child);
}
}
private void updateTranslation(CoordinatorLayout parent, View child) {
float translationY = getTranslationY(parent, child);
if (translationY != mTranslationY) {
ViewPropertyAnimatorCompat anim = ViewCompat.animate(child);
anim.cancel();
anim.translationY(translationY).setDuration(100);
mTranslationY = translationY;
}
}
private void revertTranslation(View child) {
if (mTranslationY != 0) {
ViewPropertyAnimatorCompat anim = ViewCompat.animate(child);
anim.cancel();
anim.translationY(0).setDuration(100);
mTranslationY = 0;
}
}
private float getTranslationY(CoordinatorLayout parent, View child) {
float minOffset = 0.0F;
List<View> dependencies = parent.getDependencies(child);
int i = 0;
for (int z = dependencies.size(); i < z; ++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());
}
}
return minOffset;
}
/**
* onStartNestedScroll and onNestedScroll will hide/show the FabMenu when a scroll is detected.
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed);
FloatingActionMenu fabMenu = (FloatingActionMenu) child;
if (dyConsumed > 0 && !fabMenu.isMenuButtonHidden()) {
fabMenu.hideMenuButton(true);
} else if (dyConsumed < 0 && fabMenu.isMenuButtonHidden()) {
fabMenu.showMenuButton(true);
}
}
}

View File

@@ -4,37 +4,46 @@ import android.util.Log;
import com.topjohnwu.magisk.MagiskManager;
import java.util.Locale;
public class Logger {
public static final String TAG = "Magisk";
public static final String MAIN_TAG = "Magisk";
public static final String DEBUG_TAG = "MagiskManager";
public static void debug(String msg) {
Log.d(TAG, "DEBUG: " + msg);
public static void debug(String line) {
Log.d(DEBUG_TAG, "DEBUG: " + line);
}
public static void error(String msg) {
Log.e(TAG, "ERROR: " + msg);
public static void debug(String fmt, Object... args) {
debug(String.format(Locale.US, fmt, args));
}
public static void dev(String msg, Object... args) {
public static void error(String line) {
Log.e(MAIN_TAG, "MANAGERERROR: " + line);
}
public static void error(String fmt, Object... args) {
error(String.format(Locale.US, fmt, args));
}
public static void dev(String line) {
if (MagiskManager.devLogging) {
if (args.length == 1 && args[0] instanceof Throwable) {
Log.d(TAG, "DEV: " + msg, (Throwable) args[0]);
} else {
Log.d(TAG, "DEV: " + String.format(msg, args));
}
Log.d(DEBUG_TAG, line);
}
}
public static void dev(String msg) {
if (MagiskManager.devLogging) {
Log.d(TAG, "DEV: " + msg);
}
public static void dev(String fmt, Object... args) {
dev(String.format(Locale.US, fmt, args));
}
public static void shell(boolean root, String msg) {
public static void shell(String line) {
if (MagiskManager.shellLogging) {
Log.d(root ? "SU" : "SH", msg);
Log.d(DEBUG_TAG, "SHELL: " + line);
}
}
public static void shell(String fmt, Object... args) {
shell(String.format(Locale.US, fmt, args));
}
}

View File

@@ -1,15 +1,16 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.topjohnwu.magisk.R;
import org.json.JSONException;
import org.json.JSONObject;
@@ -19,40 +20,55 @@ import java.security.SecureRandom;
public abstract class SafetyNetHelper
implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
private GoogleApiClient mGoogleApiClient;
private static boolean isRunning = false;
public SafetyNetHelper(Context context) {
mGoogleApiClient = new GoogleApiClient.Builder(context)
private GoogleApiClient mGoogleApiClient;
private Result ret;
protected FragmentActivity mActivity;
public SafetyNetHelper(FragmentActivity activity) {
ret = new Result();
mActivity = activity;
}
// Entry point to start test
public void requestTest() {
if (isRunning)
return;
// Connect Google Service
mGoogleApiClient = new GoogleApiClient.Builder(mActivity)
.enableAutoManage(mActivity, this)
.addApi(SafetyNet.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
isRunning = true;
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
Logger.dev("SN: Google API fail");
handleResults(-2);
}
@Override
public void onConnected(@Nullable Bundle bundle) {
Logger.dev("SN: Google API Connected");
safetyNetCheck();
ret.errmsg = result.getErrorMessage();
handleResults(ret);
}
@Override
public void onConnectionSuspended(int i) {
Logger.dev("SN: Google API Suspended");
handleResults(-3);
switch (i) {
case CAUSE_NETWORK_LOST:
ret.errmsg = mActivity.getString(R.string.safetyNet_network_loss);
break;
case CAUSE_SERVICE_DISCONNECTED:
ret.errmsg = mActivity.getString(R.string.safetyNet_service_disconnected);
break;
}
handleResults(ret);
}
public void requestTest() {
// Connect Google Service
mGoogleApiClient.connect();
}
private void safetyNetCheck() {
@Override
public void onConnected(@Nullable Bundle bundle) {
Logger.dev("SN: Google API Connected");
// Create nonce
byte[] nonce = new byte[24];
new SecureRandom().nextBytes(nonce);
@@ -68,16 +84,31 @@ public abstract class SafetyNetHelper
Logger.dev("SN: Response: " + json);
try {
JSONObject decoded = new JSONObject(json);
handleResults(decoded.getBoolean("ctsProfileMatch") ? 1 : 0);
} catch (JSONException ignored) {}
ret.ctsProfile = decoded.getBoolean("ctsProfileMatch");
ret.basicIntegrity = decoded.getBoolean("basicIntegrity");
ret.failed = false;
} catch (JSONException e) {
ret.errmsg = mActivity.getString(R.string.safetyNet_res_invalid);
}
} else {
Logger.dev("SN: No response");
handleResults(-1);
ret.errmsg = mActivity.getString(R.string.safetyNet_no_response);
}
// Disconnect
mGoogleApiClient.stopAutoManage(mActivity);
mGoogleApiClient.disconnect();
isRunning = false;
handleResults(ret);
});
}
public abstract void handleResults(int i);
// Callback function to save the results
public abstract void handleResults(Result result);
public static class Result {
public boolean failed = true;
public String errmsg;
public boolean ctsProfile = false;
public boolean basicIntegrity = false;
}
}

View File

@@ -1,9 +1,18 @@
package com.topjohnwu.magisk.utils;
import android.content.Context;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collection;
import java.util.List;
/**
@@ -15,203 +24,202 @@ 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 = Collections.synchronizedList(new ArrayList<String>());
private final Process shellProcess;
private final DataOutputStream STDIN;
private final DataInputStream STDOUT;
public static void init() {
private boolean isValid;
isInit = true;
private void testRootShell(DataOutputStream in, DataInputStream out) throws IOException {
in.write(("id\n").getBytes("UTF-8"));
in.flush();
String s = new BufferedReader(new InputStreamReader(out)).readLine();
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
in.close();
out.close();
throw new IOException();
}
}
private Shell() {
rootStatus = 1;
Process process = null;
DataOutputStream in = null;
DataInputStream out = null;
try {
rootShell = Runtime.getRuntime().exec("su");
rootStatus = 1;
} catch (IOException err) {
// No root
rootStatus = 0;
return;
// Try getting global namespace
process = Runtime.getRuntime().exec("su --mount-master");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
testRootShell(in, out);
} catch (IOException e) {
// Feature not implemented, normal root shell
try {
process = Runtime.getRuntime().exec("su");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
testRootShell(in, out);
} catch (IOException e1) {
rootStatus = 0;
}
}
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
rootSTDOUT.start();
// Setup umask and PATH
su("umask 022");
su("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || echo /data/busybox`:$PATH");
List<String> ret = su("echo -BOC-", "id");
if (ret == null) {
// Something wrong with root, not allowed?
rootStatus = -1;
return;
}
for (String line : ret) {
if (line.contains("uid=")) {
// id command is working, let's see if we are actually root
rootStatus = line.contains("uid=0") ? rootStatus : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
if (!rootAccess()) {
// Try to gain non-root sh
try {
process = Runtime.getRuntime().exec("sh");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
}
isValid = true;
shellProcess = process;
STDIN = in;
STDOUT = out;
sh_raw("umask 022");
}
private Shell(String command) {
Process process;
DataOutputStream in;
DataInputStream out;
try {
process = Runtime.getRuntime().exec(command);
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
isValid = true;
shellProcess = process;
STDIN = in;
STDOUT = out;
}
public static Shell getShell() {
return new Shell();
}
public static Shell getShell(String command) {
return new Shell(command);
}
public static Shell getShell(Context context) {
MagiskManager magiskManager = Utils.getMagiskManager(context);
if (magiskManager.shell == null || !magiskManager.shell.isValid) {
// Get new shell if needed
magiskManager.shell = getShell();
}
return magiskManager.shell;
}
public static boolean rootAccess() {
return isInit && rootStatus > 0;
return rootStatus > 0;
}
public static List<String> sh(String... commands) {
List<String> res = Collections.synchronizedList(new ArrayList<String>());
public void loadInputStream(InputStream in) {
try {
Process process = Runtime.getRuntime().exec("sh");
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
STDOUT.start();
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
Logger.shell(false, write);
}
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
throw e;
}
int read;
byte[] bytes = new byte[4096];
while ((read = in.read(bytes)) != -1) {
STDIN.write(bytes, 0, read);
}
process.waitFor();
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join();
process.destroy();
} catch (IOException | InterruptedException e) {
// shell probably not found
res = null;
STDIN.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public List<String> sh(String... commands) {
List<String> res = new ArrayList<>();
if (!isValid) return res;
sh(res, commands);
return res;
}
// Run with the same shell by default
public static List<String> su(String... commands) {
return su(false, commands);
public void sh_raw(String... commands) {
sh_raw(false, commands);
}
public static List<String> su(boolean newShell, String... commands) {
List<String> res;
Process process;
DataOutputStream STDIN;
StreamGobbler STDOUT;
// Create the default shell if not init
if (!newShell && !isInit)
init();
if (!newShell && !rootAccess())
return null;
if (newShell) {
res = Collections.synchronizedList(new ArrayList<String>());
public void sh_raw(boolean stdout, String... commands) {
if (!isValid) return;
synchronized (shellProcess) {
try {
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;
for (String command : commands) {
Logger.shell(command);
STDIN.write((command + (stdout ? "\n" : " >/dev/null\n")).getBytes("UTF-8"));
STDIN.flush();
}
} catch (IOException e) {
e.printStackTrace();
shellProcess.destroy();
isValid = false;
}
STDOUT.start();
} else {
process = rootShell;
STDIN = rootSTDIN;
STDOUT = rootSTDOUT;
res = rootOutList;
res.clear();
}
}
public void sh(Collection<String> output, String... commands) {
if (!isValid) return;
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
Logger.shell(true, write);
}
if (newShell) {
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
process.waitFor();
shellProcess.exitValue();
isValid = false;
return; // The process is dead, return
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
synchronized (shellProcess) {
StreamGobbler out = new StreamGobbler(STDOUT, output);
out.start();
sh_raw(true, commands);
sh_raw(true, "echo \'-shell-done-\'");
try { out.join(); } catch (InterruptedException ignored) {}
}
}
try {
STDIN.close();
} catch (IOException ignore) {
// might be closed already
}
public List<String> su(String... commands) {
if (!rootAccess()) return sh();
return sh(commands);
}
STDOUT.join();
process.destroy();
} else {
STDIN.write(("echo\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
STDIN.flush();
while (true) {
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
int end = res.size() - 1;
if (end > 0) {
if (res.get(end).equals("-root-done-")) {
res.remove(end);
if (res.get(end -1).isEmpty()) {
res.remove(end -1);
}
break;
}
}
try { STDOUT.join(100); } catch (InterruptedException err) {
rootStatus = -1;
return null;
}
}
}
}
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
return null;
}
} catch(InterruptedException e) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
public void su_raw(String... commands) {
if (!rootAccess()) return;
sh_raw(commands);
}
public void su(Collection<String> output, String... commands) {
if (!rootAccess()) return;
sh(output, commands);
}
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
@Override
public abstract boolean add(E e);
@Override
public E get(int i) {
return null;
}
return new ArrayList<>(res);
@Override
public int size() {
return 0;
}
}
}

View File

@@ -1,10 +1,12 @@
package com.topjohnwu.magisk.utils;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Collection;
/**
* Modified by topjohnwu, based on Chainfire's libsuperuser
@@ -13,40 +15,38 @@ import java.util.List;
public class StreamGobbler extends Thread {
private BufferedReader reader = null;
private List<String> writer = null;
private boolean isRoot = false;
private Collection<String> writer = null;
/**
* <p>StreamGobbler constructor</p>
*
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
* <p>We use this class because sh STDOUT and STDERR should be read as quickly as
* possible to prevent a deadlock from occurring, or Process.waitFor() never
* returning (as the buffer is full, pausing the native process)</p>
*
* @param inputStream InputStream to read from
* @param outputList {@literal List<String>} to write to, or null
*/
public StreamGobbler(InputStream inputStream, List<String> outputList) {
public StreamGobbler(InputStream inputStream, Collection<String> outputList) {
try {
while (inputStream.available() != 0) {
inputStream.skip(inputStream.available());
}
} catch (IOException ignored) {}
reader = new BufferedReader(new InputStreamReader(inputStream));
writer = outputList;
}
public StreamGobbler(InputStream inputStream, List<String> outputList, boolean root) {
reader = new BufferedReader(new InputStreamReader(inputStream));
writer = outputList;
isRoot = root;
}
@Override
public void run() {
// keep reading the InputStream until it ends (or an error occurs)
try {
String line;
while ((line = reader.readLine()) != null) {
if (TextUtils.equals(line, "-shell-done-"))
return;
writer.add(line);
if (!line.equals("-root-done-") && !line.isEmpty()) {
Logger.shell(isRoot, "OUT: " + line);
}
Logger.shell(line);
}
} catch (IOException e) {
// reader probably closed, expected exit condition

View File

@@ -0,0 +1,64 @@
package com.topjohnwu.magisk.utils;
import org.kamranzafar.jtar.TarHeader;
import java.io.File;
import java.util.Arrays;
public class TarEntry extends org.kamranzafar.jtar.TarEntry {
public TarEntry(File file, String entryName) {
super(file, entryName);
}
/*
* Workaround missing java.nio.file.attribute.PosixFilePermission
* Simply just assign a default permission to the file
* */
@Override
public void extractTarHeader(String entryName) {
int permissions = file.isDirectory() ? 000755 : 000644;
header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions);
header.userName = new StringBuffer("");
header.groupName = header.userName;
}
/*
* Rewrite the header to GNU format
* */
@Override
public void writeEntryHeader(byte[] outbuf) {
super.writeEntryHeader(outbuf);
System.arraycopy("ustar \0".getBytes(), 0, outbuf, 257, TarHeader.USTAR_MAGICLEN);
getOctalBytes(header.mode, outbuf, 100, TarHeader.MODELEN);
getOctalBytes(header.userId, outbuf, 108, TarHeader.UIDLEN);
getOctalBytes(header.groupId, outbuf, 116, TarHeader.GIDLEN);
getOctalBytes(header.size, outbuf, 124, TarHeader.SIZELEN);
getOctalBytes(header.modTime, outbuf, 136, TarHeader.MODTIMELEN);
Arrays.fill(outbuf, 148, 148 + TarHeader.CHKSUMLEN, (byte) ' ');
Arrays.fill(outbuf, 329, 329 + TarHeader.USTAR_DEVLEN, (byte) '\0');
Arrays.fill(outbuf, 337, 337 + TarHeader.USTAR_DEVLEN, (byte) '\0');
// Recalculate checksum
getOctalBytes(computeCheckSum(outbuf), outbuf, 148, TarHeader.CHKSUMLEN);
}
/*
* Proper octal to ASCII conversion
* */
private void getOctalBytes(long value, byte[] buf, int offset, int length) {
int idx = length - 1;
buf[offset + idx] = 0;
--idx;
for (long val = value; idx >= 0; --idx) {
buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
val = val >> 3;
}
}
}

View File

@@ -0,0 +1,68 @@
package com.topjohnwu.magisk.utils;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Topic {
public boolean hasPublished = false;
private List<WeakReference<Subscriber>> subscribers;
public void subscribe(Subscriber sub) {
if (subscribers == null) {
subscribers = new LinkedList<>();
}
subscribers.add(new WeakReference<>(sub));
}
public void unsubscribe() {
subscribers = null;
}
public void unsubscribe(Subscriber sub) {
for (Iterator<WeakReference<Subscriber>> i = subscribers.iterator(); i.hasNext();) {
WeakReference<Subscriber> subscriber = i.next();
if (subscriber.get() == null || subscriber.get() == sub) {
i.remove();
}
}
}
public void publish() {
publish(true);
}
public void publish(boolean record) {
hasPublished = record;
if (subscribers != null) {
for (WeakReference<Subscriber> subscriber : subscribers) {
if (subscriber.get() != null)
subscriber.get().onTopicPublished(this);
}
}
}
public interface Subscriber {
default void subscribeTopics() {
for (Topic topic : getSubscription()) {
if (topic.hasPublished) {
onTopicPublished(topic);
}
topic.subscribe(this);
}
}
default void unsubscribeTopics() {
for (Topic event : getSubscription()) {
event.unsubscribe(this);
}
}
default void onTopicPublished() {
onTopicPublished(null);
}
void onTopicPublished(Topic topic);
Topic[] getSubscription();
}
}

View File

@@ -3,100 +3,119 @@ package com.topjohnwu.magisk.utils;
import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.widget.Toast;
import com.topjohnwu.magisk.FlashActivity;
import com.topjohnwu.magisk.MagiskFragment;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.asyncs.LoadRepos;
import com.topjohnwu.magisk.SplashActivity;
import com.topjohnwu.magisk.asyncs.RestoreStockBoot;
import com.topjohnwu.magisk.asyncs.UpdateRepos;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import com.topjohnwu.magisk.receivers.ManagerUpdate;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
public class Utils {
public static final int SELECT_BOOT_IMG = 3;
public static final String UNINSTALLER = "magisk_uninstaller.sh";
public static final String UTIL_FUNCTIONS= "util_functions.sh";
public static boolean isDownloading = false;
public static boolean itemExist(String path) {
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
List<String> ret = Shell.su(command);
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
public static boolean itemExist(Shell shell, String path) {
String command = "[ -e " + path + " ] && echo true || echo false";
List<String> ret = shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static boolean commandExists(String s) {
String command = "if [ -z $(which " + s + ") ]; then echo false; else echo true; fi";
List<String> ret = Shell.sh(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static boolean createFile(String path) {
public static void createFile(Shell shell, 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";
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
shell.su_raw(command);
}
public static boolean removeItem(String path) {
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
public static void removeItem(Shell shell, String path) {
String command = "rm -rf " + path + " 2>/dev/null";
shell.su_raw(command);
}
public static List<String> getModList(String path) {
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
return Shell.su(command);
public static List<String> getModList(Shell shell, String path) {
String command = "ls -d " + path + "/* | grep -v lost+found";
return shell.su(command);
}
public static List<String> readFile(String path) {
List<String> ret;
String command = "cat " + path;
if (Shell.rootAccess()) {
ret = Shell.su(command);
} else {
ret = Shell.sh(command);
}
return ret;
public static List<String> readFile(Shell shell, String path) {
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
return shell.su(command);
}
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
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();
return;
}
runWithPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
File file = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + filename);
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs())
|| (file.exists() && !file.delete())) {
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
return;
}
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs()) || (file.exists() && !file.delete())) {
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
isDownloading = true;
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
isDownloading = true;
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
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));
receiver.setFilename(filename);
context.registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
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.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
});
}
public static String getLegalFilename(CharSequence filename) {
@@ -105,20 +124,6 @@ public class Utils {
.replace("#", "").replace("@", "").replace("*", "");
}
public static String detectBootImage() {
String[] commands = {
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
"BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || readlink /dev/block/platform/*/by-name/$PARTITION || readlink /dev/block/platform/*/*/by-name/$PARTITION`",
"if [ ! -z \"$BOOTIMAGE\" ]; then break; fi",
"done",
"echo \"${BOOTIMAGE##*/}\""
};
List<String> ret = Shell.su(commands);
if (isValidShellResponse(ret))
return ret.get(0);
return null;
}
public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) {
return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch);
}
@@ -137,25 +142,29 @@ public class Utils {
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
}
public static int getPrefsInt(SharedPreferences prefs, String key) {
return getPrefsInt(prefs, key, 0);
}
public static MagiskManager getMagiskManager(Context context) {
return (MagiskManager) context.getApplicationContext();
}
public static void checkSafetyNet(MagiskManager magiskManager) {
new SafetyNetHelper(magiskManager) {
public static void checkSafetyNet(FragmentActivity activity) {
new SafetyNetHelper(activity) {
@Override
public void handleResults(int i) {
magiskManager.SNCheckResult = i;
magiskManager.safetyNetDone.trigger();
public void handleResults(Result result) {
getMagiskManager(mActivity).SNCheckResult = result;
getMagiskManager(mActivity).safetyNetDone.publish(false);
}
}.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 void clearRepoCache(Context context) {
MagiskManager mm = getMagiskManager(context);
mm.prefs.edit().remove(UpdateRepos.ETAG_KEY).apply();
mm.repoDB.clearRepo();
mm.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT);
}
public static String getNameFromUri(Context context, Uri uri) {
@@ -182,4 +191,355 @@ public class Utils {
Snackbar.LENGTH_LONG)
.setAction(R.string.ok, (v)->{}).show();
}
public static boolean checkNetworkStatus(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
public static void showMagiskUpdateNotification(MagiskManager mm) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
builder.setSmallIcon(R.drawable.ic_magisk)
.setContentTitle(mm.getString(R.string.magisk_update_title))
.setContentText(mm.getString(R.string.magisk_update_available, mm.remoteMagiskVersionString))
.setVibrate(new long[]{0, 100, 100, 100})
.setAutoCancel(true);
Intent intent = new Intent(mm, SplashActivity.class);
intent.putExtra(MagiskManager.INTENT_SECTION, "magisk");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
stackBuilder.addParentStack(SplashActivity.class);
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent = stackBuilder.getPendingIntent(MAGISK_UPDATE_NOTIFICATION_ID,
PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
}
public static void showManagerUpdateNotification(MagiskManager mm) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, MagiskManager.NOTIFICATION_CHANNEL);
builder.setSmallIcon(R.drawable.ic_magisk)
.setContentTitle(mm.getString(R.string.manager_update_title))
.setContentText(mm.getString(R.string.manager_download_install))
.setVibrate(new long[]{0, 100, 100, 100})
.setAutoCancel(true);
Intent intent = new Intent(mm, ManagerUpdate.class);
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(APK_UPDATE_NOTIFICATION_ID, builder.build());
}
public static void enableMagiskHide(Shell shell) {
shell.su_raw("magiskhide --enable");
}
public static void disableMagiskHide(Shell shell) {
shell.su_raw("magiskhide --disable");
}
public static List<String> listMagiskHide(Shell shell) {
return shell.su("magiskhide --ls");
}
public static void addMagiskHide(Shell shell, String pkg) {
shell.su_raw("magiskhide --add " + pkg);
}
public static void rmMagiskHide(Shell shell, String pkg) {
shell.su_raw("magiskhide --rm " + pkg);
}
public static String getLocaleString(Context context, Locale locale, @StringRes int id) {
Configuration config = context.getResources().getConfiguration();
config.setLocale(locale);
Context localizedContext = context.createConfigurationContext(config);
return localizedContext.getString(id);
}
public static List<Locale> getAvailableLocale(Context context) {
List<Locale> locales = new ArrayList<>();
HashSet<String> set = new HashSet<>();
Locale locale;
int compareId = R.string.download_file_error;
// Add default locale
locales.add(Locale.ENGLISH);
set.add(getLocaleString(context, Locale.ENGLISH, compareId));
// Add some special locales
locales.add(Locale.TAIWAN);
set.add(getLocaleString(context, Locale.TAIWAN, compareId));
locale = new Locale("pt", "BR");
locales.add(locale);
set.add(getLocaleString(context, locale, compareId));
// Other locales
for (String s : context.getAssets().getLocales()) {
locale = Locale.forLanguageTag(s);
if (set.add(getLocaleString(context, locale, compareId))) {
locales.add(locale);
}
}
Collections.sort(locales, (l1, l2) -> l1.getDisplayName(l1).compareTo(l2.getDisplayName(l2)));
return locales;
}
public static String genPackageName(String prefix, int length) {
StringBuilder builder = new StringBuilder(length);
builder.append(prefix);
length -= prefix.length();
SecureRandom random = new SecureRandom();
String base = "abcdefghijklmnopqrstuvwxyz";
String alpha = base + base.toUpperCase();
String full = alpha + "0123456789..........";
char next, prev = '\0';
for (int i = 0; i < length; ++i) {
if (prev == '.' || i == length - 1 || i == 0) {
next = alpha.charAt(random.nextInt(alpha.length()));
} else {
next = full.charAt(random.nextInt(full.length()));
}
builder.append(next);
prev = next;
}
return builder.toString();
}
public static void runWithPermission(Context context, String permission, Runnable callback) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
// Passed in context should be an activity if not granted, need to show dialog!
if (!(context instanceof com.topjohnwu.magisk.components.Activity))
return;
com.topjohnwu.magisk.components.Activity activity = (com.topjohnwu.magisk.components.Activity) context;
activity.setPermissionGrantCallback(callback);
ActivityCompat.requestPermissions(activity, new String[] { permission }, 0);
} else {
callback.run();
}
}
public static void showMagiskInstallDialog(MagiskFragment fragment, boolean enc, boolean verity) {
MagiskManager mm = getMagiskManager(fragment.getActivity());
String filename = getLegalFilename("Magisk-v" + mm.remoteMagiskVersionString + ".zip");
new AlertDialogBuilder(fragment.getActivity())
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
.setMessage(mm.getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> {
List<String> options = new ArrayList<>();
options.add(mm.getString(R.string.download_zip_only));
options.add(mm.getString(R.string.patch_boot_file));
if (Shell.rootAccess()) {
options.add(mm.getString(R.string.direct_install));
}
List<String> res = Shell.getShell(mm).su("echo $SLOT");
if (isValidShellResponse(res)) {
options.add(mm.getString(R.string.install_second_slot));
}
char[] slot = isValidShellResponse(res) ? res.get(0).toCharArray() : null;
new AlertDialog.Builder(fragment.getActivity())
.setTitle(R.string.select_method)
.setItems(
options.toArray(new String [0]),
(dialog, idx) -> {
String boot;
DownloadReceiver receiver = null;
switch (idx) {
case 1:
if (mm.remoteMagiskVersionCode < 1400) {
mm.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
return;
}
mm.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
fragment.startActivityForResult(intent, SELECT_BOOT_IMG,
(requestCode, resultCode, data) -> {
if (requestCode == SELECT_BOOT_IMG
&& resultCode == Activity.RESULT_OK && data != null) {
dlAndReceive(
fragment.getActivity(),
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, data.getData())
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
mm.startActivity(intent);
}
},
mm.magiskLink,
filename
);
}
});
return;
case 0:
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
showUriSnack(fragment.getActivity(), uri);
}
};
break;
case 2:
boot = fragment.getSelectedBootImage();
if (boot == null)
return;
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, boot)
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
mm.startActivity(intent);
}
};
break;
case 3:
assert (slot != null);
// Choose the other slot
if (slot[1] == 'a') slot[1] = 'b';
else slot[1] = 'a';
// Then find the boot image again
List<String> ret = Shell.getShell(mm).su(
"BOOTIMAGE=",
"SLOT=" + String.valueOf(slot),
"find_boot_image",
"echo \"$BOOTIMAGE\""
);
boot = isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
Shell.getShell(mm).su_raw("mount_partitions");
if (boot == null)
return;
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
Intent intent = new Intent(mm, FlashActivity.class);
intent.setData(uri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(FlashActivity.SET_BOOT, boot)
.putExtra(FlashActivity.SET_ENC, enc)
.putExtra(FlashActivity.SET_VERITY, verity)
.putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_MAGISK);
mm.startActivity(intent);
}
};
default:
}
Utils.dlAndReceive(
fragment.getActivity(),
receiver,
mm.magiskLink,
filename
);
}
).show();
})
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (mm.releaseNoteLink != null) {
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mm.startActivity(openLink);
}
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static void showManagerInstallDialog(Activity activity) {
MagiskManager mm = Utils.getMagiskManager(activity);
new AlertDialogBuilder(activity)
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)))
.setMessage(mm.getString(R.string.repo_install_msg,
Utils.getLegalFilename("MagiskManager-v" +
mm.remoteManagerVersionString + ".apk")))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) -> {
Intent intent = new Intent(mm, ManagerUpdate.class);
intent.putExtra(MagiskManager.INTENT_LINK, mm.managerLink);
intent.putExtra(MagiskManager.INTENT_VERSION, mm.remoteManagerVersionString);
mm.sendBroadcast(intent);
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static void showUninstallDialog(MagiskFragment fragment) {
MagiskManager mm = Utils.getMagiskManager(fragment.getActivity());
new AlertDialogBuilder(fragment.getActivity())
.setTitle(R.string.uninstall_magisk_title)
.setMessage(R.string.uninstall_magisk_msg)
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
try {
InputStream in = mm.getAssets().open(UNINSTALLER);
File uninstaller = new File(mm.getCacheDir(), UNINSTALLER);
FileOutputStream out = new FileOutputStream(uninstaller);
byte[] bytes = new byte[1024];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
in.close();
out.close();
in = mm.getAssets().open(UTIL_FUNCTIONS);
File utils = new File(mm.getCacheDir(), UTIL_FUNCTIONS);
out = new FileOutputStream(utils);
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
in.close();
out.close();
Shell.getShell(mm).su(
"cat " + uninstaller + " > /cache/" + UNINSTALLER,
"cat " + utils + " > /data/magisk/" + UTIL_FUNCTIONS
);
mm.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
Shell.getShell(mm).su_raw(
"sleep 5",
"pm uninstall " + mm.getApplicationInfo().packageName
);
} catch (IOException e) {
e.printStackTrace();
}
})
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
String boot = fragment.getSelectedBootImage();
if (boot == null) return;
new RestoreStockBoot(mm, boot).exec();
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
public static boolean useFDE(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& context.getSystemService(DevicePolicyManager.class).getStorageEncryptionStatus()
== DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
}
public static Context getEncContext(Context context) {
if (useFDE(context))
return context.createDeviceProtectedStorageContext();
else
return context;
}
}

View File

@@ -1,13 +1,11 @@
package com.topjohnwu.magisk.utils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
@@ -18,39 +16,32 @@ public class WebService {
public final static int GET = 1;
public final static int POST = 2;
private static Map<String, List<String>> responseHeader;
/**
* Making web service call
*
* @url - url to make request
* @requestmethod - http request method
*/
public static String request(String url, int method) {
return request(url, method, null, null, false);
public static String getString(String url) {
return getString(url, null);
}
public static String request(String url, int method, boolean newline) {
return request(url, method, null, null, newline);
}
/**
* Making service call
*
* @url - url to make request
* @requestmethod - http request method
* @params - http request params
* @header - http request header
* @newline - true to append a newline each line
*/
public static String request(String urlAddress, int method,
Map<String, String> params, Map<String, String> header,
boolean newline) {
Logger.dev("WebService: Service call " + urlAddress);
URL url;
StringBuilder response = new StringBuilder();
public static String getString(String url, Map<String, String> header) {
InputStream in = request(GET, url, header);
if (in == null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(in));
int len;
StringBuilder builder = new StringBuilder();
char buf[] = new char[4096];
try {
url = new URL(urlAddress);
while ((len = br.read(buf)) != -1) {
builder.append(buf, 0, len);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
}
public static InputStream request(int method, String address, Map<String, String> header) {
Logger.dev("WebService: Service call " + address);
try {
URL url = new URL(address);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(15000);
@@ -69,56 +60,20 @@ public class WebService {
}
}
if (params != null) {
OutputStream os = conn.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
StringBuilder result = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
writer.write(result.toString());
writer.flush();
writer.close();
os.close();
}
int responseCode = conn.getResponseCode();
if (responseCode == HttpsURLConnection.HTTP_OK) {
String line;
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line = br.readLine()) != null) {
if (newline) {
response.append(line).append("\n");
} else {
response.append(line);
if (conn.getResponseCode() == HttpsURLConnection.HTTP_OK) {
if (header != null) {
header.clear();
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
List<String> l = entry.getValue();
header.put(entry.getKey(), l.get(l.size() - 1));
}
}
responseHeader = conn.getHeaderFields();
} else {
responseHeader = null;
return conn.getInputStream();
}
} catch (Exception e) {
e.printStackTrace();
}
return response.toString();
}
public static Map<String, List<String>> getLastResponseHeader() {
return responseHeader;
return null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,58 +3,17 @@
//
#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;
Java_com_topjohnwu_magisk_utils_ZipUtils_zipAdjust(JNIEnv *env, jclass type, jstring filenameIn_,
jstring filenameOut_) {
const char *filenameIn = (*env)->GetStringUTFChars(env, filenameIn_, 0);
const char *filenameOut = (*env)->GetStringUTFChars(env, filenameOut_, 0);
// Load the file to memory
insize = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
fin = malloc(insize);
read(fd, fin, insize);
// TODO
zipadjust(filenameIn, filenameOut, 0);
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);
}
(*env)->ReleaseStringUTFChars(env, filenameIn_, filenameIn);
(*env)->ReleaseStringUTFChars(env, filenameOut_, filenameOut);
}

View File

@@ -1,10 +1,14 @@
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <zlib.h>
#include <unistd.h>
#include "zipadjust.h"
size_t insize = 0, outsize = 0, alloc = 0;
unsigned char *fin = NULL, *fout = NULL;
#ifndef O_BINARY
#define O_BINARY 0
#define O_TEXT 0
#endif
#pragma pack(1)
struct local_header_struct {
@@ -82,41 +86,43 @@ static int xerror(char* message) {
return 0;
}
static int xseekread(off_t offset, void* buf, size_t bytes) {
memcpy(buf, fin + offset, bytes);
static int xseekread(int fd, off_t offset, void* buf, size_t bytes) {
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
if (read(fd, buf, bytes) != bytes) return xerror("Read failed");
return 1;
}
static int xseekwrite(off_t offset, const void* buf, size_t bytes) {
if (offset + bytes > outsize) outsize = offset + bytes;
if (outsize > alloc) {
fout = realloc(fout, outsize);
alloc = outsize;
}
memcpy(fout + offset, buf, bytes);
static int xseekwrite(int fd, off_t offset, void* buf, size_t bytes) {
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) return xerror("Seek failed");
if (write(fd, buf, bytes) != bytes) return xerror("Write failed");
return 1;
}
static int xfilecopy(off_t offsetIn, off_t offsetOut, size_t bytes) {
unsigned int CHUNK = 256 * 1024;
unsigned char* buf = malloc(CHUNK);
static int xfilecopy(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
int CHUNK = 256 * 1024;
void* buf = malloc(CHUNK);
if (buf == NULL) return xerror("malloc failed");
size_t left = bytes;
while (left > 0) {
size_t wanted = (left < CHUNK) ? left : CHUNK;
xseekread(offsetIn, buf, wanted);
xseekwrite(offsetOut, buf, wanted);
offsetIn += wanted;
offsetOut += wanted;
left -= wanted;
size_t r = read(fdIn, buf, wanted);
if (r <= 0) return xerror("Read failed");
if (write(fdOut, buf, r) != r) return xerror("Write failed");
left -= r;
}
free(buf);
return 1;
}
static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
unsigned int CHUNK = 256 * 1024;
static int xdecompress(int fdIn, int fdOut, off_t offsetIn, off_t offsetOut, size_t bytes) {
if ((offsetIn != (off_t)-1) && (lseek(fdIn, offsetIn, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
if ((offsetOut != (off_t)-1) && (lseek(fdOut, offsetOut, SEEK_SET) == (off_t)-1)) return xerror("Seek failed");
int CHUNK = 256 * 1024;
int ret;
unsigned have;
@@ -133,12 +139,9 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
if (ret != Z_OK) return xerror("ret != Z_OK");
do {
strm.avail_in = insize - offsetIn;
strm.avail_in = read(fdIn, in, CHUNK);
if (strm.avail_in == 0) break;
strm.avail_in = (strm.avail_in > CHUNK) ? CHUNK : strm.avail_in;
xseekread(offsetIn, in, strm.avail_in);
strm.next_in = in;
offsetIn += strm.avail_in;
do {
strm.avail_out = CHUNK;
@@ -156,8 +159,10 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
}
have = CHUNK - strm.avail_out;
xseekwrite(offsetOut, out, have);
offsetOut += have;
if (write(fdOut, out, have) != have) {
(void)inflateEnd(&strm);
return xerror("Write failed");
}
} while (strm.avail_out == 0);
} while (ret != Z_STREAM_END);
(void)inflateEnd(&strm);
@@ -165,118 +170,128 @@ static int xdecompress(off_t offsetIn, off_t offsetOut, size_t bytes) {
return ret == Z_STREAM_END ? 1 : 0;
}
int zipadjust(int decompress) {
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress) {
int ok = 0;
char filename[1024];
int fin = open(filenameIn, O_RDONLY | O_BINARY);
if (fin > 0) {
unsigned int size = lseek(fin, 0, SEEK_END);
lseek(fin, 0, SEEK_SET);
LOGD("%d bytes\n", size);
central_footer_t central_footer;
uint32_t central_directory_in_position = 0;
uint32_t central_directory_in_size = 0;
uint32_t central_directory_out_size = 0;
char filename[1024];
int i;
for (i = insize - 4; i >= 0; i--) {
uint32_t magic = 0;
if (!xseekread(i, &magic, sizeof(uint32_t))) return 0;
if (magic == MAGIC_CENTRAL_FOOTER) {
LOGD("central footer @ %08X\n", i);
if (!xseekread(i, &central_footer, sizeof(central_footer_t))) return 0;
central_footer_t central_footer;
uint32_t central_directory_in_position = 0;
uint32_t central_directory_in_size = 0;
uint32_t central_directory_out_size = 0;
central_header_t central_header;
if (!xseekread(central_footer.central_directory_offset, &central_header, sizeof(central_header_t))) return 0;
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
central_directory_in_position = central_footer.central_directory_offset;
central_directory_in_size = insize - central_footer.central_directory_offset;
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
break;
int i;
for (i = size - 4; i >= 0; i--) {
uint32_t magic = 0;
if (!xseekread(fin, i, &magic, sizeof(uint32_t))) return 0;
if (magic == MAGIC_CENTRAL_FOOTER) {
LOGD("central footer @ %08X\n", i);
if (!xseekread(fin, i, &central_footer, sizeof(central_footer_t))) return 0;
central_header_t central_header;
if (!xseekread(fin, central_footer.central_directory_offset, &central_header, sizeof(central_header_t))) return 0;
if ( central_header.signature == MAGIC_CENTRAL_HEADER ) {
central_directory_in_position = central_footer.central_directory_offset;
central_directory_in_size = size - central_footer.central_directory_offset;
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
break;
}
}
}
if (central_directory_in_position == 0) return 0;
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
if (!xseekread(fin, central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
memset(central_directory_out, 0, central_directory_in_size);
unlink(filenameOut);
int fout = open(filenameOut, O_CREAT | O_WRONLY | O_BINARY, 0644);
if (fout > 0) {
uintptr_t central_directory_in_index = 0;
uintptr_t central_directory_out_index = 0;
central_header_t* central_header = NULL;
uint32_t out_index = 0;
while (1) {
central_header = (central_header_t*)&central_directory_in[central_directory_in_index];
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
filename[central_header->length_filename] = (char)0;
memcpy(filename, &central_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
local_header_t local_header;
if (!xseekread(fin, central_header->offset, &local_header, sizeof(local_header_t))) return 0;
// save and update to next index before we clobber the data
uint16_t compression_method_old = central_header->compression_method;
uint32_t size_compressed_old = central_header->size_compressed;
uint32_t offset_old = central_header->offset;
uint32_t length_extra_old = central_header->length_extra;
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
central_header->offset = out_index;
central_header->flags = central_header->flags & !8;
if (decompress && (compression_method_old == 8)) {
central_header->compression_method = 0;
central_header->size_compressed = central_header->size_uncompressed;
}
central_header->length_extra = 0;
central_header->length_comment = 0;
local_header.compression_method = central_header->compression_method;
local_header.flags = central_header->flags;
local_header.crc32 = central_header->crc32;
local_header.size_uncompressed = central_header->size_uncompressed;
local_header.size_compressed = central_header->size_compressed;
local_header.length_extra = 0;
if (!xseekwrite(fout, out_index, &local_header, sizeof(local_header_t))) return 0;
out_index += sizeof(local_header_t);
if (!xseekwrite(fout, out_index, &filename[0], central_header->length_filename)) return 0;
out_index += central_header->length_filename;
if (decompress && (compression_method_old == 8)) {
if (!xdecompress(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
} else {
if (!xfilecopy(fin, fout, offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
}
out_index += local_header.size_compressed;
memcpy(&central_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
}
central_directory_out_size = central_directory_out_index;
central_footer.central_directory_size = central_directory_out_size;
central_footer.central_directory_offset = out_index;
central_footer.length_comment = 0;
if (!xseekwrite(fout, out_index, central_directory_out, central_directory_out_size)) return 0;
out_index += central_directory_out_size;
if (!xseekwrite(fout, out_index, &central_footer, sizeof(central_footer_t))) return 0;
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
LOGD("central footer @ %08X\n", out_index);
close(fout);
ok = 1;
}
free(central_directory_in);
free(central_directory_out);
close(fin);
}
if (central_directory_in_position == 0) return 0;
unsigned char* central_directory_in = (unsigned char*)malloc(central_directory_in_size);
unsigned char* central_directory_out = (unsigned char*)malloc(central_directory_in_size);
if (!xseekread(central_directory_in_position, central_directory_in, central_directory_in_size)) return 0;
memset(central_directory_out, 0, central_directory_in_size);
fout = (unsigned char*) malloc(insize);
alloc = insize;
uintptr_t central_directory_in_index = 0;
uintptr_t central_directory_out_index = 0;
central_header_t* central_header = NULL;
uint32_t out_index = 0;
while (1) {
central_header = (central_header_t*)&central_directory_in[central_directory_in_index];
if (central_header->signature != MAGIC_CENTRAL_HEADER) break;
filename[central_header->length_filename] = (char)0;
memcpy(filename, &central_directory_in[central_directory_in_index + sizeof(central_header_t)], central_header->length_filename);
LOGD("%s (%d --> %d) [%08X] (%d)\n", filename, central_header->size_uncompressed, central_header->size_compressed, central_header->crc32, central_header->length_extra + central_header->length_comment);
local_header_t local_header;
if (!xseekread(central_header->offset, &local_header, sizeof(local_header_t))) return 0;
// save and update to next index before we clobber the data
uint16_t compression_method_old = central_header->compression_method;
uint32_t size_compressed_old = central_header->size_compressed;
uint32_t offset_old = central_header->offset;
uint32_t length_extra_old = central_header->length_extra;
central_directory_in_index += sizeof(central_header_t) + central_header->length_filename + central_header->length_extra + central_header->length_comment;
// copying, rewriting, and correcting local and central headers so all the information matches, and no data descriptors are necessary
central_header->offset = out_index;
central_header->flags = central_header->flags & !8;
if (decompress && (compression_method_old == 8)) {
central_header->compression_method = 0;
central_header->size_compressed = central_header->size_uncompressed;
}
central_header->length_extra = 0;
central_header->length_comment = 0;
local_header.compression_method = central_header->compression_method;
local_header.flags = central_header->flags;
local_header.crc32 = central_header->crc32;
local_header.size_uncompressed = central_header->size_uncompressed;
local_header.size_compressed = central_header->size_compressed;
local_header.length_extra = 0;
if (!xseekwrite(out_index, &local_header, sizeof(local_header_t))) return 0;
out_index += sizeof(local_header_t);
if (!xseekwrite(out_index, &filename[0], central_header->length_filename)) return 0;
out_index += central_header->length_filename;
if (decompress && (compression_method_old == 8)) {
if (!xdecompress(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
} else {
if (!xfilecopy(offset_old + sizeof(local_header_t) + central_header->length_filename + length_extra_old, out_index, size_compressed_old)) return 0;
}
out_index += local_header.size_compressed;
memcpy(&central_directory_out[central_directory_out_index], central_header, sizeof(central_header_t) + central_header->length_filename);
central_directory_out_index += sizeof(central_header_t) + central_header->length_filename;
}
central_directory_out_size = central_directory_out_index;
central_footer.central_directory_size = central_directory_out_size;
central_footer.central_directory_offset = out_index;
central_footer.length_comment = 0;
if (!xseekwrite(out_index, central_directory_out, central_directory_out_size)) return 0;
out_index += central_directory_out_size;
if (!xseekwrite(out_index, &central_footer, sizeof(central_footer_t))) return 0;
LOGD("central header @ %08X (%d)\n", central_footer.central_directory_offset, central_footer.central_directory_size);
LOGD("central footer @ %08X\n", out_index);
ok = 1;
free(central_directory_in);
free(central_directory_out);
return ok;
}
}

View File

@@ -3,10 +3,7 @@
#include <android/log.h>
int zipadjust(int decompress);
extern size_t insize, outsize, alloc;
extern unsigned char *fin, *fout;
int zipadjust(const char* filenameIn, const char* filenameOut, int decompress);
#define LOG_TAG "zipadjust"

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@android:color/black"
tools:context="com.topjohnwu.magisk.FlashActivity">
<include layout="@layout/toolbar"/>
<HorizontalScrollView
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/flash_logs"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager">
</android.support.v7.widget.RecyclerView>
</HorizontalScrollView>
<LinearLayout
android:id="@+id/button_panel"
style="?android:buttonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@android:color/darker_gray"
android:orientation="horizontal"
android:visibility="gone">
<Button
android:id="@+id/no_thanks"
style="?android:borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/close" />
<Button
android:id="@+id/reboot"
style="?android:borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/reboot" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,251 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="@+id/install_info_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp">
<TextView
android:id="@+id/install_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="5dp"
android:textStyle="bold" />
<TextView
android:id="@+id/current_version_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="5dp"
android:textStyle="bold" />
</LinearLayout>
</android.support.v7.widget.CardView>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:maxWidth="400dp"
android:minWidth="400dp">
<android.support.v7.widget.CardView
android:id="@+id/bootimage_card"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true"
android:layout_width="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="10dp"
android:text="@string/boot_image_title"
android:textStyle="bold" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp">
<Spinner
android:layout_width="0dp"
android:layout_height="match_parent"
android:id="@+id/block_spinner"
android:layout_weight="1" />
<Button
android:text="@string/detect_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/detect_bootimage"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/install_option_card"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true"
android:layout_width="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="10dp"
android:text="@string/advanced_settings_title"
android:textStyle="bold" />
<CheckBox
android:text="@string/keep_force_encryption"
android:id="@+id/keep_force_enc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="25dp"
android:layout_marginStart="25dp" />
<CheckBox
android:text="@string/keep_dm_verity"
android:id="@+id/keep_verity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="25dp"
android:layout_marginStart="25dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/flash_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:text="@string/download_install"
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,466 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="@+id/magisk_update_card"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/magisk_update_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_margin="15dp"
android:layout_toStartOf="@+id/magisk_update_status"
android:src="@drawable/ic_refresh"
android:visibility="gone" />
<ProgressBar
android:id="@+id/magisk_update_progress"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_margin="15dp"
android:layout_toStartOf="@+id/magisk_update_status" />
<TextView
android:id="@+id/magisk_update_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:minWidth="225dp"
android:padding="6dp"
android:text="@string/checking_for_updates"
android:textStyle="bold" />
</RelativeLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginTop="15dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/magisk_status_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp"
android:layout_toStartOf="@+id/magisk_version" />
<TextView
android:id="@+id/magisk_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:minWidth="225dp"
android:padding="6dp"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="15dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/root_status_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp"
android:layout_toStartOf="@+id/root_status" />
<TextView
android:id="@+id/root_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:minWidth="225dp"
android:padding="6dp"
android:textStyle="bold" />
</RelativeLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/safetyNet_card"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/safetyNet_title"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/safetyNet_refresh"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_margin="15dp"
android:layout_toStartOf="@+id/safetyNet_status"
android:src="@drawable/ic_refresh"
android:tint="?attr/imageColorTint" />
<ProgressBar
android:id="@+id/safetyNet_check_progress"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:layout_margin="15dp"
android:layout_toStartOf="@+id/safetyNet_status"
android:visibility="gone" />
<TextView
android:id="@+id/safetyNet_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:gravity="center"
android:minWidth="175dp"
android:padding="6dp"
android:text="@string/safetyNet_check_text"
android:textStyle="bold" />
</RelativeLayout>
<LinearLayout
android:id="@+id/expand_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="10dp"
android:paddingStart="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/cts_status_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginBottom="5dp"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp" />
<TextView
android:id="@+id/cts_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minWidth="150dp"
android:padding="6dp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/basic_status_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_margin="10dp" />
<TextView
android:id="@+id/basic_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minWidth="150dp"
android:padding="6dp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/bootimage_card"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="10dp"
android:text="@string/boot_image_title"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="25dp"
android:layout_marginStart="25dp"
android:minHeight="35dp"
android:orientation="horizontal">
<Spinner
android:id="@+id/block_spinner"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:id="@+id/detect_bootimage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/detect_button"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/install_option_card"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:paddingBottom="10dp"
android:text="@string/advanced_settings_title"
android:textStyle="bold" />
<CheckBox
android:id="@+id/keep_force_enc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp"
android:layout_marginStart="50dp"
android:text="@string/keep_force_encryption" />
<CheckBox
android:id="@+id/keep_verity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp"
android:layout_marginStart="50dp"
android:text="@string/keep_dm_verity" />
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/install_button"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
android:visibility="gone"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginEnd="25dp"
android:layout_marginStart="25dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="0"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
android:id="@+id/install_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:ems="10"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/install"
android:textAllCaps="false"
android:textSize="20sp"
android:textStyle="bold" />
<FrameLayout
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_weight="0">
</FrameLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/uninstall_button"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:layout_marginEnd="5dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
app:cardCornerRadius="@dimen/card_corner_radius"
app:cardElevation="@dimen/card_elevation">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:ems="10"
android:fontFamily="sans-serif"
android:gravity="center"
android:text="@string/uninstall"
android:textAllCaps="false"
android:textSize="20sp"
android:textStyle="bold" />
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

View File

@@ -20,8 +20,10 @@
android:id="@+id/txtLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:padding="8dp"
android:textIsSelectable="true"/>
android:textIsSelectable="true"
android:textSize="10sp" />
<ProgressBar
android:id="@+id/progressBar"

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/res-auto"
<android.support.v4.widget.SwipeRefreshLayout 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:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="fill_parent"
@@ -22,7 +22,8 @@
android:layout_height="match_parent"
android:clipToPadding="false"
android:dividerHeight="@dimen/card_divider_space"
fab:layoutManager="android.support.v7.widget.LinearLayoutManager" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
<TextView
android:id="@+id/empty_rv"
@@ -36,39 +37,16 @@
android:textStyle="italic"
android:visibility="gone" />
<com.github.clans.fab.FloatingActionMenu
android:id="@+id/fabmenu"
android:layout_width="500dp"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginEnd="113dp"
android:layout_marginBottom="10dp"
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="?android:colorAccent"
fab:menu_colorPressed="?attr/colorAccentFallback"
fab:menu_animationDelayPerItem="50"
fab:menu_icon="@drawable/ic_add"
fab:menu_buttonSpacing="0dp"
fab:menu_labels_position="left" >
<com.github.clans.fab.FloatingActionButton
android:id="@+id/fab"
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" />
</com.github.clans.fab.FloatingActionMenu>
android:layout_margin="@dimen/fab_padding"
android:elevation="6dp"
android:src="@drawable/ic_add"
tools:fabSize="normal"
tools:pressedTranslationZ="12dp" />
</android.support.design.widget.CoordinatorLayout>

View File

@@ -1,196 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="@+id/magiskStatusView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/magisk_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="6dp"
android:textStyle="bold"/>
<FrameLayout
android:id="@+id/magisk_status_container"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="?android:attr/selectableItemBackground"
android:foregroundGravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/magisk_status_icon"
android:layout_width="84dp"
android:layout_height="84dp"
android:layout_gravity="center"/>
<ProgressBar
android:id="@+id/magisk_check_updates_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
<TextView
android:id="@+id/magisk_update_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="6dp"
android:textStyle="bold"
android:text="@string/checking_for_updates" />
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/rootStatusView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/root_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="6dp"
android:textStyle="bold"/>
<FrameLayout
android:id="@+id/root_status_container"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="?android:attr/selectableItemBackground"
android:foregroundGravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/root_status_icon"
android:layout_width="84dp"
android:layout_height="84dp"
android:layout_gravity="center" />
</FrameLayout>
<TextView
android:id="@+id/root_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="6dp"
android:textStyle="bold"/>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/safetyNetView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="6dp"
style="?attr/cardStyle"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="@+id/safetyNet_container"
android:layout_width="match_parent"
android:layout_height="100dp"
android:foregroundGravity="center"
android:orientation="vertical"
android:background="@color/grey500">
<ImageView
android:id="@+id/safetyNet_icon"
android:layout_width="84dp"
android:layout_height="84dp"
android:layout_gravity="center"
android:src="@drawable/ic_safetynet"/>
<ProgressBar
android:id="@+id/safetyNet_check_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</FrameLayout>
<TextView
android:id="@+id/safetyNet_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="6dp"
android:textStyle="bold"
android:text="@string/safetyNet_check_text" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textColor="@android:color/white"
android:textSize="10sp" />

View File

@@ -1,23 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:src="@drawable/ic_arrow"
android:id="@+id/arrow"
android:layout_marginEnd="10dp" />
android:layout_marginEnd="10dp"
android:src="@drawable/ic_arrow" />
<TextView
android:text="2017/1/1"
android:id="@+id/date"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/date"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical"
android:text="2017/1/1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="center_vertical" />
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>

View File

@@ -6,15 +6,9 @@
android:id="@+id/main_group">
<item
android:id="@+id/status"
android:icon="@drawable/ic_device_information"
android:title="@string/status"/>
<item
android:id="@+id/install"
android:id="@+id/magisk"
android:icon="@drawable/ic_magisk"
android:title="@string/install"
android:visible="false"/>
android:title="@string/magisk"/>
<item
android:id="@+id/superuser"

View File

@@ -2,22 +2,42 @@
<!--Universal-->
<!--Welcome Activity-->
<string name="navigation_drawer_open">فتح درج التنقل</string>
<string name="navigation_drawer_close">إغلاق درج التنقل</string>
<string name="modules">الإضافات</string>
<string name="downloads">التنزيلات</string>
<string name="superuser">Superuser</string>
<string name="log">السجل</string>
<string name="settings">الإعدادات</string>
<string name="install">التثبيت</string>
<!--Magisk Fragment-->
<string name="magisk_version">المثبت Magisk v%1$s</string>
<string name="magisk_version_error">هل قمت بتثبيت Magisk؟</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk غير مثبت</string>
<string name="magisk_update_available">Magisk v%1$.1f تحديث!</string>
<string name="cannot_check_updates">لا يمكن التحقق من التحديثات</string>
<string name="up_to_date">أحدث إصدار من %1$s مثبت</string>
<string name="checking_for_updates">البحث عن تحديثات…</string>
<string name="magisk_update_available">Magisk v%1$s متاح!</string>
<string name="cannot_check_updates">لا يمكن التحقق من التحديثات، لا يوجد إنترنت؟</string>
<string name="root_error">مروت لكن لا يوجد إذن الروت، غير مسموح به؟</string>
<string name="not_rooted">غير مروت</string>
<string name="safetyNet_check_text">انقر لبدء فحص SafetyNet</string>
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet…</string>
<string name="safetyNet_check_success">نجح فحص SafetyNet</string>
<string name="safetyNet_no_response">لا يمكن التحقق من SafetyNet، لا يوجد إنترنت؟</string>
<string name="safetyNet_network_loss">فقدان الاتصال بالشبكة</string>
<string name="safetyNet_service_disconnected">تم إنهاء الخدمة</string>
<string name="safetyNet_res_invalid">الاستجابة غير صالحه</string>
<!--Root Fragment-->
<!--Install Fragment-->
<string name="auto_detect">(تلقائي) %1$s</string>
<string name="cannot_auto_detect">(لا يمكن الكشف التلقائي)</string>
<string name="boot_image_title">موقع ملف الإقلاع</string>
<string name="detect_button">تحقق</string>
<string name="advanced_settings_title">إعدادات متقدمة</string>
<string name="keep_force_encryption">الحفاظ علي قوه التشفير</string>
<string name="keep_dm_verity">إبقاء dm-verity</string>
<string name="current_magisk_title">نسخة Magisk المثبته: %1$s</string>
<string name="install_magisk_title">آخر نسخة Magisk: %1$s</string>
<string name="uninstall">إلغاء التثبيت</string>
<string name="reboot_countdown">إعادة التشغيل في %1$d</string>
<string name="uninstall_magisk_title">إلغاء تثبيت Magisk</string>
<!--Module Fragment-->
<string name="no_info_provided">(لم يتم توفير أي معلومات)</string>
@@ -27,8 +47,7 @@
<string name="remove_file_deleted">لن يتم حذف الإضافة في إعادة التشغيل التالي</string>
<string name="disable_file_created">سيتم تعطيل الإضافة في إعادة التشغيل التالي</string>
<string name="disable_file_removed">سيتم تمكين الإضافة في إعادة التشغيل التالي</string>
<string name="author">إنشئ بواسطة %1$s</string>
<string name="fab_flash_zip">تثبيت الملف المضغوط للإضافة</string>
<string name="author">انشئ بواسطة %1$s</string>
<!--Repo Fragment-->
<string name="update_available">يتوفر تحديث</string>
@@ -58,61 +77,103 @@
<!--Toasts, Dialogs-->
<string name="permissionNotGranted">أن هذه الميزة لا تعمل دون الحصول على إذن الكتابة على التخزين الخارجي.</string>
<string name="no_thanks">لا شكراً</string>
<string name="yes">نعم</string>
<string name="ok">موافق</string>
<string name="close">إغلاق</string>
<string name="repo_install_title">تثبيت %1$s</string>
<string name="repo_install_msg">هل تريد تثبيت %1$s ?</string>
<string name="download_install">تنزيل وتثبيت</string>
<string name="download">التنزيل</string>
<string name="download_file_error">خطأ تنزيل الملف</string>
<string name="install_error">خطأ في التثبيت!</string>
<string name="invalid_zip">الملف المضغوط ليس إضافة Magisk!!</string>
<string name="reboot_title">التثبيت نجح!</string>
<string name="reboot_msg">هل تريد إعادة التشغيل الآن؟</string>
<string name="reboot">إعادة التشغيل</string>
<string name="zip_install_progress_title">تثبيت</string>
<string name="zip_unzip_msg">فك الضغط عن الملف المضغوط …</string>
<string name="zip_process_msg">معالجة الملف المضغوط …</string>
<string name="zip_install_progress_msg">تثبيت %1$s</string>
<string name="no_magisk_title">لا يوجد Magisk مثبت!</string>
<string name="no_magisk_msg">هل ترغب في تنزيل وتثبيت Magisk؟</string>
<!--URL Templates-->
<string name="downloading_toast">جاري التنزيل %1$s</string>
<string name="magisk_update_title">تحديث Magisk جديد متوفر!</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
<string name="release_notes">ملاحظات الإصدار</string>
<string name="repo_cache_cleared">تم مسح الذاكرة المؤقته للمستودع</string>
<string name="safetyNet_hide_notice">يستخدم هذا التطبيق SafetyNet, تم التعامل معها بالفعل من قبل MagiskHide بشكل افتراضي</string>
<string name="process_error">خطأ في العملية</string>
<string name="internal_storage">يتم تخزين الملف المضغوط في:\n[التخزين الداخلي]%1$s</string>
<string name="zip_process_title">معالجة</string>
<string name="manual_boot_image">يرجى تحديد ملف الإقلاع يدوياً!</string>
<!--Settings Activity -->
<string name="settings_general_category">عام</string>
<string name="settings_dark_theme_title">السمة</string>
<string name="settings_dark_theme_summary">أختر سمة</string>
<string name="settings_dark_theme_title">السمة الغامقة</string>
<string name="settings_dark_theme_summary">تفعيل السمة الغامقة</string>
<string name="settings_notification_title">إشعارات التحديث</string>
<string name="settings_notification_summary">عرض إشعارات التحديث عند توفر الإصدار الجديد</string>
<string name="settings_clear_cache_title">حذف الذاكرة المؤقتة للمستودع</string>
<string name="settings_clear_cache_summary">حذف المعلومات المخزنة مؤقتا للمستودع على الانترنت، يجبر التطبيق لتحديث عبر الانترنت</string>
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف حالات الإكتشاف</string>
<string name="settings_busybox_title">تمكين BusyBox</string>
<string name="settings_busybox_summary">ربط تحميل Magisk\'s المدمج في busybox إلى xbin</string>
<string name="settings_core_only_title">Magisk الوضع الأساسي فقط</string>
<string name="settings_core_only_summary">تمكين الميزات الأساسية فقط، لن يتم تحميل جميع الإضافات. MagiskSU، MagiskHide، systemless hosts، و لا يزال ممكنا</string>
<string name="settings_magiskhide_summary">إخفاء Magisk من مختلف الاكتشافات</string>
<string name="settings_hosts_title">تمكين المضيفين(الهوست) لـ systemless</string>
<string name="settings_hosts_summary">Systemless يدعم تطبيقات حجب الإعلانات</string>
<string name="settings_development_category">التطوير</string>
<string name="settings_su_app_adb">التطبيقات و ADB</string>
<string name="settings_su_app">التطبيقات فقط</string>
<string name="settings_su_adb">ADB فقط</string>
<string name="settings_su_disable">معطل</string>
<string name="settings_su_request_10">10 ثواني</string>
<string name="settings_su_request_20">20 ثانية</string>
<string name="settings_su_request_30">30 ثانية</string>
<string name="settings_su_request_60">60 ثانية</string>
<string name="superuser_access">Superuser صلاحيات</string>
<string name="auto_response">استجابة تلقائية</string>
<string name="request_timeout">مهلة الطلب</string>
<string name="superuser_notification">Superuser إشعارات</string>
<string name="request_timeout_summary">%1$s ثانية</string>
<string name="multiuser_mode">وضع تعدد المستخدمين</string>
<string name="settings_owner_only">مالك الجهاز فقط</string>
<string name="settings_owner_manage">إدارة مالك الجهاز</string>
<string name="settings_user_independent">مستخدم مستقل</string>
<string name="owner_only_summary">المالك فقط لديه صلاحيات الروت</string>
<string name="owner_manage_summary">يمكن للمالك فقط إدارة صلاحيات الروت وتلقي مطالبات الطلب</string>
<string name="user_indepenent_summary">كل مستخدم لديه قواعد روت منفصلة خاصة به</string>
<string name="multiuser_hint_owner_request">تم إرسال طلب إلى مالك الجهاز. يرجى التبديل إلى المالك ومنح الإذن</string>
<string name="settings_development_category">تطوير التطبيق</string>
<string name="settings_developer_logging_title">تمكين تصحيح السجلات المتقدمة</string>
<string name="settings_developer_logging_summary">حدد هذا الخيار لتمكين سجل مطول أكثر.</string>
<string name="settings_shell_logging_title">تمكين سجل تصحيح الأوامر الدفعية</string>
<string name="settings_shell_logging_summary">حدد هذا الخيار لتمكين سجل جميع الأوامر الدفعية والمخرجات</string>
<string name="settings_reboot_toast">إعادة التشغيل لتطبيق الإعدادات</string>
<string name="auto_detect">(تلقائي) %1$s</string>
<string name="checking_for_updates">البحث عن تحديثات…</string>
<string name="install">التثبيت</string>
<string name="not_rooted">غير مروت</string>
<string name="proper_root">مروت فعلاً</string>
<string name="advanced_settings_title">إعدادات متقدمة</string>
<string name="boot_image_title">موقع ملف الإقلاع</string>
<string name="checking_safetyNet_status">التحقق من حالة SafetyNet...</string>
<string name="copying_msg">نسخ الملف المضغوط إلى دليل مؤقت</string>
<string name="detect_button">تحقق</string>
<string name="downloading_toast">جاري التنزيل %1$s</string>
<string name="install_magisk_title">تثبيت Magisk الإصدار: %1$s</string>
<string name="keep_force_encryption">إبقاء التشفير القوى</string>
<string name="keep_dm_verity">إبقاء dm-verity</string>
<string name="root_error">مروت لكن لا يوجد إذن الروت، غير مسموح به؟</string>
<string name="root_info_warning">وظائف محدودة إلى حد كبير</string>
<string name="safetyNet_error">تعذر التحقق من SafetyNet، لا يوجد إنترنت؟</string>
<string name="safetyNet_pass">SafetyNet تخطى</string>
<string name="safetyNet_fail">فشل SafetyNet: عدم تطابق التشكيل الجانبي CTS</string>
<string name="status">الحالة</string>
<!--Superuser-->
<string name="su_request_title">Superuser طلبات</string>
<string name="deny_with_str">رفض%1$s</string>
<string name="deny">رفض</string>
<string name="prompt">طلب</string>
<string name="grant">سماح</string>
<string name="su_warning">منح حق الوصول الكامل إلى جهازك.\nرفض إذا كنت غير متأكد!</string>
<string name="forever">للابد</string>
<string name="once">مره</string>
<string name="tenmin">10 دقائق</string>
<string name="twentymin">20 دقائق</string>
<string name="thirtymin">30 دقائق</string>
<string name="sixtymin">60 دقائق</string>
<string name="su_allow_toast">%1$s يتم منح صلاحيات Superuser</string>
<string name="su_deny_toast">%1$s يتم رفض صلاحيات Superuser</string>
<string name="no_apps_found">لا توجد تطبيقات</string>
<string name="su_snack_grant">Superuser الصلاحيات لـ %1$s تم منحها</string>
<string name="su_snack_deny">Superuser الصلاحيات لـ %1$s تم رفضها</string>
<string name="su_snack_notif_on">الإشعارات لـ %1$s تم تفعيلها</string>
<string name="su_snack_notif_off">الإشعارات لـ %1$s تم تعطيلها</string>
<string name="su_snack_log_on">السجلات لـ %1$s تم تفعيلها</string>
<string name="su_snack_log_off">السجلات لـ %1$s تم تعطيلها</string>
<string name="su_snack_revoke">%1$s الصلاحيات سحبت</string>
<string name="su_revoke_title">سحب؟</string>
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string>
<string name="toast">نخب</string>
<string name="none">بدون</string>
</resources>
<!--Superuser logs-->
<string name="pid">PID:\u0020</string>
<string name="target_uid">الهدف UID:\u0020</string>
<string name="command">الأمر:\u0020</string>
</resources>

View File

@@ -0,0 +1,164 @@
<resources>
<!--Universal-->
<!--Welcome Activity-->
<string name="modules">Moduly</string>
<string name="downloads">Stahování</string>
<string name="superuser">Superuser</string>
<string name="log">Log</string>
<string name="settings">Nastavení</string>
<string name="install">Instalovat</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk není nainstalován</string>
<string name="checking_for_updates">Kontrola aktualizací…</string>
<string name="magisk_update_available">Magisk v%1$s je dostupný!</string>
<string name="cannot_check_updates">Nelze zkontrolovat aktualizace. Jste připojeni k Internetu?</string>
<string name="root_error">Zařízení s rootem ale chybí root povolení, máte jej zpřístupněno?</string>
<string name="not_rooted">Nemáte root</string>
<string name="safetyNet_check_text">Kliknutím zahájíte SafetyNet kontrolu</string>
<string name="checking_safetyNet_status">Kontrola stavu SafetyNet…</string>
<string name="safetyNet_no_response">Nelze zkontrolovat SafetyNet. Jste připojeni k Internetu?</string>
<!--Install Fragment-->
<string name="auto_detect">(Auto) %1$s</string>
<string name="cannot_auto_detect">(Nelze zjistit automaticky)</string>
<string name="boot_image_title">Umístění Boot Image</string>
<string name="detect_button">Zjistit</string>
<string name="advanced_settings_title">Pokročilá Nastavení</string>
<string name="keep_force_encryption">Udržet "force encryption"</string>
<string name="keep_dm_verity">Udržet dm-verity</string>
<string name="current_magisk_title">Nainstalovaná verze Magisk: %1$s</string>
<string name="install_magisk_title">Poslední verze Magisk: %1$s</string>
<string name="uninstall">Odinstalovat</string>
<string name="reboot_countdown">Restart za %1$d</string>
<string name="uninstall_magisk_title">Odinstalovat Magisk</string>
<!--Module Fragment-->
<string name="no_info_provided">(Žádné info)</string>
<string name="no_modules_found">Žádný modul nenalezen</string>
<string name="update_file_created">Modul bude aktualizován během příštího restartu</string>
<string name="remove_file_created">Modul bude smazán během příštího restartu</string>
<string name="remove_file_deleted">Modul nebude smazán během příštího restartu</string>
<string name="disable_file_created">Modul bude zakázán během příštího restartu</string>
<string name="disable_file_removed">Modul bude povolen během příštího restartu</string>
<string name="author">Vytvořeno %1$s</string>
<!--Repo Fragment-->
<string name="update_available">Dostupná Aktualizace</string>
<string name="installed">Nainstalováno</string>
<string name="not_installed">Nenainstalováno</string>
<!--Log Fragment-->
<string name="menuSaveToSd">Uložit na SD</string>
<string name="menuReload">Aktualizovat</string>
<string name="menuClearLog">Smazat Log</string>
<string name="logs_cleared">Log byl smazán</string>
<string name="log_is_empty">Log je prázdný</string>
<string name="logs_save_failed">Nezdařilo se Log uložit na SD kartu:</string>
<!--About Activity-->
<string name="about">O aplikaci</string>
<string name="app_developers">Hlavní vývojáři</string>
<string name="app_developers_"><![CDATA[Aplikaci vytvořil <a href="https://github.com/topjohnwu">topjohnwu</a> ve spolupráci s <a href="https://github.com/d8ahazard">Digitalhigh</a> a <a href="https://github.com/dvdandroid">Dvdandroid</a>.]]></string>
<string name="app_changelog">Seznam změn</string>
<string name="translators" />
<string name="app_version">Verze aplikace</string>
<string name="app_source_code">Zdrojový kód</string>
<string name="donation">Příspěvek</string>
<string name="app_translators">Překladatelé aplikace</string>
<string name="support_thread">Vlákno podpory na fóru</string>
<!--Toasts, Dialogs-->
<string name="permissionNotGranted">Tato funkce nebude fungovat bez povolení k zápisu na externí úložiště.</string>
<string name="no_thanks">Ne, díky</string>
<string name="yes">Ano</string>
<string name="ok">OK</string>
<string name="close">Zavřít</string>
<string name="repo_install_title">Instalovat %1$s</string>
<string name="repo_install_msg">Chcete nainstalovat %1$s ?</string>
<string name="download">Stáhnout</string>
<string name="download_file_error">Chyba při Stahování souboru</string>
<string name="install_error">Chyba při Instalaci!</string>
<string name="invalid_zip">Soubor zip není Magisk Modul!!</string>
<string name="reboot">Restart</string>
<string name="zip_process_msg">Zpracování zip souboru …</string>
<string name="downloading_toast">Stahování %1$s</string>
<string name="magisk_update_title">K dispozici je aktualizace Magisk!</string>
<string name="settings_reboot_toast">Restartovat pro použití nastavení</string>
<string name="release_notes">Poznámky k vydání</string>
<string name="repo_cache_cleared">Mezipaměť smazána</string>
<string name="safetyNet_hide_notice">Tato aplikace využívá SafetyNet\nVe výchozím nastavení již ovládaný skrze MagiskHide</string>
<string name="process_error">Chyba při Zpracování</string>
<string name="internal_storage">Zip je uchován v:\n[Interním Úložišti]%1$s</string>
<string name="zip_process_title">Zpracování</string>
<string name="manual_boot_image">Ručně vyberte boot image!</string>
<!--Settings Activity -->
<string name="settings_general_category">Obecné</string>
<string name="settings_dark_theme_title">Tmavý Vzhled</string>
<string name="settings_dark_theme_summary">Povolit tmavý vzhled</string>
<string name="settings_notification_title">Oznámení o Aktualizaci</string>
<string name="settings_notification_summary">Zobrazí oznámení aktualizace, když je k dispozici nová verze</string>
<string name="settings_clear_cache_title">Smazat Uchovanou Mezipaměť</string>
<string name="settings_clear_cache_summary">Smaže informace online použití v Mezipaměti, donutí aplikaci obnovit informace online</string>
<string name="settings_magiskhide_summary">Skryje root (MagiskSU) před různými aplikacemi</string>
<string name="settings_hosts_title">Nesystémová "hosts" data</string>
<string name="settings_hosts_summary">Podpora nesystémových dat "hosts" pro Adblock aplikace</string>
<string name="settings_su_app_adb">Aplikace i ADB</string>
<string name="settings_su_app">Pouze aplikace</string>
<string name="settings_su_adb">Pouze ADB</string>
<string name="settings_su_disable">Zakázáno</string>
<string name="settings_su_request_10">10 sekund</string>
<string name="settings_su_request_20">20 sekund</string>
<string name="settings_su_request_30">30 sekund</string>
<string name="settings_su_request_60">60 sekund</string>
<string name="superuser_access">Přístup Superuser</string>
<string name="auto_response">Automatická Reakce</string>
<string name="request_timeout">Časový limit Požadavku</string>
<string name="superuser_notification">Oznámení Superuser</string>
<string name="request_timeout_summary">%1$s sekund</string>
<string name="settings_development_category">Vývoj aplikace</string>
<string name="settings_developer_logging_title">Povolit pokročilé ladění logování</string>
<string name="settings_developer_logging_summary">Zkuste zapnout toto pro rozsáhlejší logování</string>
<string name="settings_shell_logging_title">Povolit logování pro příkazy shellu</string>
<string name="settings_shell_logging_summary">Logování všech příkazů shellu a jejich výstup</string>
<!--Superuser-->
<string name="su_request_title">Požadavek Superuser</string>
<string name="deny_with_str">Zamítnout%1$s</string>
<string name="deny">Zamítnout</string>
<string name="prompt">Dotaz</string>
<string name="grant">Povolit</string>
<string name="su_warning">Povolí plný přístup k vašemu zařízení.\nZamítněte pokud si nejste jisti!</string>
<string name="forever">Navždy</string>
<string name="once">Jednou</string>
<string name="tenmin">10 min</string>
<string name="twentymin">20 min</string>
<string name="thirtymin">30 min</string>
<string name="sixtymin">60 min</string>
<string name="su_allow_toast">Pro %1$s bylo oprávnění Superuser povoleno</string>
<string name="su_deny_toast">Pro %1$s bylo oprávnění Superuser zamítnuto</string>
<string name="no_apps_found">Zatím zde není žádná aplikace</string>
<string name="su_snack_grant">Superuser oprávnění pro %1$s je povoleno</string>
<string name="su_snack_deny">Superuser oprávnění pro %1$s je zamítnuto</string>
<string name="su_snack_notif_on">Oznámení pro %1$s je povoleno</string>
<string name="su_snack_notif_off">Oznámení pro %1$s je zakázáno</string>
<string name="su_snack_log_on">Logování %1$s je povoleno</string>
<string name="su_snack_log_off">Logování %1$s je zakázáno</string>
<string name="su_snack_revoke">Záznamy oprávnění %1$s jsou smazány</string>
<string name="su_revoke_title">Smazat?</string>
<string name="su_revoke_msg">Smazat záznam ohledně oprávnění pro %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Žádné</string>
<!--Superuser logs-->
<string name="pid">PID:\u0020</string>
<string name="target_uid">Cílové UID:\u0020</string>
<string name="command">Příkaz:\u0020</string>
</resources>

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