mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-10-31 10:40:52 +01:00 
			
		
		
		
	Compare commits
	
		
			461 Commits
		
	
	
		
			manager-v5
			...
			manager-v5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 15ed3e52f2 | ||
|   | 8990919dab | ||
|   | e5638e4b15 | ||
|   | 404c6fac9a | ||
|   | 267395bfa2 | ||
|   | 920fc5ae99 | ||
|   | 92ed0ae51b | ||
|   | 6764a98409 | ||
|   | fd7b5f393a | ||
|   | 2ca528f93f | ||
|   | ce2e6b7d35 | ||
|   | 684c5d225a | ||
|   | b75018b03b | ||
|   | 41499d4b3c | ||
|   | 383c97c303 | ||
|   | 74b54ef371 | ||
|   | bbf7b4db79 | ||
|   | c61f0acab5 | ||
|   | 398af123b2 | ||
|   | 315fa9d7d3 | ||
|   | fb5e8ef40c | ||
|   | e79d764148 | ||
|   | ebbee0dc43 | ||
|   | ed0c16e201 | ||
|   | 209fdf349a | ||
|   | f49f2afacd | ||
|   | 8c6330a3c4 | ||
|   | 337b777125 | ||
|   | 1b756e8d96 | ||
|   | 52d478df1a | ||
|   | 0c782edf21 | ||
|   | e3948d295e | ||
|   | 5f2c742a5c | ||
|   | b30c77aab9 | ||
|   | a5916b9c49 | ||
|   | 453180e30b | ||
|   | 8bd432d391 | ||
|   | c9d3e20aef | ||
|   | d5408d1f09 | ||
|   | f334532aba | ||
|   | be77c09f3d | ||
|   | 7de6a92753 | ||
|   | 36f76f5a14 | ||
|   | b84523d557 | ||
|   | 2c78c415e9 | ||
|   | 79ccb30dd2 | ||
|   | 3c566becf6 | ||
|   | 151ca593af | ||
|   | 4132eacba0 | ||
|   | 06e6151816 | ||
|   | 70277d4edd | ||
|   | d21d2f1a9c | ||
|   | 74a7be996f | ||
|   | 3f38579529 | ||
|   | 4d5a9f6e15 | ||
|   | 41f47acd76 | ||
|   | 821dcaa7c7 | ||
|   | 7135d26419 | ||
|   | f7fd354dce | ||
|   | 0c69a65bc4 | ||
|   | 2f2ca5eab4 | ||
|   | df9c40c035 | ||
|   | 25b67017e4 | ||
|   | bc9c3346f3 | ||
|   | 1db7e19fe8 | ||
|   | 102c03ce2b | ||
|   | ec19eb4455 | ||
|   | 6d9924d50e | ||
|   | 16c4d74274 | ||
|   | e4af5fd36a | ||
|   | 702775493a | ||
|   | b2ae826066 | ||
|   | cc3e9990fa | ||
|   | 271cbddd5e | ||
|   | c1423ca9ad | ||
|   | 74379150a1 | ||
|   | c840a30c30 | ||
|   | ae5277a898 | ||
|   | bffa837825 | ||
|   | b9e7d0faea | ||
|   | 860b08d9ed | ||
|   | 691dc1d49e | ||
|   | 9d6886d367 | ||
|   | 9589b68f5a | ||
|   | 28d88af1af | ||
|   | 8b5acd1849 | ||
|   | 33dc63a7fd | ||
|   | d0a86385b7 | ||
|   | 50a49e2c8c | ||
|   | c60adb113e | ||
|   | aee015e8f6 | ||
|   | bf6af29205 | ||
|   | 329905d472 | ||
|   | 00d450d262 | ||
|   | 2365d1bd20 | ||
|   | 5b385c18e5 | ||
|   | 98c0434ec0 | ||
|   | f318d0a3bc | ||
|   | 27f5b410c0 | ||
|   | 3f55be9676 | ||
|   | b05d2d3a2d | ||
|   | 19af5f9e0b | ||
|   | f37f330670 | ||
|   | 40082d4571 | ||
|   | 00d655f346 | ||
|   | 821726e7c0 | ||
|   | 759e905c3c | ||
|   | 8bf7e42913 | ||
|   | 0dcd073554 | ||
|   | 2fe35d578d | ||
|   | 8d139e156e | ||
|   | 7c2849356a | ||
|   | 0025ffd1c0 | ||
|   | 2ef7146642 | ||
|   | 1b27e69e40 | ||
|   | 8e7b757efd | ||
|   | 1ab543cea1 | ||
|   | a3f86903e4 | ||
|   | c239c305ab | ||
|   | 2e02af994e | ||
|   | 836d9afe17 | ||
|   | 007a352742 | ||
|   | e526e5659e | ||
|   | 4a5227c7bf | ||
|   | c2c151ec4c | ||
|   | 452096e7e4 | ||
|   | 50c2a9859e | ||
|   | 677b667307 | ||
|   | 1adf331268 | ||
|   | 349b3e961b | ||
|   | 96650c06f0 | ||
|   | 26038a0a07 | ||
|   | 6a148b5dd9 | ||
|   | 0e109ef979 | ||
|   | de2285d5e9 | ||
|   | b2483ba437 | ||
|   | a82a5e5a49 | ||
|   | d161a02e71 | ||
|   | d2b6a700b1 | ||
|   | af203cef24 | ||
|   | 673e917e76 | ||
|   | a3bd41db54 | ||
|   | 0d9527921a | ||
|   | f0e4aec0af | ||
|   | b0d65b5edd | ||
|   | 75532ef591 | ||
|   | 9a6d1bd700 | ||
|   | a7ed6c15d3 | ||
|   | 5ee49ba065 | ||
|   | d34bd47bea | ||
|   | f17792380b | ||
|   | c11920110e | ||
|   | ec5a993fea | ||
|   | d250c2cc89 | ||
|   | 767e73f40c | ||
|   | 3f699c9d2f | ||
|   | 50dbd9befd | ||
|   | 760e01bf92 | ||
|   | 543f435b1e | ||
|   | 91337218b3 | ||
|   | afff3c0a49 | ||
|   | a1871e4bc3 | ||
|   | 3aa0294cd4 | ||
|   | 310b266251 | ||
|   | 21b1b5098e | ||
|   | a3a4a5d8a5 | ||
|   | 270536f33c | ||
|   | 66bb433cc6 | ||
|   | bd4ef1a03a | ||
|   | aa2d9a3bf1 | ||
|   | fd6cbb138c | ||
|   | aa75c8e5e4 | ||
|   | c461fc6daa | ||
|   | 96eaa833f5 | ||
|   | 863b13a694 | ||
|   | e6fea4e6dd | ||
|   | 83bfc13056 | ||
|   | bc4f09209b | ||
|   | 967ca17238 | ||
|   | 595c72147c | ||
|   | f3c3b5a649 | ||
|   | 1cd2c5e653 | ||
|   | b2873dd44b | ||
|   | bb80ab4026 | ||
|   | 80cabb338b | ||
|   | 2c69e2c151 | ||
|   | c1dd23f5e0 | ||
|   | f93624a41c | ||
|   | 9f4559a059 | ||
|   | fd05cad303 | ||
|   | d58b06e493 | ||
|   | 2f0b549027 | ||
|   | 87dbd7e541 | ||
|   | 96e5da36be | ||
|   | 43745edac0 | ||
|   | f5ceee547c | ||
|   | b612bce779 | ||
|   | 2e88e5e9c7 | ||
|   | 9a7aa25c90 | ||
|   | c4420fe932 | ||
|   | a5260f3a95 | ||
|   | 47ccf4b1f5 | ||
|   | a356b21895 | ||
|   | 614a36c888 | ||
|   | f520fe36bd | ||
|   | 7273a1c34d | ||
|   | dc45cbce37 | ||
|   | 708d8f75c0 | ||
|   | bd37d90228 | ||
|   | b1ad691464 | ||
|   | f4e7baf31e | ||
|   | c0e60c41f2 | ||
|   | c8dad43e00 | ||
|   | a8f124704d | ||
|   | eed2816491 | ||
|   | a6334b3e35 | ||
|   | 334beebfeb | ||
|   | 13dad848bd | ||
|   | e518f4cef8 | ||
|   | c8fd5da2da | ||
|   | 3a74729ecc | ||
|   | 49c672ac4d | ||
|   | b570cb5b77 | ||
|   | 97bf388471 | ||
|   | 1a32aaea6f | ||
|   | 4635883dec | ||
|   | 3ba6db4a50 | ||
|   | 2f1de25747 | ||
|   | f60fd42ac0 | ||
|   | ecc8f9c792 | ||
|   | e295dfdcf7 | ||
|   | fc42c25390 | ||
|   | 27d5858e06 | ||
|   | e1ef732b60 | ||
|   | 9840b95c21 | ||
|   | a6f8446d81 | ||
|   | c1c844c830 | ||
|   | 389299afd1 | ||
|   | 826543a291 | ||
|   | 4ac83cfded | ||
|   | 64c363ce53 | ||
|   | cca4347bf9 | ||
|   | 3ae3d4926a | ||
|   | 36025d6d9f | ||
|   | e171362e3e | ||
|   | 3e0bf2ae15 | ||
|   | 07aa9f4b8b | ||
|   | b2d9f3fc64 | ||
|   | 5fb3e9167e | ||
|   | 99c74b31be | ||
|   | ce5b13824e | ||
|   | c39170c42e | ||
|   | fd19fbf300 | ||
|   | 166469827f | ||
|   | a34ed538b6 | ||
|   | 5f22d3e055 | ||
|   | fdd700f3e5 | ||
|   | adf930f126 | ||
|   | 05f41928cd | ||
|   | 2ee0829871 | ||
|   | 743560825d | ||
|   | e3d84ac349 | ||
|   | 266c832b30 | ||
|   | f5374a024e | ||
|   | 4956d826fb | ||
|   | f5cc2af5d0 | ||
|   | 5880d4a6ec | ||
|   | ae05dce958 | ||
|   | 9ebe372a9a | ||
|   | e6e04cc5b3 | ||
|   | 12352510fd | ||
|   | 2b3d927937 | ||
|   | a8890740f5 | ||
|   | f60d7ee54b | ||
|   | 896ca2ef6b | ||
|   | c036f6d529 | ||
|   | 6f457c0c59 | ||
|   | 13bf1b27b4 | ||
|   | f742bb1c47 | ||
|   | aa0b9e2db2 | ||
|   | c10076f7ed | ||
|   | bcd92499f2 | ||
|   | b2bb0d4f72 | ||
|   | e140481f14 | ||
|   | 186bd11463 | ||
|   | a0490d6687 | ||
|   | beef740ade | ||
|   | 2ac7786a90 | ||
|   | a3fb5e910f | ||
|   | 319afe86b5 | ||
|   | 762ab66b86 | ||
|   | 0c239a42de | ||
|   | e9322fba26 | ||
|   | 39b6df27b3 | ||
|   | b1ee284e7f | ||
|   | e986332bf2 | ||
|   | 48f9b27381 | ||
|   | 42a6e0dd10 | ||
|   | d4798b02ac | ||
|   | 963edfe8ab | ||
|   | 53237f3ae0 | ||
|   | 64da9281a4 | ||
|   | ab7fd9799d | ||
|   | f6bcc84251 | ||
|   | 35dc3d9df9 | ||
|   | 566714a75d | ||
|   | c92f30b122 | ||
|   | 294ad094c4 | ||
|   | c1a0f520f9 | ||
|   | 773c24b7fc | ||
|   | 8f926c7ca9 | ||
|   | c562cbc2bb | ||
|   | 3fbbb0865a | ||
|   | 7d5f612a48 | ||
|   | 4a5a36440b | ||
|   | 43dd5cfea1 | ||
|   | 7b5fec1842 | ||
|   | 5762ded601 | ||
|   | a3abb86daa | ||
|   | 4f5c656b05 | ||
|   | a31cddbe7b | ||
|   | b4ecd93f1c | ||
|   | 0acc23e058 | ||
|   | cdd5f9b628 | ||
|   | 4c9f5f4655 | ||
|   | b80ba13cb4 | ||
|   | 8260bdc09c | ||
|   | 24f856e02b | ||
|   | 3aa619b928 | ||
|   | 4cb5e98d94 | ||
|   | 272910575e | ||
|   | a15a62f4bc | ||
|   | 53cf11db8c | ||
|   | 01052fbe47 | ||
|   | a5e1e075c7 | ||
|   | 6be32ac688 | ||
|   | b362c0ef38 | ||
|   | bba9969e31 | ||
|   | 007ba24809 | ||
|   | df21539311 | ||
|   | 2592cb6019 | ||
|   | f7df17a7ed | ||
|   | 62f42b72f8 | ||
|   | a1ba4fda6f | ||
|   | 1c06b04c45 | ||
|   | 2ee22fd374 | ||
|   | 4c230d9e61 | ||
|   | 727294fbbe | ||
|   | 478c43969b | ||
|   | 79b5303350 | ||
|   | ce4b742b25 | ||
|   | a9dc15bda5 | ||
|   | ba6387ff5c | ||
|   | 8fa98508b7 | ||
|   | decdbaecf9 | ||
|   | 6d87cf9be0 | ||
|   | 94f434c4a6 | ||
|   | 7ba867c30b | ||
|   | 3424395e10 | ||
|   | 926c7359a2 | ||
|   | ec0af99a2e | ||
|   | b4d948886c | ||
|   | 4d8d79372a | ||
|   | 04a589722c | ||
|   | d4a10e2873 | ||
|   | 4998ad6c7e | ||
|   | a07ca5ff50 | ||
|   | f07e7571ab | ||
|   | 834c16485c | ||
|   | 04a4265ef3 | ||
|   | 0ec473195d | ||
|   | 0bf09256b0 | ||
|   | db8fd2c913 | ||
|   | dbe6e5b3d7 | ||
|   | cc81cd446b | ||
|   | 439c7118f1 | ||
|   | d8154a5815 | ||
|   | 4e3787bc0d | ||
|   | 02e0955924 | ||
|   | a78950e822 | ||
|   | 1ce1a94a35 | ||
|   | 977b6d9f67 | ||
|   | b5e6dbd797 | ||
|   | 833e6688f1 | ||
|   | bc22c9f84f | ||
|   | 2149a7d116 | ||
|   | 29175d2c17 | ||
|   | 803454d5c8 | ||
|   | 36cf32dc42 | ||
|   | 657f4ab303 | ||
|   | ea6552615d | ||
|   | 4bf3287fce | ||
|   | 832c2034c2 | ||
|   | b0aa26e1f1 | ||
|   | e52baeb967 | ||
|   | 8268eb9a83 | ||
|   | 3cc458abd9 | ||
|   | 337b4c4268 | ||
|   | 001f8657f6 | ||
|   | ea884e7fa1 | ||
|   | 1b1394cf5d | ||
|   | 1eef930dbb | ||
|   | 1e175e74ed | ||
|   | 75a46c365e | ||
|   | 8e7b8825f5 | ||
|   | 2ecbca303b | ||
|   | 8195a4d616 | ||
|   | 7ba40f925f | ||
|   | 345cd1795f | ||
|   | 959aaee045 | ||
|   | 53477f0f59 | ||
|   | 5716218f41 | ||
|   | 9df6b9d5c0 | ||
|   | ec46031d36 | ||
|   | 55b84d166a | ||
|   | 34ae8bacec | ||
|   | cb4e5ca0f7 | ||
|   | 0ba45468c4 | ||
|   | 710502784e | ||
|   | 0275a8558d | ||
|   | 58acc75cf6 | ||
|   | 874ababb9f | ||
|   | 3771e6b0cd | ||
|   | 33eaefa966 | ||
|   | cd7e236d57 | ||
|   | 54c0b7c7d5 | ||
|   | a2177daec2 | ||
|   | 628386b453 | ||
|   | b222bfb3e0 | ||
|   | ab199d883d | ||
|   | 356065d1ee | ||
|   | 76e7c5623d | ||
|   | 085fba050a | ||
|   | 295334d3ac | ||
|   | 36124ddca4 | ||
|   | bd6585765e | ||
|   | c325deb4ed | ||
|   | 73bb0b10ee | ||
|   | 72820b162c | ||
|   | 89e5b8d057 | ||
|   | da4f53ebbb | ||
|   | 8458553b74 | ||
|   | 55ecc41d06 | ||
|   | 28fcdf2cbb | ||
|   | 24087679a8 | ||
|   | 5ac6a8cb4a | ||
|   | 668d85d14e | ||
|   | c11a3dc95c | ||
|   | 56f57c20a2 | ||
|   | 240d14779a | ||
|   | 3550d1e61c | ||
|   | 6513ad249c | ||
|   | 50297b1880 | ||
|   | f189b78b9e | ||
|   | 5c0250f495 | ||
|   | 2093f726e9 | ||
|   | 10efe3859d | ||
|   | 6933bcf7bb | ||
|   | 2ea046cd80 | ||
|   | f4097a372b | ||
|   | 87ea2a2bef | 
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,4 +5,8 @@ | ||||
| /build | ||||
| app/release | ||||
| *.hprof | ||||
| app/.externalNativeBuild/ | ||||
| .externalNativeBuild/ | ||||
| src/main/assets | ||||
| public.certificate.x509.pem | ||||
| private.key.pk8 | ||||
| *.apk | ||||
|   | ||||
| @@ -1,6 +1,2 @@ | ||||
| # Magisk Manager  | ||||
| You need to install CMake and NDK to build the zipadjust library for zip preprocessing | ||||
|  | ||||
| ## Pre-built Binaries | ||||
| Busybox (arm and x86) compiled by osm0sis (`libbusybox.so` under `app\src\main\jniLibs`)   | ||||
| Source and more info: [osm0sis' Odds and Ends](https://forum.xda-developers.com/showthread.php?t=2239421) | ||||
| # Magisk Manager | ||||
| This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk). | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -1,63 +0,0 @@ | ||||
| apply plugin: 'com.android.application' | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 26 | ||||
|     buildToolsVersion "26.0.0" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "com.topjohnwu.magisk" | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 26 | ||||
|         versionCode 46 | ||||
|         versionName "5.0.6" | ||||
|         ndk { | ||||
|             moduleName 'zipadjust' | ||||
|             abiFilters 'x86', 'armeabi-v7a' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled true | ||||
|             shrinkResources true | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility JavaVersion.VERSION_1_8 | ||||
|     } | ||||
|     dexOptions { | ||||
|         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 { | ||||
|     implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     implementation 'com.android.support:recyclerview-v7:26.0.0-beta2' | ||||
|     implementation 'com.android.support:cardview-v7:26.0.0-beta2' | ||||
|     implementation 'com.android.support:design:26.0.0-beta2' | ||||
|     implementation 'com.android.support:support-v4:26.0.0-beta2' | ||||
|     implementation 'com.jakewharton:butterknife:8.7.0' | ||||
|     implementation 'com.thoughtbot:expandablerecyclerview:1.4' | ||||
|     implementation 'us.feras.mdv:markdownview:1.1.0' | ||||
|     implementation 'com.madgag.spongycastle:core:1.54.0.0' | ||||
|     implementation 'com.madgag.spongycastle:prov:1.54.0.0' | ||||
|     implementation 'com.madgag.spongycastle:pkix:1.54.0.0' | ||||
|     implementation 'com.google.android.gms:play-services-safetynet:9.0.1' | ||||
|     annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0' | ||||
| } | ||||
| @@ -1,137 +0,0 @@ | ||||
| #!/system/bin/sh | ||||
| ########################################################################################## | ||||
| # | ||||
| # Magisk Uninstaller | ||||
| # by topjohnwu | ||||
| #  | ||||
| # This script can be placed in /cache/magisk_uninstaller.sh | ||||
| # The Magisk main binary will pick up the script, and uninstall itself, following a reboot | ||||
| # This script can also be used in flashable zip with the uninstaller_loader.sh | ||||
| #  | ||||
| # This script will try to do restoration with the following: | ||||
| # 1-1. Find and restore the original stock boot image dump (OTA proof) | ||||
| # 1-2. If 1-1 fails, restore ramdisk from the internal backup | ||||
| #      (ramdisk fully restored, not OTA friendly) | ||||
| # 1-3. If 1-2 fails, it will remove added files in ramdisk, however modified files | ||||
| #      are remained modified, because we have no backups. By doing so, Magisk will  | ||||
| #      not be started at boot, but this isn't actually 100% cleaned up | ||||
| # 2. Remove all Magisk related files | ||||
| #    (The list is LARGE, most likely due to bad decision in early versions | ||||
| #    the latest versions has much less bloat to cleanup) | ||||
| # | ||||
| ########################################################################################## | ||||
|  | ||||
| # Call ui_print_wrap if exists, or else simply use echo | ||||
| # Useful when wrapped in flashable zip | ||||
| ui_print_wrap() { | ||||
|   type ui_print >/dev/null 2>&1 && ui_print "$1" || echo "$1" | ||||
| } | ||||
|  | ||||
| # Call abort if exists, or else show error message and exit | ||||
| # Essential when wrapped in flashable zip | ||||
| abort_wrap() { | ||||
|   type abort >/dev/null 2>&1 | ||||
|   if [ $? -ne 0 ]; then | ||||
|     ui_print_wrap "$1" | ||||
|     exit 1 | ||||
|   else | ||||
|     abort "$1" | ||||
|   fi | ||||
| } | ||||
|  | ||||
| if [ ! -d $MAGISKBIN -o ! -f $MAGISKBIN/magiskboot -o ! -f $MAGISKBIN/util_functions.sh ]; then | ||||
|   ui_print_wrap "! Cannot find $MAGISKBIN" | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| [ -z $BOOTMODE ] && BOOTMODE=false | ||||
|  | ||||
| MAGISKBIN=/data/magisk | ||||
| CHROMEDIR=$MAGISKBIN/chromeos | ||||
|  | ||||
| # Default permissions | ||||
| umask 022 | ||||
|  | ||||
| # Load utility functions | ||||
| . $MAGISKBIN/util_functions.sh | ||||
|  | ||||
| # Find the boot image | ||||
| find_boot_image | ||||
| [ -z $BOOTIMAGE ] && abort "! Unable to detect boot image" | ||||
|  | ||||
| ui_print_wrap "- Found Boot Image: $BOOTIMAGE" | ||||
|  | ||||
| cd $MAGISKBIN | ||||
|  | ||||
| ui_print_wrap "- Unpacking boot image" | ||||
| ./magiskboot --unpack "$BOOTIMAGE" | ||||
| [ $? -ne 0 ] && abort_wrap "! Unable to unpack boot image" | ||||
|  | ||||
| # Update our previous backup to new format if exists | ||||
| if [ -f /data/stock_boot.img ]; then | ||||
|   SHA1=`./magiskboot --sha1 /data/stock_boot.img | tail -n 1` | ||||
|   STOCKDUMP=/data/stock_boot_${SHA1}.img | ||||
|   mv /data/stock_boot.img $STOCKDUMP | ||||
|   ./magiskboot --compress $STOCKDUMP | ||||
| fi | ||||
|  | ||||
| # Detect boot image state | ||||
| ./magiskboot --cpio-test ramdisk.cpio | ||||
| case $? in | ||||
|   0 )  # Stock boot | ||||
|     ui_print_wrap "- Stock boot image detected!" | ||||
|     ui_print_wrap "! Magisk is not installed!" | ||||
|     exit | ||||
|     ;; | ||||
|   1 )  # Magisk patched | ||||
|     ui_print_wrap "- Magisk patched image detected!" | ||||
|     # Find SHA1 of stock boot image | ||||
|     if [ -z $SHA1 ]; then | ||||
|       ./magiskboot --cpio-extract ramdisk.cpio init.magisk.rc init.magisk.rc.old | ||||
|       SHA1=`grep_prop "# STOCKSHA1" init.magisk.rc.old` | ||||
|       rm -f init.magisk.rc.old | ||||
|     fi | ||||
|     [ ! -z $SHA1 ] && STOCKDUMP=/data/stock_boot_${SHA1}.img | ||||
|     if [ -f ${STOCKDUMP}.gz ]; then | ||||
|       ui_print_wrap "- Boot image backup found!" | ||||
|       ./magiskboot --decompress ${STOCKDUMP}.gz stock_boot.img | ||||
|     else | ||||
|       ui_print_wrap "! Boot image backup unavailable" | ||||
|       ui_print_wrap "- Restoring ramdisk with backup" | ||||
|       ./magiskboot --cpio-restore ramdisk.cpio | ||||
|       ./magiskboot --repack $BOOTIMAGE stock_boot.img | ||||
|     fi | ||||
|     ;; | ||||
|   2 ) # Other patched | ||||
|     ui_print_wrap "! Boot image patched by other programs!" | ||||
|     abort_wrap "! Cannot uninstall with this uninstaller" | ||||
|     ;; | ||||
| esac | ||||
|  | ||||
| # Sign chromeos boot | ||||
| if [ -f chromeos ]; then | ||||
|   echo > empty | ||||
|  | ||||
|   LD_LIBRARY_PATH=$SYSTEMLIB $CHROMEDIR/futility vbutil_kernel --pack stock_boot.img.signed \ | ||||
|   --keyblock $CHROMEDIR/kernel.keyblock --signprivate $CHROMEDIR/kernel_data_key.vbprivk \ | ||||
|   --version 1 --vmlinuz stock_boot.img --config empty --arch arm --bootloader empty --flags 0x1 | ||||
|  | ||||
|   rm -f empty stock_boot.img | ||||
|   mv stock_boot.img.signed stock_boot.img | ||||
| fi | ||||
|  | ||||
| ui_print_wrap "- Flashing stock/reverted image" | ||||
| if [ -L "$BOOTIMAGE" ]; then | ||||
|   dd if=stock_boot.img of="$BOOTIMAGE" bs=4096 | ||||
| else | ||||
|   cat stock_boot.img /dev/zero | dd of="$BOOTIMAGE" bs=4096 >/dev/null 2>&1 | ||||
| fi | ||||
| rm -f stock_boot.img | ||||
|  | ||||
| ui_print_wrap "- 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/magisk_debug.log \ | ||||
|         /data/busybox /data/magisk /data/custom_ramdisk_patch.sh 2>/dev/null | ||||
|  | ||||
| $BOOTMODE && reboot | ||||
										
											Binary file not shown.
										
									
								
							| @@ -1,27 +0,0 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD | ||||
| VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g | ||||
| VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE | ||||
| AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe | ||||
| Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET | ||||
| MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G | ||||
| A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p | ||||
| ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI | ||||
| hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM | ||||
| qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 | ||||
| wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy | ||||
| 4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU | ||||
| RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s | ||||
| zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw | ||||
| HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ | ||||
| AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE | ||||
| CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH | ||||
| QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG | ||||
| CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud | ||||
| EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa | ||||
| J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y | ||||
| LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe | ||||
| +ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX | ||||
| 31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr | ||||
| sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,192 +0,0 @@ | ||||
| ########################################################################################## | ||||
| #  | ||||
| # Magisk General Utility Functions | ||||
| # by topjohnwu | ||||
| #  | ||||
| # Used in flash_script.sh, addon.d.sh, magisk module installers, and uninstaller | ||||
| #  | ||||
| ########################################################################################## | ||||
|  | ||||
| get_outfd() { | ||||
|   readlink /proc/$$/fd/$OUTFD 2>/dev/null | grep /tmp >/dev/null | ||||
|   if [ "$?" -eq "0" ]; then | ||||
|     OUTFD=0 | ||||
|  | ||||
|     for FD in `ls /proc/$$/fd`; do | ||||
|       readlink /proc/$$/fd/$FD 2>/dev/null | grep pipe >/dev/null | ||||
|       if [ "$?" -eq "0" ]; then | ||||
|         ps | grep " 3 $FD " | grep -v grep >/dev/null | ||||
|         if [ "$?" -eq "0" ]; then | ||||
|           OUTFD=$FD | ||||
|           break | ||||
|         fi | ||||
|       fi | ||||
|     done | ||||
|   fi | ||||
| } | ||||
|  | ||||
| ui_print() { | ||||
|   if $BOOTMODE; then | ||||
|     echo "$1" | ||||
|   else  | ||||
|     echo -n -e "ui_print $1\n" >> /proc/self/fd/$OUTFD | ||||
|     echo -n -e "ui_print\n" >> /proc/self/fd/$OUTFD | ||||
|   fi | ||||
| } | ||||
|  | ||||
| getvar() { | ||||
|   local VARNAME=$1 | ||||
|   local VALUE=$(eval echo \$"$VARNAME"); | ||||
|   for FILE in /dev/.magisk /data/.magisk /cache/.magisk /system/.magisk; do | ||||
|     if [ -z "$VALUE" ]; then | ||||
|       LINE=$(cat $FILE 2>/dev/null | grep "$VARNAME=") | ||||
|       if [ ! -z "$LINE" ]; then | ||||
|         VALUE=${LINE#*=} | ||||
|       fi | ||||
|     fi | ||||
|   done | ||||
|   eval $VARNAME=\$VALUE | ||||
| } | ||||
|  | ||||
| find_boot_image() { | ||||
|   if [ -z "$BOOTIMAGE" ]; then | ||||
|     for BLOCK in boot_a BOOT_A kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do | ||||
|       BOOTIMAGE=`ls /dev/block/by-name/$BLOCK || ls /dev/block/platform/*/by-name/$BLOCK || ls /dev/block/platform/*/*/by-name/$BLOCK` 2>/dev/null | ||||
|       [ ! -z $BOOTIMAGE ] && break | ||||
|     done | ||||
|   fi | ||||
|   # Recovery fallback | ||||
|   if [ -z "$BOOTIMAGE" ]; then | ||||
|     for FSTAB in /etc/*fstab*; do | ||||
|       BOOTIMAGE=`grep -E '\b/boot\b' $FSTAB | grep -v "#" | grep -oE '/dev/[a-zA-Z0-9_./-]*'` | ||||
|       [ ! -z $BOOTIMAGE ] && break | ||||
|     done | ||||
|   fi | ||||
|   [ -L "$BOOTIMAGE" ] && BOOTIMAGE=`readlink $BOOTIMAGE` | ||||
| } | ||||
|  | ||||
| is_mounted() { | ||||
|   if [ ! -z "$2" ]; then | ||||
|     cat /proc/mounts | grep $1 | grep $2, >/dev/null | ||||
|   else | ||||
|     cat /proc/mounts | grep $1 >/dev/null | ||||
|   fi | ||||
|   return $? | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| remove_system_su() { | ||||
|   if [ -f /system/bin/su -o -f /system/xbin/su ] && [ ! -f /su/bin/su ]; then | ||||
|     ui_print "! System installed root detected, mount rw :(" | ||||
|     mount -o rw,remount /system | ||||
|     # SuperSU | ||||
|     if [ -e /system/bin/.ext/.su ]; then | ||||
|       mv -f /system/bin/app_process32_original /system/bin/app_process32 2>/dev/null | ||||
|       mv -f /system/bin/app_process64_original /system/bin/app_process64 2>/dev/null | ||||
|       mv -f /system/bin/install-recovery_original.sh /system/bin/install-recovery.sh 2>/dev/null | ||||
|       cd /system/bin | ||||
|       if [ -e app_process64 ]; then | ||||
|         ln -sf app_process64 app_process | ||||
|       else | ||||
|         ln -sf app_process32 app_process | ||||
|       fi | ||||
|     fi | ||||
|     rm -rf /system/.pin /system/bin/.ext /system/etc/.installed_su_daemon /system/etc/.has_su_daemon \ | ||||
|     /system/xbin/daemonsu /system/xbin/su /system/xbin/sugote /system/xbin/sugote-mksh /system/xbin/supolicy \ | ||||
|     /system/bin/app_process_init /system/bin/su /cache/su /system/lib/libsupol.so /system/lib64/libsupol.so \ | ||||
|     /system/su.d /system/etc/install-recovery.sh /system/etc/init.d/99SuperSUDaemon /cache/install-recovery.sh \ | ||||
|     /system/.supersu /cache/.supersu /data/.supersu \ | ||||
|     /system/app/Superuser.apk /system/app/SuperSU /cache/Superuser.apk  2>/dev/null | ||||
|   fi | ||||
| } | ||||
|  | ||||
| api_level_arch_detect() { | ||||
|   API=`grep_prop ro.build.version.sdk` | ||||
|   ABI=`grep_prop ro.product.cpu.abi | cut -c-3` | ||||
|   ABI2=`grep_prop ro.product.cpu.abi2 | cut -c-3` | ||||
|   ABILONG=`grep_prop ro.product.cpu.abi` | ||||
|  | ||||
|   ARCH=arm | ||||
|   IS64BIT=false | ||||
|   if [ "$ABI" = "x86" ]; then ARCH=x86; fi; | ||||
|   if [ "$ABI2" = "x86" ]; then ARCH=x86; fi; | ||||
|   if [ "$ABILONG" = "arm64-v8a" ]; then ARCH=arm64; IS64BIT=true; fi; | ||||
|   if [ "$ABILONG" = "x86_64" ]; then ARCH=x64; IS64BIT=true; fi; | ||||
| } | ||||
|  | ||||
| recovery_actions() { | ||||
|   # TWRP bug fix | ||||
|   mount -o bind /dev/urandom /dev/random | ||||
|   # Temporarily block out all custom recovery binaries/libs | ||||
|   mv /sbin /sbin_tmp | ||||
|   # Add all possible library paths | ||||
|   OLD_LD_PATH=$LD_LIBRARY_PATH | ||||
|   $IS64BIT && export LD_LIBRARY_PATH=/system/lib64:/system/vendor/lib64 || export LD_LIBRARY_PATH=/system/lib:/system/vendor/lib | ||||
| } | ||||
|  | ||||
| recovery_cleanup() { | ||||
|   mv /sbin_tmp /sbin | ||||
|   # Clear LD_LIBRARY_PATH | ||||
|   export LD_LIBRARY_PATH=$OLD_LD_PATH | ||||
|   ui_print "- Unmounting partitions" | ||||
|   umount -l /system | ||||
|   umount -l /vendor 2>/dev/null | ||||
|   umount -l /dev/random | ||||
| } | ||||
|  | ||||
| abort() { | ||||
|   ui_print "$1" | ||||
|   mv /sbin_tmp /sbin 2>/dev/null | ||||
|   exit 1 | ||||
| } | ||||
|  | ||||
| set_perm() { | ||||
|   chown $2:$3 $1 || exit 1 | ||||
|   chmod $4 $1 || exit 1 | ||||
|   if [ ! -z $5 ]; then | ||||
|     chcon $5 $1 2>/dev/null | ||||
|   else | ||||
|     chcon 'u:object_r:system_file:s0' $1 2>/dev/null | ||||
|   fi | ||||
| } | ||||
|  | ||||
| set_perm_recursive() { | ||||
|   find $1 -type d 2>/dev/null | while read dir; do | ||||
|     set_perm $dir $2 $3 $4 $6 | ||||
|   done | ||||
|   find $1 -type f 2>/dev/null | while read file; do | ||||
|     set_perm $file $2 $3 $5 $6 | ||||
|   done | ||||
| } | ||||
|  | ||||
| mktouch() { | ||||
|   mkdir -p ${1%/*} | ||||
|   if [ -z "$2" ]; then | ||||
|     touch $1 | ||||
|   else | ||||
|     echo $2 > $1 | ||||
|   fi | ||||
|   chmod 644 $1 | ||||
| } | ||||
|  | ||||
| request_size_check() { | ||||
|   reqSizeM=`du -s $1 | cut -f1` | ||||
|   reqSizeM=$((reqSizeM / 1024 + 1)) | ||||
| } | ||||
|  | ||||
| image_size_check() { | ||||
|   SIZE="`$MAGISKBIN/magisk --imgsize $IMG`" | ||||
|   curUsedM=`echo "$SIZE" | cut -d" " -f1` | ||||
|   curSizeM=`echo "$SIZE" | cut -d" " -f2` | ||||
|   curFreeM=$((curSizeM - curUsedM)) | ||||
| } | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 73 KiB | 
| @@ -1,133 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.text.Html; | ||||
| import android.text.Spanned; | ||||
| import android.text.TextUtils; | ||||
| import android.text.method.LinkMovementMethod; | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.components.AboutCardRow; | ||||
| import com.topjohnwu.magisk.components.Activity; | ||||
| import com.topjohnwu.magisk.components.AlertDialogBuilder; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class AboutActivity extends Activity { | ||||
|  | ||||
|     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"; | ||||
|  | ||||
|     @BindView(R.id.toolbar) Toolbar toolbar; | ||||
|     @BindView(R.id.app_version_info) AboutCardRow appVersionInfo; | ||||
|     @BindView(R.id.app_changelog) AboutCardRow appChangelog; | ||||
|     @BindView(R.id.app_developers) AboutCardRow appDevelopers; | ||||
|     @BindView(R.id.app_translators) AboutCardRow appTranslators; | ||||
|     @BindView(R.id.app_source_code) AboutCardRow appSourceCode; | ||||
|     @BindView(R.id.support_thread) AboutCardRow supportThread; | ||||
|     @BindView(R.id.donation) AboutCardRow donation; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         if (getApplicationContext().isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_Transparent_Dark); | ||||
|         } | ||||
|         setContentView(R.layout.activity_about); | ||||
|         ButterKnife.bind(this); | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|         toolbar.setNavigationOnClickListener(view -> finish()); | ||||
|  | ||||
|         ActionBar ab = getSupportActionBar(); | ||||
|         if (ab != null) { | ||||
|             ab.setTitle(R.string.about); | ||||
|             ab.setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|  | ||||
|         appVersionInfo.setSummary(BuildConfig.VERSION_NAME); | ||||
|  | ||||
|         String changes = null; | ||||
|         try (InputStream is = getAssets().open("changelog.html")) { | ||||
|             int size = is.available(); | ||||
|  | ||||
|             byte[] buffer = new byte[size]; | ||||
|             is.read(buffer); | ||||
|  | ||||
|             changes = new String(buffer); | ||||
|         } catch (IOException ignored) { | ||||
|         } | ||||
|  | ||||
|         appChangelog.removeSummary(); | ||||
|         if (changes == null) { | ||||
|             appChangelog.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             Spanned result; | ||||
|             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { | ||||
|                 result = Html.fromHtml(changes, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); | ||||
|             } else { | ||||
|                 result = Html.fromHtml(changes); | ||||
|             } | ||||
|             appChangelog.setOnClickListener(v -> { | ||||
|                 AlertDialog d = new AlertDialogBuilder(this) | ||||
|                         .setTitle(R.string.app_changelog) | ||||
|                         .setMessage(result) | ||||
|                         .setPositiveButton(android.R.string.ok, null) | ||||
|                         .show(); | ||||
|  | ||||
|                 //noinspection ConstantConditions | ||||
|                 ((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         appDevelopers.removeSummary(); | ||||
|         appDevelopers.setOnClickListener(view -> { | ||||
|             Spanned result; | ||||
|             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { | ||||
|                 result = Html.fromHtml(getString(R.string.app_developers_), Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); | ||||
|             } else { | ||||
|                 result = Html.fromHtml(getString(R.string.app_developers_)); | ||||
|             } | ||||
|             AlertDialog d = new AlertDialogBuilder(this) | ||||
|                     .setTitle(R.string.app_developers) | ||||
|                     .setMessage(result) | ||||
|                     .setPositiveButton(android.R.string.ok, null) | ||||
|                     .create(); | ||||
|  | ||||
|             d.show(); | ||||
|             //noinspection ConstantConditions | ||||
|             ((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|         }); | ||||
|  | ||||
|         String translators = getString(R.string.translators); | ||||
|         if (TextUtils.isEmpty(translators)) { | ||||
|             appTranslators.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             appTranslators.setSummary(translators); | ||||
|         } | ||||
|  | ||||
|         appSourceCode.removeSummary(); | ||||
|         appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(SOURCE_CODE_URL)))); | ||||
|  | ||||
|         supportThread.removeSummary(); | ||||
|         supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(XDA_THREAD)))); | ||||
|  | ||||
|         donation.removeSummary(); | ||||
|         donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)))); | ||||
|  | ||||
|         setFloating(); | ||||
|     } | ||||
|  | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,220 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.Application; | ||||
| import android.app.NotificationChannel; | ||||
| import android.app.NotificationManager; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.os.Build; | ||||
| import android.os.Handler; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.text.TextUtils; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.database.SuDatabaseHelper; | ||||
| import com.topjohnwu.magisk.module.Module; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
| import com.topjohnwu.magisk.utils.CallbackEvent; | ||||
| import com.topjohnwu.magisk.utils.SafetyNetHelper; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.ValueSortedMap; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.List; | ||||
|  | ||||
| public class MagiskManager extends Application { | ||||
|  | ||||
|     public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk"; | ||||
|     public static final String TMP_FOLDER_PATH = "/dev/tmp"; | ||||
|     public static final String MAGISK_PATH = "/magisk"; | ||||
|     public static final String UNINSTALLER = "magisk_uninstaller.sh"; | ||||
|     public static final String UTIL_FUNCTIONS= "util_functions.sh"; | ||||
|     public static final String INTENT_SECTION = "section"; | ||||
|     public static final String BUSYBOX_VERSION = "1.26.2"; | ||||
|     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"; | ||||
|  | ||||
|     // 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<>(); | ||||
|  | ||||
|     // Info | ||||
|     public String magiskVersionString; | ||||
|     public int magiskVersionCode = -1; | ||||
|     public String remoteMagiskVersionString; | ||||
|     public int remoteMagiskVersionCode = -1; | ||||
|     public String magiskLink; | ||||
|     public String releaseNoteLink; | ||||
|     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; | ||||
|  | ||||
|     // Data | ||||
|     public ValueSortedMap<String, Repo> repoMap; | ||||
|     public ValueSortedMap<String, Module> moduleMap; | ||||
|     public List<String> blockList; | ||||
|     public List<ApplicationInfo> appList; | ||||
|     public List<String> magiskHideList; | ||||
|  | ||||
|     // Configurations | ||||
|     public static boolean shellLogging; | ||||
|     public static boolean devLogging; | ||||
|  | ||||
|     public boolean magiskHide; | ||||
|     public boolean isDarkTheme; | ||||
|     public boolean updateNotification; | ||||
|     public boolean suReauth; | ||||
|     public int suRequestTimeout; | ||||
|     public int suLogTimeout = 14; | ||||
|     public int suAccessState; | ||||
|     public int multiuserMode; | ||||
|     public int suResponseType; | ||||
|     public int suNotificationType; | ||||
|     public int suNamespaceMode; | ||||
|  | ||||
|     // Global resources | ||||
|     public SharedPreferences prefs; | ||||
|     public SuDatabaseHelper suDB; | ||||
|  | ||||
|     private static Handler mHandler = new Handler(); | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
|         prefs = PreferenceManager.getDefaultSharedPreferences(this); | ||||
|     } | ||||
|  | ||||
|     public void toast(String msg, int duration) { | ||||
|         mHandler.post(() -> Toast.makeText(this, msg, duration).show()); | ||||
|     } | ||||
|  | ||||
|     public void toast(int resId, int duration) { | ||||
|         mHandler.post(() -> Toast.makeText(this, resId, duration).show()); | ||||
|     } | ||||
|  | ||||
|     public void init() { | ||||
|         isDarkTheme = prefs.getBoolean("dark_theme", false); | ||||
|         if (BuildConfig.DEBUG) { | ||||
|             devLogging = prefs.getBoolean("developer_logging", false); | ||||
|             shellLogging = prefs.getBoolean("shell_logging", false); | ||||
|         } else { | ||||
|             devLogging = false; | ||||
|             shellLogging = false; | ||||
|         } | ||||
|         magiskHide = prefs.getBoolean("magiskhide", true); | ||||
|         updateNotification = prefs.getBoolean("notification", true); | ||||
|         initSU(); | ||||
|         // Always start a new root shell manually, just for safety | ||||
|         Shell.init(); | ||||
|         updateMagiskInfo(); | ||||
|         // Initialize busybox | ||||
|         File busybox = new File(getApplicationInfo().dataDir + "/busybox/busybox"); | ||||
|         if (!busybox.exists() || !TextUtils.equals(prefs.getString("busybox_version", ""), BUSYBOX_VERSION)) { | ||||
|             busybox.getParentFile().mkdirs(); | ||||
|             Shell.su( | ||||
|                     "cp -f " + new File(getApplicationInfo().nativeLibraryDir, "libbusybox.so") + " " + busybox, | ||||
|                     "chmod -R 755 " + busybox.getParent(), | ||||
|                     busybox + " --install -s " + busybox.getParent() | ||||
|             ); | ||||
|         } | ||||
|         // Initialize prefs | ||||
|         prefs.edit() | ||||
|                 .putBoolean("dark_theme", isDarkTheme) | ||||
|                 .putBoolean("magiskhide", magiskHide) | ||||
|                 .putBoolean("notification", updateNotification) | ||||
|                 .putBoolean("hosts", new File("/magisk/.core/hosts").exists()) | ||||
|                 .putBoolean("disable", Utils.itemExist(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("busybox_version", BUSYBOX_VERSION) | ||||
|                 .apply(); | ||||
|         // Add busybox to PATH | ||||
|         Shell.su("PATH=$PATH:" + busybox.getParent()); | ||||
|  | ||||
|         // 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); | ||||
|             ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void initSUConfig() { | ||||
|         suDB = new SuDatabaseHelper(this); | ||||
|         suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10); | ||||
|         suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0); | ||||
|         suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1); | ||||
|         suReauth = prefs.getBoolean("su_reauth", false); | ||||
|     } | ||||
|  | ||||
|     public void initSU() { | ||||
|         // Create the app data directory, so su binary can work properly | ||||
|         new File(getApplicationInfo().dataDir).mkdirs(); | ||||
|  | ||||
|         initSUConfig(); | ||||
|  | ||||
|         List<String> ret = Shell.sh("su -v"); | ||||
|         if (Utils.isValidShellResponse(ret)) { | ||||
|             suVersion = ret.get(0); | ||||
|             isSuClient = suVersion.toUpperCase().contains("MAGISK"); | ||||
|         } | ||||
|         if (isSuClient) { | ||||
|             suAccessState = suDB.getSettings(SuDatabaseHelper.ROOT_ACCESS, 3); | ||||
|             multiuserMode = suDB.getSettings(SuDatabaseHelper.MULTIUSER_MODE, 0); | ||||
|             suNamespaceMode = suDB.getSettings(SuDatabaseHelper.MNT_NS, 1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void updateMagiskInfo() { | ||||
|         List<String> ret; | ||||
|         ret = Shell.sh("magisk -v"); | ||||
|         if (!Utils.isValidShellResponse(ret)) { | ||||
|             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 " + DISABLE_INDICATION_PROP); | ||||
|         try { | ||||
|             disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0; | ||||
|         } catch (NumberFormatException e) { | ||||
|             disabled = false; | ||||
|         } | ||||
|         ret = Shell.sh("getprop " + MAGISKHIDE_PROP); | ||||
|         try { | ||||
|             magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0; | ||||
|         } catch (NumberFormatException e) { | ||||
|             magiskHide = true; | ||||
|         } | ||||
|          | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,216 +0,0 @@ | ||||
| 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; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 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.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 butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import butterknife.Unbinder; | ||||
|  | ||||
| public class ReposFragment extends Fragment implements CallbackEvent.Listener<Void> { | ||||
|  | ||||
|     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; | ||||
|  | ||||
|     @Override | ||||
|     public void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setHasOptionsMenu(true); | ||||
|     } | ||||
|  | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         View view = inflater.inflate(R.layout.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); | ||||
|  | ||||
|         mSwipeRefreshLayout.setRefreshing(true); | ||||
|  | ||||
|         mSwipeRefreshLayout.setOnRefreshListener(() -> { | ||||
|             recyclerView.setVisibility(View.GONE); | ||||
|             new LoadRepos(getActivity()).exec(); | ||||
|         }); | ||||
|  | ||||
|         if (getApplication().repoLoadDone.isTriggered) { | ||||
|             reloadRepos(); | ||||
|             updateUI(); | ||||
|         } | ||||
|  | ||||
|         searchListener = new SearchView.OnQueryTextListener() { | ||||
|             @Override | ||||
|             public boolean onQueryTextSubmit(String query) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public boolean onQueryTextChange(String newText) { | ||||
|                 new FilterApps().exec(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 | ||||
|     public void onDestroyView() { | ||||
|         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(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,234 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.preference.ListPreference; | ||||
| import android.preference.PreferenceCategory; | ||||
| import android.preference.PreferenceFragment; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.preference.PreferenceScreen; | ||||
| import android.preference.SwitchPreference; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.asyncs.MagiskHide; | ||||
| 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.Utils; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class SettingsActivity extends Activity { | ||||
|  | ||||
|     @BindView(R.id.toolbar) Toolbar toolbar; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         if (getApplicationContext().isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_Transparent_Dark); | ||||
|         } | ||||
|  | ||||
|         setContentView(R.layout.activity_settings); | ||||
|         ButterKnife.bind(this); | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|  | ||||
|         toolbar.setNavigationOnClickListener(view -> finish()); | ||||
|  | ||||
|         ActionBar ab = getSupportActionBar(); | ||||
|         if (ab != null) { | ||||
|             ab.setTitle(R.string.settings); | ||||
|             ab.setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|  | ||||
|         setFloating(); | ||||
|  | ||||
|         if (savedInstanceState == null) { | ||||
|             getFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class SettingsFragment extends PreferenceFragment | ||||
|             implements SharedPreferences.OnSharedPreferenceChangeListener { | ||||
|  | ||||
|         private SharedPreferences prefs; | ||||
|         private PreferenceScreen prefScreen; | ||||
|  | ||||
|         private ListPreference suAccess, autoRes, suNotification, requestTimeout, multiuserMode, namespaceMode; | ||||
|         private MagiskManager magiskManager; | ||||
|  | ||||
|         @Override | ||||
|         public void onCreate(Bundle savedInstanceState) { | ||||
|             super.onCreate(savedInstanceState); | ||||
|             addPreferencesFromResource(R.xml.app_settings); | ||||
|             prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|             prefScreen = getPreferenceScreen(); | ||||
|             magiskManager = Utils.getMagiskManager(getActivity()); | ||||
|  | ||||
|             PreferenceCategory magiskCategory = (PreferenceCategory) findPreference("magisk"); | ||||
|             PreferenceCategory suCategory = (PreferenceCategory) findPreference("superuser"); | ||||
|             PreferenceCategory developer = (PreferenceCategory) findPreference("developer"); | ||||
|  | ||||
|             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"); | ||||
|  | ||||
|             setSummary(); | ||||
|  | ||||
|             // Disable dangerous settings in user mode if selected owner manage | ||||
|             if (getActivity().getApplicationInfo().uid > 99999) { | ||||
|                 prefScreen.removePreference(magiskCategory); | ||||
|                 prefScreen.removePreference(suCategory); | ||||
|             } | ||||
|  | ||||
|             // 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; | ||||
|             }); | ||||
|  | ||||
|             if (!BuildConfig.DEBUG) { | ||||
|                 prefScreen.removePreference(developer); | ||||
|             } | ||||
|  | ||||
|             if (!Shell.rootAccess()) { | ||||
|                 prefScreen.removePreference(magiskCategory); | ||||
|                 prefScreen.removePreference(suCategory); | ||||
|             } else { | ||||
|                 if (!magiskManager.isSuClient) { | ||||
|                     prefScreen.removePreference(suCategory); | ||||
|                 } | ||||
|                 if (magiskManager.magiskVersionCode < 1300) { | ||||
|                     prefScreen.removePreference(magiskCategory); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onResume() { | ||||
|             super.onResume(); | ||||
|             prefs.registerOnSharedPreferenceChangeListener(this); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onPause() { | ||||
|             super.onPause(); | ||||
|             prefs.unregisterOnSharedPreferenceChangeListener(this); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { | ||||
|             Logger.dev("Settings: Prefs change " + key); | ||||
|             boolean enabled; | ||||
|  | ||||
|             switch (key) { | ||||
|                 case "dark_theme": | ||||
|                     enabled = prefs.getBoolean("dark_theme", false); | ||||
|                     if (magiskManager.isDarkTheme != enabled) { | ||||
|                         magiskManager.isDarkTheme = enabled; | ||||
|                         magiskManager.reloadMainActivity.trigger(); | ||||
|                         getActivity().recreate(); | ||||
|                     } | ||||
|                     break; | ||||
|                 case "disable": | ||||
|                     enabled = prefs.getBoolean("disable", false); | ||||
|                     if (enabled) { | ||||
|                         Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE); | ||||
|                     } else { | ||||
|                         Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE); | ||||
|                     } | ||||
|                     Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show(); | ||||
|                     break; | ||||
|                 case "magiskhide": | ||||
|                     enabled = prefs.getBoolean("magiskhide", false); | ||||
|                     if (enabled) { | ||||
|                         if (!magiskManager.isSuClient) { | ||||
|                             new AlertDialogBuilder(getActivity()) | ||||
|                                     .setTitle(R.string.no_magisksu_title) | ||||
|                                     .setMessage(R.string.no_magisksu_msg) | ||||
|                                     .setPositiveButton(R.string.understand, (dialog, which) -> new MagiskHide().enable()) | ||||
|                                     .setCancelable(false) | ||||
|                                     .show(); | ||||
|                         } else { | ||||
|                             new MagiskHide().enable(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         new MagiskHide().disable(); | ||||
|                     } | ||||
|                     break; | ||||
|                 case "hosts": | ||||
|                     enabled = prefs.getBoolean("hosts", false); | ||||
|                     if (enabled) { | ||||
|                         Shell.su_async(null, | ||||
|                                 "cp -af /system/etc/hosts /magisk/.core/hosts", | ||||
|                                 "mount -o bind /magisk/.core/hosts /system/etc/hosts"); | ||||
|                     } else { | ||||
|                         Shell.su_async(null, | ||||
|                                 "umount -l /system/etc/hosts", | ||||
|                                 "rm -f /magisk/.core/hosts"); | ||||
|                     } | ||||
|                     break; | ||||
|                 case "su_access": | ||||
|                     magiskManager.suAccessState = Utils.getPrefsInt(prefs, "su_access", 3); | ||||
|                     magiskManager.suDB.setSettings(SuDatabaseHelper.ROOT_ACCESS, magiskManager.suAccessState); | ||||
|                     break; | ||||
|                 case "multiuser_mode": | ||||
|                     magiskManager.multiuserMode = Utils.getPrefsInt(prefs, "multiuser_mode", 0); | ||||
|                     magiskManager.suDB.setSettings(SuDatabaseHelper.MULTIUSER_MODE, magiskManager.multiuserMode); | ||||
|                     break; | ||||
|                 case "mnt_ns": | ||||
|                     magiskManager.suNamespaceMode = Utils.getPrefsInt(prefs, "mnt_ns", 1); | ||||
|                     magiskManager.suDB.setSettings(SuDatabaseHelper.MNT_NS, magiskManager.suNamespaceMode); | ||||
|                     break; | ||||
|                 case "su_request_timeout": | ||||
|                     magiskManager.suRequestTimeout = Utils.getPrefsInt(prefs, "su_request_timeout", 10); | ||||
|                     break; | ||||
|                 case "su_auto_response": | ||||
|                     magiskManager.suResponseType = Utils.getPrefsInt(prefs, "su_auto_response", 0); | ||||
|                     break; | ||||
|                 case "su_notification": | ||||
|                     magiskManager.suNotificationType = Utils.getPrefsInt(prefs, "su_notification", 1); | ||||
|                     break; | ||||
|                 case "developer_logging": | ||||
|                     MagiskManager.devLogging = prefs.getBoolean("developer_logging", false); | ||||
|                     break; | ||||
|                 case "shell_logging": | ||||
|                     MagiskManager.shellLogging = prefs.getBoolean("shell_logging", false); | ||||
|                     break; | ||||
|             } | ||||
|             setSummary(); | ||||
|         } | ||||
|  | ||||
|         private void setSummary() { | ||||
|             suAccess.setSummary(getResources() | ||||
|                     .getStringArray(R.array.su_access)[magiskManager.suAccessState]); | ||||
|             autoRes.setSummary(getResources() | ||||
|                     .getStringArray(R.array.auto_response)[magiskManager.suResponseType]); | ||||
|             suNotification.setSummary(getResources() | ||||
|                     .getStringArray(R.array.su_notification)[magiskManager.suNotificationType]); | ||||
|             requestTimeout.setSummary( | ||||
|                     getString(R.string.request_timeout_summary, prefs.getString("su_request_timeout", "10"))); | ||||
|             multiuserMode.setSummary(getResources() | ||||
|                     .getStringArray(R.array.multiuser_summary)[magiskManager.multiuserMode]); | ||||
|             namespaceMode.setSummary(getResources() | ||||
|                     .getStringArray(R.array.namespace_summary)[magiskManager.suNamespaceMode]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| 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 android.text.TextUtils; | ||||
|  | ||||
| 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.components.Activity; | ||||
| import com.topjohnwu.magisk.services.UpdateCheckService; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| public class SplashActivity extends Activity{ | ||||
|  | ||||
|     private static final int UPDATE_SERVICE_ID = 1; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|  | ||||
|         super.onCreate(savedInstanceState); | ||||
|  | ||||
|         // Init the info and configs and root shell | ||||
|         getApplicationContext().init(); | ||||
|  | ||||
|         // Now fire all async tasks | ||||
|         new GetBootBlocks(this).exec(); | ||||
|         new LoadModules(this).setCallBack(() -> new LoadRepos(this).exec()).exec(); | ||||
|         new LoadApps(this).exec(); | ||||
|  | ||||
|         if (Utils.checkNetworkStatus(this)) { | ||||
|             // Initialize the update check service, notify every 8 hours | ||||
|             if (!TextUtils.equals("install", getIntent().getStringExtra(MagiskManager.INTENT_SECTION))) { | ||||
|                 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 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); | ||||
|         } | ||||
|         startActivity(intent); | ||||
|         finish(); | ||||
|     } | ||||
| } | ||||
| @@ -1,123 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.text.TextUtils; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.asyncs.ProcessRepoZip; | ||||
| import com.topjohnwu.magisk.components.AlertDialogBuilder; | ||||
| import com.topjohnwu.magisk.components.MarkDownWindow; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
| import com.topjohnwu.magisk.receivers.DownloadReceiver; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> { | ||||
|  | ||||
|     private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos; | ||||
|     private Context mContext; | ||||
|  | ||||
|     public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) { | ||||
|         mUpdateRepos = update; | ||||
|         mInstalledRepos = installed; | ||||
|         mOthersRepos = others; | ||||
|     } | ||||
|  | ||||
|     @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); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(final ViewHolder holder, int position) { | ||||
|         Repo repo = getItem(position); | ||||
|  | ||||
|         holder.title.setText(repo.getName()); | ||||
|         holder.versionName.setText(repo.getVersion()); | ||||
|         String author = repo.getAuthor(); | ||||
|         holder.author.setText(TextUtils.isEmpty(author) ? null : mContext.getString(R.string.author, author)); | ||||
|         holder.description.setText(repo.getDescription()); | ||||
|  | ||||
|         holder.infoLayout.setOnClickListener(v -> new MarkDownWindow(null, repo.getDetailUrl(), mContext)); | ||||
|  | ||||
|         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)) | ||||
|                     .setCancelable(true) | ||||
|                     .setPositiveButton(R.string.install, (d, i) -> Utils.dlAndReceive( | ||||
|                             mContext, | ||||
|                             new DownloadReceiver() { | ||||
|                                 @Override | ||||
|                                 public void onDownloadDone(Uri uri, Context context) { | ||||
|                                     new ProcessRepoZip((Activity) mContext, 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, Context context) { | ||||
|                                     new ProcessRepoZip((Activity) mContext, uri, false).exec(); | ||||
|                                 } | ||||
|                             }, | ||||
|                             repo.getZipUrl(), | ||||
|                             Utils.getLegalFilename(filename))) | ||||
|                     .setNegativeButton(R.string.no_thanks, null) | ||||
|                     .show(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size(); | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|             } | ||||
|         } else { | ||||
|             return mUpdateRepos.get(position); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.title) TextView title; | ||||
|         @BindView(R.id.version_name) TextView versionName; | ||||
|         @BindView(R.id.description) TextView description; | ||||
|         @BindView(R.id.author) TextView author; | ||||
|         @BindView(R.id.info_layout) LinearLayout infoLayout; | ||||
|         @BindView(R.id.download) ImageView downloadImage; | ||||
|  | ||||
|         ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             ButterKnife.bind(this, itemView); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,178 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.util.SparseArray; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Comparator; | ||||
|  | ||||
| public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||
|  | ||||
|     private static final int SECTION_TYPE = 0; | ||||
|  | ||||
|     private boolean mValid = true; | ||||
|     private int mSectionResourceId; | ||||
|     private int mTextResourceId; | ||||
|     private RecyclerView.Adapter mBaseAdapter; | ||||
|     private SparseArray<Section> mSections = new SparseArray<Section>(); | ||||
|  | ||||
|  | ||||
|     public SimpleSectionedRecyclerViewAdapter(int sectionResourceId, int textResourceId, | ||||
|                                               RecyclerView.Adapter baseAdapter) { | ||||
|  | ||||
|         mSectionResourceId = sectionResourceId; | ||||
|         mTextResourceId = textResourceId; | ||||
|         mBaseAdapter = baseAdapter; | ||||
|  | ||||
|         mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { | ||||
|             @Override | ||||
|             public void onChanged() { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyDataSetChanged(); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeChanged(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeChanged(positionStart, itemCount); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeInserted(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeInserted(positionStart, itemCount); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeRemoved(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeRemoved(positionStart, itemCount); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static class SectionViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         public TextView title; | ||||
|  | ||||
|         public SectionViewHolder(View view, int mTextResourceid) { | ||||
|             super(view); | ||||
|             title = (TextView) view.findViewById(mTextResourceid); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) { | ||||
|         if (typeView == SECTION_TYPE) { | ||||
|             View view = LayoutInflater.from(parent.getContext()).inflate(mSectionResourceId, parent, false); | ||||
|             return new SectionViewHolder(view,mTextResourceId); | ||||
|         }else{ | ||||
|             return mBaseAdapter.onCreateViewHolder(parent, typeView -1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) { | ||||
|         if (isSectionHeaderPosition(position)) { | ||||
|             ((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title); | ||||
|         }else{ | ||||
|             mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemViewType(int position) { | ||||
|         return isSectionHeaderPosition(position) | ||||
|                 ? SECTION_TYPE | ||||
|                 : mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static class Section { | ||||
|         int firstPosition; | ||||
|         int sectionedPosition; | ||||
|         CharSequence title; | ||||
|  | ||||
|         public Section(int firstPosition, CharSequence title) { | ||||
|             this.firstPosition = firstPosition; | ||||
|             this.title = title; | ||||
|         } | ||||
|  | ||||
|         public CharSequence getTitle() { | ||||
|             return title; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public void setSections(Section[] sections) { | ||||
|         mSections.clear(); | ||||
|  | ||||
|         Arrays.sort(sections, new Comparator<Section>() { | ||||
|             @Override | ||||
|             public int compare(Section o, Section o1) { | ||||
|                 return (o.firstPosition == o1.firstPosition) | ||||
|                         ? 0 | ||||
|                         : ((o.firstPosition < o1.firstPosition) ? -1 : 1); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         int offset = 0; // offset positions for the headers we're adding | ||||
|         for (Section section : sections) { | ||||
|             section.sectionedPosition = section.firstPosition + offset; | ||||
|             mSections.append(section.sectionedPosition, section); | ||||
|             ++offset; | ||||
|         } | ||||
|  | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     public int positionToSectionedPosition(int position) { | ||||
|         int offset = 0; | ||||
|         for (int i = 0; i < mSections.size(); i++) { | ||||
|             if (mSections.valueAt(i).firstPosition > position) { | ||||
|                 break; | ||||
|             } | ||||
|             ++offset; | ||||
|         } | ||||
|         return position + offset; | ||||
|     } | ||||
|  | ||||
|     public int sectionedPositionToPosition(int sectionedPosition) { | ||||
|         if (isSectionHeaderPosition(sectionedPosition)) { | ||||
|             return RecyclerView.NO_POSITION; | ||||
|         } | ||||
|  | ||||
|         int offset = 0; | ||||
|         for (int i = 0; i < mSections.size(); i++) { | ||||
|             if (mSections.valueAt(i).sectionedPosition > sectionedPosition) { | ||||
|                 break; | ||||
|             } | ||||
|             --offset; | ||||
|         } | ||||
|         return sectionedPosition + offset; | ||||
|     } | ||||
|  | ||||
|     public boolean isSectionHeaderPosition(int position) { | ||||
|         return mSections.get(position) != null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public long getItemId(int position) { | ||||
|         return isSectionHeaderPosition(position) | ||||
|                 ? Integer.MAX_VALUE - mSections.indexOfKey(position) | ||||
|                 : mBaseAdapter.getItemId(sectionedPositionToPosition(position)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,197 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.animation.Animation; | ||||
| import android.view.animation.RotateAnimation; | ||||
| import android.widget.ImageView; | ||||
| 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.ExpandableViewHolder; | ||||
| import com.topjohnwu.magisk.superuser.SuLogEntry; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class SuLogAdapter { | ||||
|  | ||||
|     private ExpandableAdapter adapter; | ||||
|     private Set<SuLogEntry> expandList = new HashSet<>(); | ||||
|  | ||||
|     public SuLogAdapter(List<SuLogEntry> list) { | ||||
|  | ||||
|         // Separate the logs with date | ||||
|         Map<String, List<SuLogEntry>> logEntryMap = new LinkedHashMap<>(); | ||||
|         List<SuLogEntry> group; | ||||
|         for (SuLogEntry log : list) { | ||||
|             String date = log.getDateString(); | ||||
|             group = logEntryMap.get(date); | ||||
|             if (group == null) { | ||||
|                 group = new ArrayList<>(); | ||||
|                 logEntryMap.put(date, group); | ||||
|             } | ||||
|             group.add(log); | ||||
|         } | ||||
|  | ||||
|         // Then format them into expandable groups | ||||
|         List<LogGroup> logEntryGroups = new ArrayList<>(); | ||||
|         for (Map.Entry<String, List<SuLogEntry>> entry : logEntryMap.entrySet()) { | ||||
|             logEntryGroups.add(new LogGroup(entry.getKey(), entry.getValue())); | ||||
|         } | ||||
|         adapter = new ExpandableAdapter(logEntryGroups); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public RecyclerView.Adapter getAdapter() { | ||||
|         return adapter; | ||||
|     } | ||||
|  | ||||
|     private class ExpandableAdapter | ||||
|             extends ExpandableRecyclerViewAdapter<LogGroupViewHolder, LogViewHolder> { | ||||
|  | ||||
|         ExpandableAdapter(List<? extends ExpandableGroup> groups) { | ||||
|             super(groups); | ||||
|             expandableList.expandedGroupIndexes[0] = true; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public LogGroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { | ||||
|             View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false); | ||||
|             return new LogGroupViewHolder(v); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public LogViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) { | ||||
|             View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false); | ||||
|             return new LogViewHolder(v); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBindChildViewHolder(LogViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { | ||||
|             Context context = holder.itemView.getContext(); | ||||
|             SuLogEntry logEntry = (SuLogEntry) group.getItems().get(childIndex); | ||||
|             holder.setExpanded(expandList.contains(logEntry)); | ||||
|             holder.itemView.setOnClickListener(view -> { | ||||
|                 if (holder.getExpanded()) { | ||||
|                     holder.collapse(); | ||||
|                     expandList.remove(logEntry); | ||||
|                 } else { | ||||
|                     holder.expand(); | ||||
|                     expandList.add(logEntry); | ||||
|                 } | ||||
|             }); | ||||
|             holder.appName.setText(logEntry.appName); | ||||
|             holder.action.setText(context.getString(logEntry.action ? R.string.grant : R.string.deny)); | ||||
|             holder.command.setText(logEntry.command); | ||||
|             holder.fromPid.setText(String.valueOf(logEntry.fromPid)); | ||||
|             holder.toUid.setText(String.valueOf(logEntry.toUid)); | ||||
|             holder.time.setText(logEntry.getTimeString()); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onBindGroupViewHolder(LogGroupViewHolder holder, int flatPosition, ExpandableGroup group) { | ||||
|             holder.date.setText(group.getTitle()); | ||||
|             if (isGroupExpanded(flatPosition)) { | ||||
|                 holder.expand(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private class LogGroup extends ExpandableGroup<SuLogEntry> { | ||||
|         LogGroup(String title, List<SuLogEntry> items) { | ||||
|             super(title, items); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static class LogGroupViewHolder extends GroupViewHolder { | ||||
|  | ||||
|         @BindView(R.id.date) TextView date; | ||||
|         @BindView(R.id.arrow) ImageView arrow; | ||||
|  | ||||
|         public LogGroupViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             ButterKnife.bind(this, itemView); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void expand() { | ||||
|             RotateAnimation rotate = | ||||
|                     new RotateAnimation(360, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); | ||||
|             rotate.setDuration(300); | ||||
|             rotate.setFillAfter(true); | ||||
|             arrow.setAnimation(rotate); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void collapse() { | ||||
|             RotateAnimation rotate = | ||||
|                     new RotateAnimation(180, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); | ||||
|             rotate.setDuration(300); | ||||
|             rotate.setFillAfter(true); | ||||
|             arrow.setAnimation(rotate); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Wrapper class | ||||
|     static class LogViewHolder extends ChildViewHolder { | ||||
|  | ||||
|         private InternalViewHolder expandableViewHolder; | ||||
|  | ||||
|         @BindView(R.id.app_name) TextView appName; | ||||
|         @BindView(R.id.action) TextView action; | ||||
|         @BindView(R.id.time) TextView time; | ||||
|         @BindView(R.id.fromPid) TextView fromPid; | ||||
|         @BindView(R.id.toUid) TextView toUid; | ||||
|         @BindView(R.id.command) TextView command; | ||||
|  | ||||
|         LogViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             ButterKnife.bind(this, itemView); | ||||
|             expandableViewHolder = new InternalViewHolder(itemView); | ||||
|         } | ||||
|  | ||||
|         private class InternalViewHolder extends ExpandableViewHolder { | ||||
|  | ||||
|             InternalViewHolder(View itemView) { | ||||
|                 super(itemView); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void setExpandLayout(View itemView) { | ||||
|                 expandLayout = itemView.findViewById(R.id.expand_layout); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private boolean getExpanded() { | ||||
|             return expandableViewHolder.mExpanded; | ||||
|         } | ||||
|  | ||||
|         private void setExpanded(boolean expanded) { | ||||
|             expandableViewHolder.setExpanded(expanded); | ||||
|         } | ||||
|  | ||||
|         private void expand() { | ||||
|             expandableViewHolder.expand(); | ||||
|         } | ||||
|  | ||||
|         private void collapse() { | ||||
|             expandableViewHolder.collapse(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,57 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.content.Context; | ||||
|  | ||||
| import com.topjohnwu.magisk.BuildConfig; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.WebService; | ||||
|  | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| public class CheckUpdates extends ParallelTask<Void, Void, Void> { | ||||
|  | ||||
|     private static final String UPDATE_JSON = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/magisk_update.json"; | ||||
|  | ||||
|     private boolean showNotification = false; | ||||
|  | ||||
|     public CheckUpdates(Context context, boolean b) { | ||||
|         this(context); | ||||
|         showNotification = b; | ||||
|     } | ||||
|  | ||||
|     public CheckUpdates(Context context) { | ||||
|         magiskManager = Utils.getMagiskManager(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInBackground(Void... voids) { | ||||
|         String jsonStr = WebService.request(UPDATE_JSON, WebService.GET); | ||||
|         try { | ||||
|             JSONObject json = new JSONObject(jsonStr); | ||||
|             JSONObject magisk = json.getJSONObject("magisk"); | ||||
|             magiskManager.remoteMagiskVersionString = magisk.getString("version"); | ||||
|             magiskManager.remoteMagiskVersionCode = magisk.getInt("versionCode"); | ||||
|             magiskManager.magiskLink = magisk.getString("link"); | ||||
|             magiskManager.releaseNoteLink = magisk.getString("note"); | ||||
|             JSONObject manager = json.getJSONObject("app"); | ||||
|             magiskManager.remoteManagerVersionString = manager.getString("version"); | ||||
|             magiskManager.remoteManagerVersionCode = manager.getInt("versionCode"); | ||||
|             magiskManager.managerLink = manager.getString("link"); | ||||
|         } catch (JSONException ignored) {} | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         if (showNotification && magiskManager.updateNotification) { | ||||
|             if (BuildConfig.VERSION_CODE < magiskManager.remoteManagerVersionCode) { | ||||
|                 Utils.showManagerUpdate(magiskManager); | ||||
|             } else if (magiskManager.magiskVersionCode < magiskManager.remoteMagiskVersionCode) { | ||||
|                 Utils.showMagiskUpdate(magiskManager); | ||||
|             } | ||||
|         } | ||||
|         magiskManager.updateCheckDone.trigger(); | ||||
|         super.onPostExecute(v); | ||||
|     } | ||||
| } | ||||
| @@ -1,154 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.app.ProgressDialog; | ||||
| import android.net.Uri; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.MagiskManager; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.components.AlertDialogBuilder; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.ZipUtils; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.util.List; | ||||
|  | ||||
| public class FlashZip extends RootTask<Void, String, Integer> { | ||||
|  | ||||
|     private Uri mUri; | ||||
|     private File mCachedFile, mScriptFile, mCheckFile; | ||||
|  | ||||
|     private String mFilename; | ||||
|     private ProgressDialog progress; | ||||
|  | ||||
|     public FlashZip(Activity context, Uri uri) { | ||||
|         super(context); | ||||
|         mUri = uri; | ||||
|  | ||||
|         mCachedFile = new File(magiskManager.getCacheDir(), "install.zip"); | ||||
|         mScriptFile = new File(magiskManager.getCacheDir(), "/META-INF/com/google/android/update-binary"); | ||||
|         mCheckFile = new File(mScriptFile.getParent(), "updater-script"); | ||||
|  | ||||
|         // Try to get the filename ourselves | ||||
|         mFilename = Utils.getNameFromUri(magiskManager, mUri); | ||||
|     } | ||||
|  | ||||
|     private void copyToCache() throws Throwable { | ||||
|         publishProgress(magiskManager.getString(R.string.copying_msg)); | ||||
|  | ||||
|         if (mCachedFile.exists() && !mCachedFile.delete()) { | ||||
|             Logger.error("FlashZip: Error while deleting already existing file"); | ||||
|             throw new IOException(); | ||||
|         } | ||||
|         try ( | ||||
|                 InputStream in = magiskManager.getContentResolver().openInputStream(mUri); | ||||
|                 OutputStream outputStream = new FileOutputStream(mCachedFile) | ||||
|         ) { | ||||
|             byte buffer[] = new byte[1024]; | ||||
|             int length; | ||||
|             if (in == null) throw new FileNotFoundException(); | ||||
|             while ((length = in.read(buffer)) > 0) | ||||
|                 outputStream.write(buffer, 0, length); | ||||
|  | ||||
|             Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath()); | ||||
|         } catch (FileNotFoundException e) { | ||||
|             Logger.error("FlashZip: Invalid Uri"); | ||||
|             throw e; | ||||
|         } catch (IOException e) { | ||||
|             Logger.error("FlashZip: Error in creating file"); | ||||
|             throw e; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean unzipAndCheck() throws Exception { | ||||
|         ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android"); | ||||
|         List<String> ret; | ||||
|         ret = Utils.readFile(mCheckFile.getPath()); | ||||
|         return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK"); | ||||
|     } | ||||
|  | ||||
|     private int cleanup(int ret) { | ||||
|         Shell.su( | ||||
|                 "rm -rf " + mCachedFile.getParent() + "/*", | ||||
|                 "rm -rf " + MagiskManager.TMP_FOLDER_PATH | ||||
|         ); | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPreExecute() { | ||||
|         progress = new ProgressDialog(activity); | ||||
|         progress.setTitle(R.string.zip_install_progress_title); | ||||
|         progress.show(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onProgressUpdate(String... values) { | ||||
|         progress.setMessage(values[0]); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Integer doInRoot(Void... voids) { | ||||
|         Logger.dev("FlashZip Running... " + mFilename); | ||||
|         List<String> ret; | ||||
|         try { | ||||
|             copyToCache(); | ||||
|             if (!unzipAndCheck()) return cleanup(0); | ||||
|             publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename)); | ||||
|             ret = Shell.su( | ||||
|                     "BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile, | ||||
|                     "if [ $? -eq 0 ]; then echo true; else echo false; fi" | ||||
|             ); | ||||
|             if (!Utils.isValidShellResponse(ret)) return -1; | ||||
|             Logger.dev("FlashZip: Console log:"); | ||||
|             for (String line : ret) { | ||||
|                 Logger.dev(line); | ||||
|             } | ||||
|             if (Boolean.parseBoolean(ret.get(ret.size() - 1))) | ||||
|                 return cleanup(1); | ||||
|  | ||||
|         } catch (Throwable e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return cleanup(-1); | ||||
|     } | ||||
|  | ||||
|     // -1 = error, manual install; 0 = invalid zip; 1 = success | ||||
|     @Override | ||||
|     protected void onPostExecute(Integer result) { | ||||
|         progress.dismiss(); | ||||
|         switch (result) { | ||||
|             case -1: | ||||
|                 Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show(); | ||||
|                 Utils.showUriSnack(activity, mUri); | ||||
|                 break; | ||||
|             case 0: | ||||
|                 Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show(); | ||||
|                 break; | ||||
|             case 1: | ||||
|                 onSuccess(); | ||||
|                 break; | ||||
|         } | ||||
|         super.onPostExecute(result); | ||||
|     } | ||||
|  | ||||
|     protected void onSuccess() { | ||||
|         magiskManager.updateCheckDone.trigger(); | ||||
|         new LoadModules(activity).exec(); | ||||
|  | ||||
|         new AlertDialogBuilder(activity) | ||||
|                 .setTitle(R.string.reboot_title) | ||||
|                 .setMessage(R.string.reboot_msg) | ||||
|                 .setPositiveButton(R.string.reboot, (dialogInterface, i) -> Shell.su(true, "reboot")) | ||||
|                 .setNegativeButton(R.string.no_thanks, null) | ||||
|                 .show(); | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +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 RootTask<Void, Void, Void> { | ||||
|  | ||||
|     public GetBootBlocks(Activity context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInRoot(Void... params) { | ||||
|         magiskManager.blockList = Shell.su( | ||||
|                 "find /dev/block -type b -maxdepth 1 | grep -v -E \"loop|ram|dm-0\"" | ||||
|         ); | ||||
|         if (magiskManager.bootBlock == null) { | ||||
|             magiskManager.bootBlock = Utils.detectBootImage(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         magiskManager.blockDetectionDone.trigger(); | ||||
|         super.onPostExecute(v); | ||||
|     } | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
|  | ||||
| import com.topjohnwu.magisk.adapters.ApplicationAdapter; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
|  | ||||
| public class LoadApps extends ParallelTask<Void, Void, Void> { | ||||
|  | ||||
|     public LoadApps(Activity context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInBackground(Void... voids) { | ||||
|         PackageManager pm = magiskManager.getPackageManager(); | ||||
|         List<ApplicationInfo> list = pm.getInstalledApplications(0); | ||||
|         for (Iterator<ApplicationInfo> i = list.iterator(); i.hasNext(); ) { | ||||
|             ApplicationInfo info = i.next(); | ||||
|             if (ApplicationAdapter.BLACKLIST.contains(info.packageName) || !info.enabled) { | ||||
|                 i.remove(); | ||||
|             } | ||||
|         } | ||||
|         Collections.sort(list, (a, b) -> a.loadLabel(pm).toString().toLowerCase() | ||||
|                 .compareTo(b.loadLabel(pm).toString().toLowerCase())); | ||||
|         magiskManager.appList = Collections.unmodifiableList(list); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         new MagiskHide(activity).list(); | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
|  | ||||
| import com.topjohnwu.magisk.MagiskManager; | ||||
| import com.topjohnwu.magisk.module.BaseModule; | ||||
| import com.topjohnwu.magisk.module.Module; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.ValueSortedMap; | ||||
|  | ||||
| public class LoadModules extends RootTask<Void, Void, Void> { | ||||
|  | ||||
|     public LoadModules(Activity context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInRoot(Void... voids) { | ||||
|         Logger.dev("LoadModules: Loading modules"); | ||||
|  | ||||
|         magiskManager.moduleMap = new ValueSortedMap<>(); | ||||
|  | ||||
|         for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) { | ||||
|             Logger.dev("LoadModules: Adding modules from " + path); | ||||
|             Module module; | ||||
|             try { | ||||
|                 module = new Module(path); | ||||
|                 magiskManager.moduleMap.put(module.getId(), module); | ||||
|             } catch (BaseModule.CacheModException ignored) {} | ||||
|         } | ||||
|  | ||||
|         Logger.dev("LoadModules: Data load done"); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         magiskManager.moduleLoadDone.trigger(); | ||||
|         super.onPostExecute(v); | ||||
|     } | ||||
| } | ||||
| @@ -1,189 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.SharedPreferences; | ||||
| import android.text.TextUtils; | ||||
|  | ||||
| import com.topjohnwu.magisk.database.RepoDatabaseHelper; | ||||
| import com.topjohnwu.magisk.module.BaseModule; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.ValueSortedMap; | ||||
| import com.topjohnwu.magisk.utils.WebService; | ||||
|  | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class LoadRepos extends ParallelTask<Void, Void, Void> { | ||||
|  | ||||
|     public static final String ETAG_KEY = "ETag"; | ||||
|  | ||||
|     private static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d"; | ||||
|     private static final String IF_NONE_MATCH = "If-None-Match"; | ||||
|     private static final String LINK_KEY = "Link"; | ||||
|  | ||||
|     private static final int CHECK_ETAG = 0; | ||||
|     private static final int LOAD_NEXT = 1; | ||||
|     private static final int LOAD_PREV = 2; | ||||
|  | ||||
|     private List<String> etags; | ||||
|     private ValueSortedMap<String, Repo> cached, fetched; | ||||
|     private RepoDatabaseHelper repoDB; | ||||
|     private SharedPreferences prefs; | ||||
|  | ||||
|     public LoadRepos(Activity context) { | ||||
|         super(context); | ||||
|         prefs = magiskManager.prefs; | ||||
|         String prefsPath = context.getApplicationInfo().dataDir + "/shared_prefs"; | ||||
|         repoDB = new RepoDatabaseHelper(magiskManager); | ||||
|         // Legacy data cleanup | ||||
|         File old = new File(prefsPath, "RepoMap.xml"); | ||||
|         if (old.exists() || !prefs.getString("repomap", "empty").equals("empty")) { | ||||
|             old.delete(); | ||||
|             prefs.edit().remove("version").remove("repomap").remove(ETAG_KEY).apply(); | ||||
|             repoDB.clearRepo(); | ||||
|         } | ||||
|         etags = new ArrayList<>( | ||||
|                 Arrays.asList(magiskManager.prefs.getString(ETAG_KEY, "").split(","))); | ||||
|     } | ||||
|  | ||||
|     private void loadJSON(String jsonString) throws Exception { | ||||
|         JSONArray jsonArray = new JSONArray(jsonString); | ||||
|  | ||||
|         for (int i = 0; i < jsonArray.length(); i++) { | ||||
|             JSONObject jsonobject = jsonArray.getJSONObject(i); | ||||
|             String id = jsonobject.getString("description"); | ||||
|             String name = jsonobject.getString("name"); | ||||
|             String lastUpdate = jsonobject.getString("pushed_at"); | ||||
|             SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); | ||||
|             Date updatedDate = format.parse(lastUpdate); | ||||
|             Repo repo = cached.get(id); | ||||
|             try { | ||||
|                 if (repo == null) { | ||||
|                     Logger.dev("LoadRepos: Create new repo " + id); | ||||
|                     repo = new Repo(name, updatedDate); | ||||
|                 } else { | ||||
|                     // Popout from cached | ||||
|                     cached.remove(id); | ||||
|                     repo.update(updatedDate); | ||||
|                 } | ||||
|                 if (repo.getId() != null) { | ||||
|                     fetched.put(id, repo); | ||||
|                 } | ||||
|             } catch (BaseModule.CacheModException ignored) {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private boolean loadPage(int page, String url, int mode) { | ||||
|         Logger.dev("LoadRepos: Loading page: " + (page + 1)); | ||||
|         Map<String, String> header = new HashMap<>(); | ||||
|         if (mode == CHECK_ETAG && page < etags.size() && !TextUtils.isEmpty(etags.get(page))) { | ||||
|             Logger.dev("ETAG: " + etags.get(page)); | ||||
|             header.put(IF_NONE_MATCH, etags.get(page)); | ||||
|         } | ||||
|         if (url == null) { | ||||
|             url = String.format(Locale.US, REPO_URL, page + 1); | ||||
|         } | ||||
|         String jsonString = WebService.request(url, WebService.GET, header, true); | ||||
|         if (TextUtils.isEmpty(jsonString)) { | ||||
|             // At least check the pages we know | ||||
|             return page + 1 < etags.size() && loadPage(page + 1, null, CHECK_ETAG); | ||||
|         } | ||||
|  | ||||
|         // The request succeed, parse the new stuffs | ||||
|         try { | ||||
|             loadJSON(jsonString); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Update the ETAG | ||||
|         String newEtag = header.get(ETAG_KEY); | ||||
|         newEtag = newEtag.substring(newEtag.indexOf('\"'), newEtag.lastIndexOf('\"') + 1); | ||||
|         Logger.dev("New ETAG: " + newEtag); | ||||
|         if (page < etags.size()) { | ||||
|             etags.set(page, newEtag); | ||||
|         } else { | ||||
|             etags.add(newEtag); | ||||
|         } | ||||
|  | ||||
|         String links = header.get(LINK_KEY); | ||||
|         if (links != null) { | ||||
|             if (mode == CHECK_ETAG || mode == LOAD_NEXT) { | ||||
|                 // Try to check next page URL | ||||
|                 url = null; | ||||
|                 for (String s : links.split(", ")) { | ||||
|                     if (s.contains("next")) { | ||||
|                         url = s.substring(s.indexOf("<") + 1, s.indexOf(">; ")); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (url != null) { | ||||
|                     loadPage(page + 1, url, LOAD_NEXT); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (mode == CHECK_ETAG || mode == LOAD_PREV) { | ||||
|                 // Try to check prev page URL | ||||
|                 url = null; | ||||
|                 for (String s : links.split(", ")) { | ||||
|                     if (s.contains("prev")) { | ||||
|                         url = s.substring(s.indexOf("<") + 1, s.indexOf(">; ")); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (url != null) { | ||||
|                     loadPage(page - 1, url, LOAD_PREV); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInBackground(Void... voids) { | ||||
|         Logger.dev("LoadRepos: Loading repos"); | ||||
|  | ||||
|         cached = repoDB.getRepoMap(false); | ||||
|         fetched = new ValueSortedMap<>(); | ||||
|  | ||||
|         if (!loadPage(0, null, CHECK_ETAG)) { | ||||
|             magiskManager.repoMap = repoDB.getRepoMap(); | ||||
|             Logger.dev("LoadRepos: No updates, use DB"); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         repoDB.addRepoMap(fetched); | ||||
|         repoDB.removeRepo(cached); | ||||
|  | ||||
|         // Update ETag | ||||
|         StringBuilder etagBuilder = new StringBuilder(); | ||||
|         for (int i = 0; i < etags.size(); ++i) { | ||||
|             if (i != 0) etagBuilder.append(","); | ||||
|             etagBuilder.append(etags.get(i)); | ||||
|         } | ||||
|         prefs.edit().putString(ETAG_KEY, etagBuilder.toString()).apply(); | ||||
|  | ||||
|         magiskManager.repoMap = repoDB.getRepoMap(); | ||||
|         Logger.dev("LoadRepos: Done"); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         magiskManager.repoLoadDone.trigger(); | ||||
|         super.onPostExecute(v); | ||||
|     } | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| public class MagiskHide extends RootTask<Object, Void, Void> { | ||||
|  | ||||
|     private boolean isList = false; | ||||
|  | ||||
|     public MagiskHide() {} | ||||
|  | ||||
|     public MagiskHide(Activity context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Void doInRoot(Object... params) { | ||||
|         String command = (String) params[0]; | ||||
|         List<String> ret = Shell.su("magiskhide --" + command); | ||||
|         if (isList) { | ||||
|             magiskManager.magiskHideList = ret; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Void v) { | ||||
|         if (isList) { | ||||
|             magiskManager.magiskHideDone.trigger(); | ||||
|         } | ||||
|         super.onPostExecute(v); | ||||
|     } | ||||
|  | ||||
|     public void add(CharSequence packageName) { | ||||
|         exec("add " + packageName); | ||||
|     } | ||||
|  | ||||
|     public void rm(CharSequence packageName) { | ||||
|         exec("rm " + packageName); | ||||
|     } | ||||
|  | ||||
|     public void enable() { | ||||
|         exec("enable"); | ||||
|     } | ||||
|  | ||||
|     public void disable() { | ||||
|         exec("disable"); | ||||
|     } | ||||
|  | ||||
|     public void list() { | ||||
|         isList = true; | ||||
|         if (magiskManager == null) return; | ||||
|         exec("ls"); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.app.ProgressDialog; | ||||
| import android.net.Uri; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| 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()) { | ||||
|             synchronized (Shell.lock) { | ||||
|                 Shell.su("rm -f /dev/.magisk", | ||||
|                         (mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "", | ||||
|                         "echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk", | ||||
|                         "echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk" | ||||
|                 ); | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Boolean result) { | ||||
|         progressDialog.dismiss(); | ||||
|         if (result) { | ||||
|             new FlashZip(activity, mUri).exec(); | ||||
|         } else { | ||||
|             Utils.showUriSnack(activity, mUri); | ||||
|         } | ||||
|         super.onPostExecute(result); | ||||
|     } | ||||
| } | ||||
| @@ -1,97 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.app.ProgressDialog; | ||||
| import android.net.Uri; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.ZipUtils; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.OutputStream; | ||||
|  | ||||
| public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> { | ||||
|  | ||||
|     private Uri mUri; | ||||
|     private ProgressDialog progressDialog; | ||||
|     private boolean mInstall; | ||||
|  | ||||
|     public ProcessRepoZip(Activity context, Uri uri, boolean install) { | ||||
|         super(context); | ||||
|         mUri = uri; | ||||
|         mInstall = install; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPreExecute() { | ||||
|         progressDialog = ProgressDialog.show(activity, | ||||
|                 activity.getString(R.string.zip_process_title), | ||||
|                 activity.getString(R.string.zip_process_msg)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected Boolean doInBackground(Void... params) { | ||||
|         try { | ||||
|  | ||||
|             // Create temp file | ||||
|             File temp1 = new File(magiskManager.getCacheDir(), "1.zip"); | ||||
|             File temp2 = new File(magiskManager.getCacheDir(), "2.zip"); | ||||
|             magiskManager.getCacheDir().mkdirs(); | ||||
|             temp1.createNewFile(); | ||||
|             temp2.createNewFile(); | ||||
|  | ||||
|             // First remove top folder in Github source zip, Uri -> temp1 | ||||
|             ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), temp1); | ||||
|  | ||||
|             // Then sign the zip for the first time, temp1 -> temp2 | ||||
|             ZipUtils.signZip(activity, temp1, temp2, false); | ||||
|  | ||||
|             // Adjust the zip to prevent unzip issues, temp2 -> temp1 | ||||
|             ZipUtils.zipAdjust(temp2.getPath(), temp1.getPath()); | ||||
|  | ||||
|             // Finally, sign the whole zip file again, temp1 -> temp2 | ||||
|             ZipUtils.signZip(activity, temp1, temp2, true); | ||||
|  | ||||
|             // Write it back to the downloaded zip, temp2 -> Uri | ||||
|             FileInputStream in = new FileInputStream(temp2); | ||||
|             try (OutputStream target = activity.getContentResolver().openOutputStream(mUri)) { | ||||
|                 byte[] buffer = new byte[4096]; | ||||
|                 int length; | ||||
|                 if (target == null) throw new FileNotFoundException(); | ||||
|                 while ((length = in.read(buffer)) > 0) | ||||
|                     target.write(buffer, 0, length); | ||||
|             } | ||||
|  | ||||
|             // Delete the temp file | ||||
|             temp1.delete(); | ||||
|             temp2.delete(); | ||||
|  | ||||
|             return true; | ||||
|         } catch (Exception e) { | ||||
|             Logger.error("ProcessRepoZip: Error!"); | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPostExecute(Boolean result) { | ||||
|         progressDialog.dismiss(); | ||||
|         if (result) { | ||||
|             if (Shell.rootAccess() && mInstall) { | ||||
|                 new FlashZip(activity, mUri).exec(); | ||||
|             } else { | ||||
|                 Utils.showUriSnack(activity, mUri); | ||||
|             } | ||||
|  | ||||
|         } else { | ||||
|             Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| package com.topjohnwu.magisk.asyncs; | ||||
|  | ||||
| import android.app.Activity; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
|  | ||||
| public abstract class RootTask <Params, Progress, Result> extends ParallelTask<Params, Progress, Result> { | ||||
|  | ||||
|     public RootTask() {} | ||||
|  | ||||
|     public RootTask(Activity context) { | ||||
|         super(context); | ||||
|     } | ||||
|  | ||||
|     @SafeVarargs | ||||
|     @Override | ||||
|     final protected Result doInBackground(Params... params) { | ||||
|         synchronized (Shell.lock) { | ||||
|             return doInRoot(params); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     abstract protected Result doInRoot(Params... params); | ||||
|  | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public void exec(Params... params) { | ||||
|         if (Shell.rootAccess()) { | ||||
|             super.exec(params); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| package com.topjohnwu.magisk.components; | ||||
|  | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.view.WindowManager; | ||||
|  | ||||
| import com.topjohnwu.magisk.MagiskManager; | ||||
| import com.topjohnwu.magisk.R; | ||||
|  | ||||
| public class Activity extends AppCompatActivity { | ||||
|  | ||||
|     @Override | ||||
|     public MagiskManager getApplicationContext() { | ||||
|         return (MagiskManager) super.getApplicationContext(); | ||||
|     } | ||||
|  | ||||
|     protected void setFloating() { | ||||
|         boolean isTablet = getResources().getBoolean(R.bool.isTablet); | ||||
|         if (isTablet) { | ||||
|             WindowManager.LayoutParams params = getWindow().getAttributes(); | ||||
|             params.height = getResources().getDimensionPixelSize(R.dimen.floating_height); | ||||
|             params.width = getResources().getDimensionPixelSize(R.dimen.floating_width); | ||||
|             params.alpha = 1.0f; | ||||
|             params.dimAmount = 0.6f; | ||||
|             params.flags |= 2; | ||||
|             getWindow().setAttributes(params); | ||||
|             setFinishOnTouchOutside(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,77 +0,0 @@ | ||||
| package com.topjohnwu.magisk.components; | ||||
|  | ||||
| import android.animation.ValueAnimator; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.ViewTreeObserver; | ||||
|  | ||||
| public abstract class ExpandableViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|     protected ViewGroup expandLayout; | ||||
|     private ValueAnimator expandAnimator, collapseAnimator; | ||||
|     private static int expandHeight = 0; | ||||
|  | ||||
|     public boolean mExpanded = false; | ||||
|  | ||||
|     public ExpandableViewHolder(View itemView) { | ||||
|         super(itemView); | ||||
|         setExpandLayout(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); | ||||
|                         expandAnimator = slideAnimator(0, expandHeight); | ||||
|                         collapseAnimator = slideAnimator(expandHeight, 0); | ||||
|                         return true; | ||||
|                     } | ||||
|  | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     public 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); | ||||
|     } | ||||
|  | ||||
|     public void expand() { | ||||
|         if (mExpanded) return; | ||||
|         expandLayout.setVisibility(View.VISIBLE); | ||||
|         expandAnimator.start(); | ||||
|         mExpanded = true; | ||||
|     } | ||||
|  | ||||
|     public void collapse() { | ||||
|         if (!mExpanded) return; | ||||
|         collapseAnimator.start(); | ||||
|         mExpanded = false; | ||||
|     } | ||||
|  | ||||
|     public abstract void setExpandLayout(View itemView); | ||||
|  | ||||
|     private ValueAnimator slideAnimator(int start, int end) { | ||||
|  | ||||
|         ValueAnimator animator = ValueAnimator.ofInt(start, end); | ||||
|  | ||||
|         animator.addUpdateListener(valueAnimator -> { | ||||
|             int value = (Integer) valueAnimator.getAnimatedValue(); | ||||
|             ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams(); | ||||
|             layoutParams.height = value; | ||||
|             expandLayout.setLayoutParams(layoutParams); | ||||
|         }); | ||||
|         return animator; | ||||
|     } | ||||
| } | ||||
| @@ -1,12 +0,0 @@ | ||||
| package com.topjohnwu.magisk.components; | ||||
|  | ||||
| import com.topjohnwu.magisk.MagiskManager; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| public class Fragment extends android.support.v4.app.Fragment { | ||||
|  | ||||
|     public MagiskManager getApplication() { | ||||
|         return Utils.getMagiskManager(getActivity()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
|  | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user