mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-11-03 15:52:30 +01:00 
			
		
		
		
	Compare commits
	
		
			274 Commits
		
	
	
		
			manager-v7
			...
			manager-v7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					59fd38bbf8 | ||
| 
						 | 
					06dc6df270 | ||
| 
						 | 
					ff8460b361 | ||
| 
						 | 
					674d272eaa | ||
| 
						 | 
					c3e00c279d | ||
| 
						 | 
					175d920c94 | ||
| 
						 | 
					04920883ea | ||
| 
						 | 
					5e44b0b9d5 | ||
| 
						 | 
					23c1a1dab8 | ||
| 
						 | 
					f5d054b93c | ||
| 
						 | 
					d25ae5e0a9 | ||
| 
						 | 
					c42a51dcbb | ||
| 
						 | 
					da3fd92b31 | ||
| 
						 | 
					4a45ba3c14 | ||
| 
						 | 
					dbc8bed234 | ||
| 
						 | 
					f8b4190a11 | ||
| 
						 | 
					479972e3ae | ||
| 
						 | 
					3ea28b0afb | ||
| 
						 | 
					2b3cc28966 | ||
| 
						 | 
					751642b39a | ||
| 
						 | 
					d6c2c821a4 | ||
| 
						 | 
					dfc65b95f7 | ||
| 
						 | 
					b45d922463 | ||
| 
						 | 
					f87ee3fcf9 | ||
| 
						 | 
					e0927cd763 | ||
| 
						 | 
					21099eabfa | ||
| 
						 | 
					abbd2e6b72 | ||
| 
						 | 
					5b7ddbbb01 | ||
| 
						 | 
					6352fbb3b2 | ||
| 
						 | 
					d3f49334e2 | ||
| 
						 | 
					c4356171b3 | ||
| 
						 | 
					5c5625911d | ||
| 
						 | 
					6a10cc9c55 | ||
| 
						 | 
					6b317f918e | ||
| 
						 | 
					08b528dc4f | ||
| 
						 | 
					fc886a5a47 | ||
| 
						 | 
					0cb90e2e55 | ||
| 
						 | 
					64113a69b4 | ||
| 
						 | 
					544bb7459c | ||
| 
						 | 
					578a50b464 | ||
| 
						 | 
					3d4081d0af | ||
| 
						 | 
					b763b81f56 | ||
| 
						 | 
					947dae4900 | ||
| 
						 | 
					debd1d7d54 | ||
| 
						 | 
					cba0d04000 | ||
| 
						 | 
					695e7e6da0 | ||
| 
						 | 
					4cd4bfa1d7 | ||
| 
						 | 
					16b400964b | ||
| 
						 | 
					cf2d02c0dd | ||
| 
						 | 
					0fcd0de0d1 | ||
| 
						 | 
					748a35774f | ||
| 
						 | 
					a52a3e38ed | ||
| 
						 | 
					ee0cef06a6 | ||
| 
						 | 
					0e5a113a0c | ||
| 
						 | 
					a1ccd44013 | ||
| 
						 | 
					4d91e50d6d | ||
| 
						 | 
					120668c7bc | ||
| 
						 | 
					d81ccde569 | ||
| 
						 | 
					e8581b4adb | ||
| 
						 | 
					19906575a3 | ||
| 
						 | 
					9329094a4e | ||
| 
						 | 
					b44f5122fd | ||
| 
						 | 
					17981730a4 | ||
| 
						 | 
					53de6da26c | ||
| 
						 | 
					3e30ccdeee | ||
| 
						 | 
					baaaf7d5de | ||
| 
						 | 
					45d8d139a9 | ||
| 
						 | 
					fe644e10d0 | ||
| 
						 | 
					f383d11d10 | ||
| 
						 | 
					ef1b928532 | ||
| 
						 | 
					6e46d394b1 | ||
| 
						 | 
					f109038d12 | ||
| 
						 | 
					e31e687602 | ||
| 
						 | 
					86bfb22d4c | ||
| 
						 | 
					3f057367e3 | ||
| 
						 | 
					3d7ed5820e | ||
| 
						 | 
					0118f2efa7 | ||
| 
						 | 
					15312e4709 | ||
| 
						 | 
					bf1568a73a | ||
| 
						 | 
					13a2520ea5 | ||
| 
						 | 
					f53238f206 | ||
| 
						 | 
					9375748d9b | ||
| 
						 | 
					201df54e79 | ||
| 
						 | 
					0b54fe477b | ||
| 
						 | 
					4119e6669e | ||
| 
						 | 
					d33e5226b3 | ||
| 
						 | 
					d73f39c706 | ||
| 
						 | 
					087b451e17 | ||
| 
						 | 
					86481c74ff | ||
| 
						 | 
					5b937fb1fa | ||
| 
						 | 
					ff828116bc | ||
| 
						 | 
					ee39616a8b | ||
| 
						 | 
					cdb53ca049 | ||
| 
						 | 
					8cf475f708 | ||
| 
						 | 
					0cb449e1d6 | ||
| 
						 | 
					e6adb7abca | ||
| 
						 | 
					cfad7dd317 | ||
| 
						 | 
					dd35224f92 | ||
| 
						 | 
					1283590eeb | ||
| 
						 | 
					dca3fe396f | ||
| 
						 | 
					8d87eae11b | ||
| 
						 | 
					fd7eaacae0 | ||
| 
						 | 
					fba33cbbe9 | ||
| 
						 | 
					950ffcd790 | ||
| 
						 | 
					c178299013 | ||
| 
						 | 
					5d17c1f588 | ||
| 
						 | 
					a75c00d94e | ||
| 
						 | 
					cd19517414 | ||
| 
						 | 
					155f39aab5 | ||
| 
						 | 
					4514d0b467 | ||
| 
						 | 
					6f4a938a31 | ||
| 
						 | 
					1303ea95dd | ||
| 
						 | 
					727fe1bd15 | ||
| 
						 | 
					64ebc977e9 | ||
| 
						 | 
					e89c50d934 | ||
| 
						 | 
					c859ddfb8f | ||
| 
						 | 
					a6126c5eda | ||
| 
						 | 
					85d9bd9106 | ||
| 
						 | 
					39e9622205 | ||
| 
						 | 
					021994c9f3 | ||
| 
						 | 
					2e7ce2a769 | ||
| 
						 | 
					84f0ff2fad | ||
| 
						 | 
					e6561e5f84 | ||
| 
						 | 
					5fa452aa74 | ||
| 
						 | 
					2225ccb146 | ||
| 
						 | 
					5aafc78847 | ||
| 
						 | 
					0d03833cff | ||
| 
						 | 
					a797d5d396 | ||
| 
						 | 
					f2494374f8 | ||
| 
						 | 
					48395ba860 | ||
| 
						 | 
					5ba5f5f94e | ||
| 
						 | 
					42ce6fd334 | ||
| 
						 | 
					f5c3ee3ae1 | ||
| 
						 | 
					3c7ece1605 | ||
| 
						 | 
					870efc49ea | ||
| 
						 | 
					085ede6d93 | ||
| 
						 | 
					4ef19d17da | ||
| 
						 | 
					223913c30a | ||
| 
						 | 
					010e4de4e1 | ||
| 
						 | 
					41134466ed | ||
| 
						 | 
					8f07747452 | ||
| 
						 | 
					eb5ce5be1e | ||
| 
						 | 
					71d855e836 | ||
| 
						 | 
					33b7ab593c | ||
| 
						 | 
					8706d834b4 | ||
| 
						 | 
					7cfab33ebb | ||
| 
						 | 
					1ababc8c7f | ||
| 
						 | 
					1f75e63c37 | ||
| 
						 | 
					cb3f9b9740 | ||
| 
						 | 
					9784353223 | ||
| 
						 | 
					7d93ca5c73 | ||
| 
						 | 
					ac20063e86 | ||
| 
						 | 
					debaec32af | ||
| 
						 | 
					0e9b71e7a9 | ||
| 
						 | 
					85f5ff3c14 | ||
| 
						 | 
					3d81f167ea | ||
| 
						 | 
					fb70a2e52d | ||
| 
						 | 
					460e85a1b5 | ||
| 
						 | 
					539b64bd57 | ||
| 
						 | 
					90e38a06a2 | ||
| 
						 | 
					09ab910630 | ||
| 
						 | 
					c15f80b33f | ||
| 
						 | 
					b2e6ba3c4a | ||
| 
						 | 
					b16f696b0e | ||
| 
						 | 
					9adfb382e8 | ||
| 
						 | 
					44368383f4 | ||
| 
						 | 
					d1ff7e0ffe | ||
| 
						 | 
					42e7db8d13 | ||
| 
						 | 
					0c17ea5755 | ||
| 
						 | 
					cdaff5b39c | ||
| 
						 | 
					2b1b970e78 | ||
| 
						 | 
					0aebc0a8e3 | ||
| 
						 | 
					c3a89f589e | ||
| 
						 | 
					971cd73fb3 | ||
| 
						 | 
					1947860d61 | ||
| 
						 | 
					55aaa421e8 | ||
| 
						 | 
					a8932706d8 | ||
| 
						 | 
					a97972aac0 | ||
| 
						 | 
					094c3d559a | ||
| 
						 | 
					6fb032b3c2 | ||
| 
						 | 
					8ca188f4d4 | ||
| 
						 | 
					746a1d8d59 | ||
| 
						 | 
					63c5e00d86 | ||
| 
						 | 
					9d2e5d6665 | ||
| 
						 | 
					f6045bf8b5 | ||
| 
						 | 
					e83f40d5c5 | ||
| 
						 | 
					e5118418b2 | ||
| 
						 | 
					7cd814d917 | ||
| 
						 | 
					78282c1a49 | ||
| 
						 | 
					fd4214ccf3 | ||
| 
						 | 
					0785945635 | ||
| 
						 | 
					967bdeae7b | ||
| 
						 | 
					452db51669 | ||
| 
						 | 
					5875ced367 | ||
| 
						 | 
					fbac6bcfd0 | ||
| 
						 | 
					0dcd3ece9d | ||
| 
						 | 
					224fff89e3 | ||
| 
						 | 
					22e73644f9 | ||
| 
						 | 
					6a0f6ab319 | ||
| 
						 | 
					88a394836f | ||
| 
						 | 
					f822c1c2e4 | ||
| 
						 | 
					1d16d980b3 | ||
| 
						 | 
					501b18f986 | ||
| 
						 | 
					21ed759e53 | ||
| 
						 | 
					8d50dfd93c | ||
| 
						 | 
					51e40dd98c | ||
| 
						 | 
					b2048379af | ||
| 
						 | 
					011539f6f1 | ||
| 
						 | 
					5457c3803f | ||
| 
						 | 
					b3d777bb6c | ||
| 
						 | 
					12e00c3054 | ||
| 
						 | 
					40b683111c | ||
| 
						 | 
					9542ca773f | ||
| 
						 | 
					8af832a496 | ||
| 
						 | 
					6836130fda | ||
| 
						 | 
					724893879f | ||
| 
						 | 
					736729f5ef | ||
| 
						 | 
					aa47966347 | ||
| 
						 | 
					d64d12afe8 | ||
| 
						 | 
					1f8df419c4 | ||
| 
						 | 
					7ba8202af5 | ||
| 
						 | 
					d7b691cf59 | ||
| 
						 | 
					7058d5e4cd | ||
| 
						 | 
					52fd508fea | ||
| 
						 | 
					41045b62dc | ||
| 
						 | 
					188ea2644a | ||
| 
						 | 
					4c8f357978 | ||
| 
						 | 
					4bb2fd6ba6 | ||
| 
						 | 
					33c9f74508 | ||
| 
						 | 
					f53fe67372 | ||
| 
						 | 
					51ff724691 | ||
| 
						 | 
					291bf93f9d | ||
| 
						 | 
					5fcd629f16 | ||
| 
						 | 
					ab90901793 | ||
| 
						 | 
					4f206fd918 | ||
| 
						 | 
					7233285437 | ||
| 
						 | 
					8e348a11c2 | ||
| 
						 | 
					085ea6d0a1 | ||
| 
						 | 
					aaf88b1895 | ||
| 
						 | 
					4f4a9412a3 | ||
| 
						 | 
					a92e039363 | ||
| 
						 | 
					33aa4ca4b7 | ||
| 
						 | 
					05658cafc7 | ||
| 
						 | 
					ff3710de66 | ||
| 
						 | 
					db8dd9f186 | ||
| 
						 | 
					e8b73ba6d1 | ||
| 
						 | 
					f1112fdf37 | ||
| 
						 | 
					a48c4f9e05 | ||
| 
						 | 
					19a521d2e9 | ||
| 
						 | 
					dd6e55ac31 | ||
| 
						 | 
					b1e63f0f14 | ||
| 
						 | 
					b0e49a4cc8 | ||
| 
						 | 
					1e94517a72 | ||
| 
						 | 
					98f60216ac | ||
| 
						 | 
					e29b712108 | ||
| 
						 | 
					a462435f2f | ||
| 
						 | 
					911b8273fe | ||
| 
						 | 
					09935e591a | ||
| 
						 | 
					4a212dba35 | ||
| 
						 | 
					aac9e85e04 | ||
| 
						 | 
					bb67a837d3 | ||
| 
						 | 
					6cde695194 | ||
| 
						 | 
					a1a1ac0bbb | ||
| 
						 | 
					9ec8bc2166 | ||
| 
						 | 
					28cd6a75e7 | ||
| 
						 | 
					4cc7aced15 | ||
| 
						 | 
					1058aeb04f | ||
| 
						 | 
					cfec0db947 | ||
| 
						 | 
					120bd6cd68 | ||
| 
						 | 
					9aef06d1b8 | ||
| 
						 | 
					e6e9dd751c | ||
| 
						 | 
					5dd677756f | ||
| 
						 | 
					b77c590910 | ||
| 
						 | 
					7e5f2822ae | 
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -11,7 +11,7 @@
 | 
			
		||||
*.bat text eol=crlf
 | 
			
		||||
 | 
			
		||||
# Denote all files that are truly binary and should not be modified.
 | 
			
		||||
chromeos/** binary
 | 
			
		||||
tools/** binary
 | 
			
		||||
*.jar binary
 | 
			
		||||
*.exe binary
 | 
			
		||||
*.apk binary
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -19,3 +19,9 @@
 | 
			
		||||
[submodule "nanopb"]
 | 
			
		||||
	path = native/jni/external/nanopb
 | 
			
		||||
	url = https://github.com/nanopb/nanopb.git
 | 
			
		||||
[submodule "mincrypt"]
 | 
			
		||||
	path = native/jni/external/mincrypt
 | 
			
		||||
	url = https://github.com/topjohnwu/mincrypt.git
 | 
			
		||||
[submodule "termux-elf-cleaner"]
 | 
			
		||||
	path = tools/termux-elf-cleaner
 | 
			
		||||
	url = https://github.com/termux/termux-elf-cleaner.git
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
 | 
			
		||||
 | 
			
		||||
## Bug Reports
 | 
			
		||||
 | 
			
		||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
 | 
			
		||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that are already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by [opening an issue on GitHub](https://github.com/topjohnwu/Magisk/issues) or directly in the thread.
 | 
			
		||||
 | 
			
		||||
## Building Environment Requirements
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@ android {
 | 
			
		||||
        applicationId 'com.topjohnwu.magisk'
 | 
			
		||||
        vectorDrawables.useSupportLibrary = true
 | 
			
		||||
        multiDexEnabled true
 | 
			
		||||
        versionName configProps['appVersion']
 | 
			
		||||
        versionCode configProps['appVersionCode'] as Integer
 | 
			
		||||
        versionName props['appVersion']
 | 
			
		||||
        versionCode props['appVersionCode'] as Integer
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
@@ -39,7 +39,7 @@ android {
 | 
			
		||||
        exclude '/META-INF/*.kotlin_module'
 | 
			
		||||
        exclude '/META-INF/rxkotlin.properties'
 | 
			
		||||
        exclude '/androidsupportmultidexversion.txt'
 | 
			
		||||
        exclude '/org/**'
 | 
			
		||||
        exclude '/org/bouncycastle/**'
 | 
			
		||||
        exclude '/kotlin/**'
 | 
			
		||||
        exclude '/kotlinx/**'
 | 
			
		||||
    }
 | 
			
		||||
@@ -55,21 +55,33 @@ androidExtensions {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation fileTree(include: ['*.jar'], dir: 'libs')
 | 
			
		||||
    implementation project(':net')
 | 
			
		||||
    implementation project(':shared')
 | 
			
		||||
    implementation project(':signing')
 | 
			
		||||
 | 
			
		||||
    implementation 'com.github.topjohnwu:jtar:1.0.0'
 | 
			
		||||
    implementation 'com.jakewharton.timber:timber:4.7.1'
 | 
			
		||||
    implementation 'com.github.skoumalcz:teanity:0.3.3'
 | 
			
		||||
    implementation 'com.ncapdevi:frag-nav:3.2.0'
 | 
			
		||||
    implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
 | 
			
		||||
 | 
			
		||||
    def vMarkwon = '3.0.1'
 | 
			
		||||
    implementation "ru.noties.markwon:core:${vMarkwon}"
 | 
			
		||||
    implementation "ru.noties.markwon:html:${vMarkwon}"
 | 
			
		||||
    implementation "ru.noties.markwon:image-svg:${vMarkwon}"
 | 
			
		||||
    implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
 | 
			
		||||
    implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
 | 
			
		||||
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
 | 
			
		||||
 | 
			
		||||
    def vLibsu = '2.5.0'
 | 
			
		||||
    implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
 | 
			
		||||
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
 | 
			
		||||
 | 
			
		||||
    def vBAdapt = '3.1.1'
 | 
			
		||||
    def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
 | 
			
		||||
    implementation "${bindingAdapter}:${vBAdapt}"
 | 
			
		||||
    implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
 | 
			
		||||
 | 
			
		||||
    def vMarkwon = '4.1.1'
 | 
			
		||||
    implementation "io.noties.markwon:core:${vMarkwon}"
 | 
			
		||||
    implementation "io.noties.markwon:html:${vMarkwon}"
 | 
			
		||||
    implementation "io.noties.markwon:image:${vMarkwon}"
 | 
			
		||||
    implementation 'com.caverock:androidsvg:1.4'
 | 
			
		||||
 | 
			
		||||
    def vLibsu = '2.5.1'
 | 
			
		||||
    implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
 | 
			
		||||
    implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
 | 
			
		||||
 | 
			
		||||
@@ -78,12 +90,13 @@ dependencies {
 | 
			
		||||
    implementation "org.koin:koin-android:${vKoin}"
 | 
			
		||||
    implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
 | 
			
		||||
 | 
			
		||||
    def vRetrofit = "2.6.0"
 | 
			
		||||
    def vRetrofit = '2.6.2'
 | 
			
		||||
    implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
 | 
			
		||||
    implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
 | 
			
		||||
    implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
 | 
			
		||||
    implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
 | 
			
		||||
 | 
			
		||||
    def vOkHttp = "3.12.3"
 | 
			
		||||
    def vOkHttp = '3.12.6'
 | 
			
		||||
    implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
 | 
			
		||||
    implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
 | 
			
		||||
 | 
			
		||||
@@ -99,16 +112,22 @@ dependencies {
 | 
			
		||||
            replacedBy('com.github.topjohnwu:room-runtime')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    def vRoom = "2.1.0"
 | 
			
		||||
    def vRoom = "2.2.0"
 | 
			
		||||
    implementation "com.github.topjohnwu:room-runtime:${vRoom}"
 | 
			
		||||
    kapt "androidx.room:room-compiler:${vRoom}"
 | 
			
		||||
 | 
			
		||||
    def vNav = "2.1.0"
 | 
			
		||||
    implementation "androidx.navigation:navigation-fragment-ktx:$vNav"
 | 
			
		||||
    implementation "androidx.navigation:navigation-ui-ktx:$vNav"
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
 | 
			
		||||
    implementation 'androidx.browser:browser:1.0.0'
 | 
			
		||||
    implementation 'androidx.preference:preference:1.0.0'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'
 | 
			
		||||
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
 | 
			
		||||
    implementation 'androidx.preference:preference:1.1.0'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.1.0-beta05'
 | 
			
		||||
    implementation 'androidx.cardview:cardview:1.0.0'
 | 
			
		||||
    implementation 'androidx.work:work-runtime:2.0.1'
 | 
			
		||||
    implementation 'androidx.transition:transition:1.2.0-alpha01'
 | 
			
		||||
    implementation 'androidx.work:work-runtime:2.2.0'
 | 
			
		||||
    implementation 'androidx.transition:transition:1.2.0'
 | 
			
		||||
    implementation 'androidx.multidex:multidex:2.0.1'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.1.0-alpha07'
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.1.0'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.1.0-beta01'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							@@ -17,9 +17,9 @@
 | 
			
		||||
#}
 | 
			
		||||
 | 
			
		||||
# Snet
 | 
			
		||||
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
 | 
			
		||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
 | 
			
		||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback {
 | 
			
		||||
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
 | 
			
		||||
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
 | 
			
		||||
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
 | 
			
		||||
  void onResponse(int);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -29,16 +29,13 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# DelegateWorker
 | 
			
		||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
 | 
			
		||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
 | 
			
		||||
 | 
			
		||||
# BootSigner
 | 
			
		||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
 | 
			
		||||
 | 
			
		||||
# Strip logging
 | 
			
		||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
 | 
			
		||||
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
 | 
			
		||||
  public *** debug(...);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Excessive obfuscation
 | 
			
		||||
-repackageclasses 'a'
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,15 @@
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    package="com.topjohnwu.magisk">
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
    <uses-permission android:name="android.permission.VIBRATE" />
 | 
			
		||||
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 | 
			
		||||
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 | 
			
		||||
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:name="a.e"
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:theme="@style/MagiskTheme"
 | 
			
		||||
        android:usesCleartextTraffic="true"
 | 
			
		||||
        tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
 | 
			
		||||
@@ -41,9 +42,9 @@
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.m"
 | 
			
		||||
            android:exported="false"
 | 
			
		||||
            android:directBootAware="true"
 | 
			
		||||
            android:excludeFromRecents="true"
 | 
			
		||||
            android:exported="false"
 | 
			
		||||
            android:theme="@style/MagiskTheme.SU" />
 | 
			
		||||
 | 
			
		||||
        <!-- Receiver -->
 | 
			
		||||
@@ -65,7 +66,8 @@
 | 
			
		||||
 | 
			
		||||
        <!-- Service -->
 | 
			
		||||
 | 
			
		||||
        <service android:name="a.j" />
 | 
			
		||||
        <service android:name="a.j"
 | 
			
		||||
            android:exported="false" />
 | 
			
		||||
 | 
			
		||||
        <!-- Hardcode GMS version -->
 | 
			
		||||
        <meta-data
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package a;
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadModuleService;
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadService;
 | 
			
		||||
 | 
			
		||||
public class j extends DownloadModuleService {
 | 
			
		||||
public class j extends DownloadService {
 | 
			
		||||
    /* stub */
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,14 @@ package a;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.worker.DelegateWorker;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.ParameterizedType;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.work.Worker;
 | 
			
		||||
import androidx.work.WorkerParameters;
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.base.DelegateWorker;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.ParameterizedType;
 | 
			
		||||
 | 
			
		||||
public abstract class w<T extends DelegateWorker> extends Worker {
 | 
			
		||||
 | 
			
		||||
    /* Wrapper class to workaround Proguard -keep class * extends Worker */
 | 
			
		||||
@@ -22,7 +22,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
 | 
			
		||||
        try {
 | 
			
		||||
            base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
 | 
			
		||||
                    .getActualTypeArguments()[0]).newInstance();
 | 
			
		||||
            base.setActualWorker(this);
 | 
			
		||||
            base.attachWorker(this);
 | 
			
		||||
        } catch (Exception ignored) {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,41 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import android.os.AsyncTask
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.appcompat.app.AppCompatDelegate
 | 
			
		||||
import androidx.multidex.MultiDex
 | 
			
		||||
import androidx.room.Room
 | 
			
		||||
import androidx.work.impl.WorkDatabase
 | 
			
		||||
import androidx.work.impl.WorkDatabase_Impl
 | 
			
		||||
import com.topjohnwu.magisk.data.database.RepoDatabase
 | 
			
		||||
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
 | 
			
		||||
import com.topjohnwu.magisk.di.ActivityTracker
 | 
			
		||||
import com.topjohnwu.magisk.di.koinModules
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.utils.LocaleManager
 | 
			
		||||
import com.topjohnwu.magisk.utils.RootUtils
 | 
			
		||||
import com.topjohnwu.magisk.utils.inject
 | 
			
		||||
import com.topjohnwu.net.Networking
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import org.koin.android.ext.koin.androidContext
 | 
			
		||||
import org.koin.core.context.startKoin
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.util.concurrent.ThreadPoolExecutor
 | 
			
		||||
 | 
			
		||||
open class App : Application(), Application.ActivityLifecycleCallbacks {
 | 
			
		||||
open class App : Application() {
 | 
			
		||||
 | 
			
		||||
    lateinit var protectedContext: Context
 | 
			
		||||
 | 
			
		||||
    @Volatile
 | 
			
		||||
    private var foreground: Activity? = null
 | 
			
		||||
    init {
 | 
			
		||||
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
 | 
			
		||||
        Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
 | 
			
		||||
        Shell.Config.verboseLogging(BuildConfig.DEBUG)
 | 
			
		||||
        Shell.Config.addInitializers(RootUtils::class.java)
 | 
			
		||||
        Shell.Config.setTimeout(2)
 | 
			
		||||
        Room.setFactory {
 | 
			
		||||
            when (it) {
 | 
			
		||||
                WorkDatabase::class.java -> WorkDatabase_Impl()
 | 
			
		||||
                RepoDatabase::class.java -> RepoDatabase_Impl()
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(base)
 | 
			
		||||
@@ -42,19 +48,7 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
 | 
			
		||||
            modules(koinModules)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protectedContext = baseContext
 | 
			
		||||
        self = this
 | 
			
		||||
        deContext = base
 | 
			
		||||
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= 24) {
 | 
			
		||||
            protectedContext = base.createDeviceProtectedStorageContext()
 | 
			
		||||
            deContext = protectedContext
 | 
			
		||||
            deContext.moveSharedPreferencesFrom(base, base.defaultPrefsName)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        registerActivityLifecycleCallbacks(this)
 | 
			
		||||
 | 
			
		||||
        Networking.init(base)
 | 
			
		||||
        registerActivityLifecycleCallbacks(get<ActivityTracker>())
 | 
			
		||||
        LocaleManager.setLocale(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -62,67 +56,4 @@ open class App : Application(), Application.ActivityLifecycleCallbacks {
 | 
			
		||||
        super.onConfigurationChanged(newConfig)
 | 
			
		||||
        LocaleManager.setLocale(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region ActivityLifecycleCallbacks
 | 
			
		||||
    override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
 | 
			
		||||
 | 
			
		||||
    override fun onActivityStarted(activity: Activity) {}
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun onActivityResumed(activity: Activity) {
 | 
			
		||||
        foreground = activity
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun onActivityPaused(activity: Activity) {
 | 
			
		||||
        foreground = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityStopped(activity: Activity) {}
 | 
			
		||||
 | 
			
		||||
    override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
 | 
			
		||||
 | 
			
		||||
    override fun onActivityDestroyed(activity: Activity) {}
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    private val Context.defaultPrefsName get() = "${packageName}_preferences"
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        @SuppressLint("StaticFieldLeak")
 | 
			
		||||
        @Deprecated("Use dependency injection")
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        lateinit var self: App
 | 
			
		||||
 | 
			
		||||
        @SuppressLint("StaticFieldLeak")
 | 
			
		||||
        @Deprecated("Use dependency injection; replace with protectedContext")
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        lateinit var deContext: Context
 | 
			
		||||
 | 
			
		||||
        @Deprecated("Use Rx or similar")
 | 
			
		||||
        @JvmField
 | 
			
		||||
        var THREAD_POOL: ThreadPoolExecutor
 | 
			
		||||
 | 
			
		||||
        init {
 | 
			
		||||
            AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
 | 
			
		||||
            Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
 | 
			
		||||
            Shell.Config.verboseLogging(BuildConfig.DEBUG)
 | 
			
		||||
            Shell.Config.addInitializers(RootUtils::class.java)
 | 
			
		||||
            Shell.Config.setTimeout(2)
 | 
			
		||||
            THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor
 | 
			
		||||
            Room.setFactory {
 | 
			
		||||
                when (it) {
 | 
			
		||||
                    WorkDatabase::class.java -> WorkDatabase_Impl()
 | 
			
		||||
                    else -> null
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Deprecated("")
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        fun foreground(): Activity? {
 | 
			
		||||
            val app: App by inject()
 | 
			
		||||
            return app.foreground
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadModuleService
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadService
 | 
			
		||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
 | 
			
		||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
 | 
			
		||||
import com.topjohnwu.magisk.ui.MainActivity
 | 
			
		||||
@@ -16,11 +16,10 @@ object ClassMap {
 | 
			
		||||
        FlashActivity::class.java to a.f::class.java,
 | 
			
		||||
        UpdateCheckService::class.java to a.g::class.java,
 | 
			
		||||
        GeneralReceiver::class.java to a.h::class.java,
 | 
			
		||||
        DownloadModuleService::class.java to a.j::class.java,
 | 
			
		||||
        DownloadService::class.java to a.j::class.java,
 | 
			
		||||
        SuRequestActivity::class.java to a.m::class.java
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    operator fun <T : Class<*>>get(c: Class<*>): T {
 | 
			
		||||
        return map.getOrElse(c) { throw IllegalArgumentException() } as T
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,20 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
import android.os.Environment
 | 
			
		||||
import android.util.Xml
 | 
			
		||||
import androidx.core.content.edit
 | 
			
		||||
import com.topjohnwu.magisk.data.database.SettingsDao
 | 
			
		||||
import com.topjohnwu.magisk.data.database.StringDao
 | 
			
		||||
import com.topjohnwu.magisk.data.repository.DBConfig
 | 
			
		||||
import com.topjohnwu.magisk.di.Protected
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.extensions.inject
 | 
			
		||||
import com.topjohnwu.magisk.extensions.packageName
 | 
			
		||||
import com.topjohnwu.magisk.model.preference.PreferenceModel
 | 
			
		||||
import com.topjohnwu.magisk.utils.*
 | 
			
		||||
import com.topjohnwu.magisk.utils.FingerprintHelper
 | 
			
		||||
import com.topjohnwu.magisk.utils.Utils
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import com.topjohnwu.superuser.io.SuFile
 | 
			
		||||
import com.topjohnwu.superuser.io.SuFileInputStream
 | 
			
		||||
@@ -39,9 +45,9 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
        const val CUSTOM_CHANNEL = "custom_channel"
 | 
			
		||||
        const val LOCALE = "locale"
 | 
			
		||||
        const val DARK_THEME = "dark_theme"
 | 
			
		||||
        const val ETAG_KEY = "ETag"
 | 
			
		||||
        const val REPO_ORDER = "repo_order"
 | 
			
		||||
        const val SHOW_SYSTEM_APP = "show_system"
 | 
			
		||||
        const val DOWNLOAD_PATH = "download_path"
 | 
			
		||||
 | 
			
		||||
        // system state
 | 
			
		||||
        const val MAGISKHIDE = "magiskhide"
 | 
			
		||||
@@ -91,9 +97,10 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val defaultChannel =
 | 
			
		||||
            if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
 | 
			
		||||
            else Value.DEFAULT_CHANNEL
 | 
			
		||||
        if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
 | 
			
		||||
        else Value.DEFAULT_CHANNEL
 | 
			
		||||
 | 
			
		||||
    var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
 | 
			
		||||
    var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
 | 
			
		||||
 | 
			
		||||
    var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
 | 
			
		||||
@@ -104,73 +111,26 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
    var darkTheme by preference(Key.DARK_THEME, true)
 | 
			
		||||
    var suReAuth by preference(Key.SU_REAUTH, false)
 | 
			
		||||
    var checkUpdate by preference(Key.CHECK_UPDATES, true)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    var magiskHide by preference(Key.MAGISKHIDE, true)
 | 
			
		||||
    var coreOnly by preference(Key.COREONLY, false)
 | 
			
		||||
    var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
 | 
			
		||||
 | 
			
		||||
    var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
 | 
			
		||||
    var locale by preference(Key.LOCALE, "")
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    var etagKey by preference(Key.ETAG_KEY, "")
 | 
			
		||||
 | 
			
		||||
    var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
 | 
			
		||||
    var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
 | 
			
		||||
    var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
 | 
			
		||||
    var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    var suManager by dbStrings(Key.SU_MANAGER, "")
 | 
			
		||||
    var suManager by dbStrings(Key.SU_MANAGER, "", true)
 | 
			
		||||
 | 
			
		||||
    // Always return a path in external storage where we can write
 | 
			
		||||
    val downloadDirectory get() =
 | 
			
		||||
        Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
 | 
			
		||||
 | 
			
		||||
    fun initialize() = prefs.edit {
 | 
			
		||||
        val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
 | 
			
		||||
        if (config.exists()) runCatching {
 | 
			
		||||
            val input = SuFileInputStream(config).buffered()
 | 
			
		||||
            val parser = Xml.newPullParser()
 | 
			
		||||
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
 | 
			
		||||
            parser.setInput(input, "UTF-8")
 | 
			
		||||
            parser.nextTag()
 | 
			
		||||
            parser.require(XmlPullParser.START_TAG, null, "map")
 | 
			
		||||
            while (parser.next() != XmlPullParser.END_TAG) {
 | 
			
		||||
                if (parser.eventType != XmlPullParser.START_TAG)
 | 
			
		||||
                    continue
 | 
			
		||||
                val key: String = parser.getAttributeValue(null, "name")
 | 
			
		||||
                val value: String = parser.getAttributeValue(null, "value")
 | 
			
		||||
                when (parser.name) {
 | 
			
		||||
                    "string" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "string")
 | 
			
		||||
                        putString(key, parser.nextText())
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "string")
 | 
			
		||||
                    }
 | 
			
		||||
                    "boolean" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "boolean")
 | 
			
		||||
                        putBoolean(key, value.toBoolean())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "boolean")
 | 
			
		||||
                    }
 | 
			
		||||
                    "int" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "int")
 | 
			
		||||
                        putInt(key, value.toInt())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "int")
 | 
			
		||||
                    }
 | 
			
		||||
                    "long" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "long")
 | 
			
		||||
                        putLong(key, value.toLong())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "long")
 | 
			
		||||
                    }
 | 
			
		||||
                    "float" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "int")
 | 
			
		||||
                        putFloat(key, value.toFloat())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "int")
 | 
			
		||||
                    }
 | 
			
		||||
                    else -> parser.next()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            config.delete()
 | 
			
		||||
        }
 | 
			
		||||
        remove(Key.ETAG_KEY)
 | 
			
		||||
        parsePrefs(this)
 | 
			
		||||
 | 
			
		||||
        if (!prefs.contains(Key.UPDATE_CHANNEL))
 | 
			
		||||
            putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
 | 
			
		||||
 | 
			
		||||
@@ -184,12 +144,64 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
        putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
 | 
			
		||||
        val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
 | 
			
		||||
        if (config.exists()) runCatching {
 | 
			
		||||
            val input = SuFileInputStream(config).buffered()
 | 
			
		||||
            val parser = Xml.newPullParser()
 | 
			
		||||
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
 | 
			
		||||
            parser.setInput(input, "UTF-8")
 | 
			
		||||
            parser.nextTag()
 | 
			
		||||
            parser.require(XmlPullParser.START_TAG, null, "map")
 | 
			
		||||
            while (parser.next() != XmlPullParser.END_TAG) {
 | 
			
		||||
                if (parser.eventType != XmlPullParser.START_TAG)
 | 
			
		||||
                    continue
 | 
			
		||||
                val key: String = parser.getAttributeValue(null, "name")
 | 
			
		||||
                fun value() = parser.getAttributeValue(null, "value")!!
 | 
			
		||||
                when (parser.name) {
 | 
			
		||||
                    "string" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "string")
 | 
			
		||||
                        putString(key, parser.nextText())
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "string")
 | 
			
		||||
                    }
 | 
			
		||||
                    "boolean" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "boolean")
 | 
			
		||||
                        putBoolean(key, value().toBoolean())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "boolean")
 | 
			
		||||
                    }
 | 
			
		||||
                    "int" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "int")
 | 
			
		||||
                        putInt(key, value().toInt())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "int")
 | 
			
		||||
                    }
 | 
			
		||||
                    "long" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "long")
 | 
			
		||||
                        putLong(key, value().toLong())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "long")
 | 
			
		||||
                    }
 | 
			
		||||
                    "float" -> {
 | 
			
		||||
                        parser.require(XmlPullParser.START_TAG, null, "int")
 | 
			
		||||
                        putFloat(key, value().toFloat())
 | 
			
		||||
                        parser.nextTag()
 | 
			
		||||
                        parser.require(XmlPullParser.END_TAG, null, "int")
 | 
			
		||||
                    }
 | 
			
		||||
                    else -> parser.next()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            config.delete()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun export() {
 | 
			
		||||
        // Flush prefs to disk
 | 
			
		||||
        prefs.edit().apply()
 | 
			
		||||
        val xml = File("${get<Context>(Protected).filesDir.parent}/shared_prefs",
 | 
			
		||||
                "${packageName}_preferences.xml")
 | 
			
		||||
        prefs.edit().commit()
 | 
			
		||||
        val xml = File(
 | 
			
		||||
            "${get<Context>(Protected).filesDir.parent}/shared_prefs",
 | 
			
		||||
            "${packageName}_preferences.xml"
 | 
			
		||||
        )
 | 
			
		||||
        Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,27 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import android.os.Environment
 | 
			
		||||
import android.os.Process
 | 
			
		||||
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
object Const {
 | 
			
		||||
 | 
			
		||||
    const val DEBUG_TAG = "MagiskManager"
 | 
			
		||||
 | 
			
		||||
    // Paths
 | 
			
		||||
    const val MAGISK_PATH = "/sbin/.magisk/img"
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)!!
 | 
			
		||||
    @JvmField
 | 
			
		||||
    var MAGISK_DISABLE_FILE = File("xxx")
 | 
			
		||||
    const val TMP_FOLDER_PATH = "/dev/tmp"
 | 
			
		||||
    const val MAGISK_LOG = "/cache/magisk.log"
 | 
			
		||||
 | 
			
		||||
    // Versions
 | 
			
		||||
    const val SNET_EXT_VER = 12
 | 
			
		||||
    const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
 | 
			
		||||
    const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
 | 
			
		||||
    const val SNET_EXT_VER = 13
 | 
			
		||||
    const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
 | 
			
		||||
    const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
 | 
			
		||||
 | 
			
		||||
    // Misc
 | 
			
		||||
    const val ANDROID_MANIFEST = "AndroidManifest.xml"
 | 
			
		||||
    const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
 | 
			
		||||
    const val MANAGER_CONFIGS = ".tmp.magisk.config"
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val USER_ID = Process.myUid() / 100000
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        EXTERNAL_PATH.mkdirs()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object MagiskVersion {
 | 
			
		||||
        const val MIN_SUPPORT = 18000
 | 
			
		||||
    }
 | 
			
		||||
@@ -53,35 +41,28 @@ object Const {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object Url {
 | 
			
		||||
        @Deprecated("This shouldn't be used. There's literally no need for it")
 | 
			
		||||
        const val REPO_URL =
 | 
			
		||||
            "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d"
 | 
			
		||||
        const val FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"
 | 
			
		||||
        const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
 | 
			
		||||
        const val MODULE_INSTALLER =
 | 
			
		||||
            "https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh"
 | 
			
		||||
        const val PAYPAL_URL = "https://www.paypal.me/topjohnwu"
 | 
			
		||||
        const val PATREON_URL = "https://www.patreon.com/topjohnwu"
 | 
			
		||||
        const val TWITTER_URL = "https://twitter.com/topjohnwu"
 | 
			
		||||
        const val XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"
 | 
			
		||||
        const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
 | 
			
		||||
        @JvmField
 | 
			
		||||
        val BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl")
 | 
			
		||||
        const val GITHUB_RAW_API_URL = "https://raw.githubusercontent.com/"
 | 
			
		||||
 | 
			
		||||
        private fun getRaw(where: String, name: String) =
 | 
			
		||||
            "${GITHUB_RAW_API_URL}topjohnwu/magisk_files/$where/$name"
 | 
			
		||||
        const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
 | 
			
		||||
        const val GITHUB_API_URL = "https://api.github.com/users/Magisk-Modules-Repo/"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object Key {
 | 
			
		||||
        // others
 | 
			
		||||
        const val LINK_KEY = "Link"
 | 
			
		||||
        const val IF_NONE_MATCH = "If-None-Match"
 | 
			
		||||
        const val ETAG_KEY = "ETag"
 | 
			
		||||
        // intents
 | 
			
		||||
        const val OPEN_SECTION = "section"
 | 
			
		||||
        const val INTENT_SET_NAME = "filename"
 | 
			
		||||
        const val INTENT_SET_LINK = "link"
 | 
			
		||||
        const val INTENT_SET_APP = "app_json"
 | 
			
		||||
        const val FLASH_ACTION = "action"
 | 
			
		||||
        const val FLASH_DATA = "additional_data"
 | 
			
		||||
        const val DISMISS_ID = "dismiss_id"
 | 
			
		||||
        const val BROADCAST_MANAGER_UPDATE = "manager_update"
 | 
			
		||||
        const val BROADCAST_REBOOT = "reboot"
 | 
			
		||||
    }
 | 
			
		||||
@@ -94,5 +75,4 @@ object Const {
 | 
			
		||||
        const val UNINSTALL = "uninstall"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
package com.topjohnwu.magisk;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.UpdateInfo;
 | 
			
		||||
import com.topjohnwu.superuser.Shell;
 | 
			
		||||
import com.topjohnwu.superuser.ShellUtils;
 | 
			
		||||
 | 
			
		||||
public final class Info {
 | 
			
		||||
 | 
			
		||||
    public static int magiskVersionCode = -1;
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    public static String magiskVersionString = "";
 | 
			
		||||
 | 
			
		||||
    public static UpdateInfo remote = new UpdateInfo();
 | 
			
		||||
 | 
			
		||||
    public static boolean keepVerity = false;
 | 
			
		||||
    public static boolean keepEnc = false;
 | 
			
		||||
    public static boolean recovery = false;
 | 
			
		||||
 | 
			
		||||
    public static void loadMagiskInfo() {
 | 
			
		||||
        try {
 | 
			
		||||
            magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
 | 
			
		||||
            magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
 | 
			
		||||
            Config.setMagiskHide(Shell.su("magiskhide --status").exec().isSuccess());
 | 
			
		||||
        } catch (NumberFormatException ignored) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								app/src/main/java/com/topjohnwu/magisk/Info.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/src/main/java/com/topjohnwu/magisk/Info.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import com.topjohnwu.superuser.ShellUtils
 | 
			
		||||
 | 
			
		||||
object Info {
 | 
			
		||||
 | 
			
		||||
    var magiskVersionCode = -1
 | 
			
		||||
 | 
			
		||||
    var magiskVersionString = ""
 | 
			
		||||
 | 
			
		||||
    var remote = UpdateInfo()
 | 
			
		||||
 | 
			
		||||
    var keepVerity = false
 | 
			
		||||
    var keepEnc = false
 | 
			
		||||
    var recovery = false
 | 
			
		||||
 | 
			
		||||
    fun loadMagiskInfo() {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
 | 
			
		||||
            magiskVersionCode = ShellUtils.fastCmd("magisk -V").toInt()
 | 
			
		||||
            Config.magiskHide = Shell.su("magiskhide --status").exec().isSuccess
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.Manifest
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.appcompat.app.AppCompatDelegate
 | 
			
		||||
import androidx.collection.SparseArrayCompat
 | 
			
		||||
import androidx.core.app.ActivityCompat
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.databinding.DataBindingUtil
 | 
			
		||||
import androidx.databinding.ViewDataBinding
 | 
			
		||||
import com.topjohnwu.magisk.BR
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
 | 
			
		||||
import com.topjohnwu.magisk.extensions.set
 | 
			
		||||
import com.topjohnwu.magisk.model.events.EventHandler
 | 
			
		||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
 | 
			
		||||
import com.topjohnwu.magisk.utils.LocaleManager
 | 
			
		||||
import com.topjohnwu.magisk.utils.currentLocale
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
 | 
			
		||||
 | 
			
		||||
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
 | 
			
		||||
        AppCompatActivity(), EventHandler {
 | 
			
		||||
 | 
			
		||||
    protected lateinit var binding: Binding
 | 
			
		||||
    protected abstract val layoutRes: Int
 | 
			
		||||
    protected abstract val viewModel: ViewModel
 | 
			
		||||
    protected open val snackbarView get() = binding.root
 | 
			
		||||
    protected open val navHostId: Int = 0
 | 
			
		||||
    protected open val defaultPosition: Int = 0
 | 
			
		||||
 | 
			
		||||
    private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        val theme = if (Config.darkTheme) {
 | 
			
		||||
            AppCompatDelegate.MODE_NIGHT_YES
 | 
			
		||||
        } else {
 | 
			
		||||
            AppCompatDelegate.MODE_NIGHT_NO
 | 
			
		||||
        }
 | 
			
		||||
        AppCompatDelegate.setDefaultNightMode(theme)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun applyOverrideConfiguration(config: Configuration?) {
 | 
			
		||||
        // Force applying our preferred local
 | 
			
		||||
        config?.setLocale(currentLocale)
 | 
			
		||||
        super.applyOverrideConfiguration(config)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(LocaleManager.getLocaleContext(base))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        viewModel.viewEvents.observe(this, viewEventObserver)
 | 
			
		||||
 | 
			
		||||
        binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
 | 
			
		||||
            setVariable(BR.viewModel, viewModel)
 | 
			
		||||
            lifecycleOwner = this@BaseActivity
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
 | 
			
		||||
        val request = PermissionRequestBuilder().apply(builder).build()
 | 
			
		||||
        val ungranted = permissions.filter {
 | 
			
		||||
            ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ungranted.isEmpty()) {
 | 
			
		||||
            request.onSuccess()
 | 
			
		||||
        } else {
 | 
			
		||||
            val requestCode = Random.nextInt(256, 512)
 | 
			
		||||
            resultCallbacks[requestCode] =  { result, _ ->
 | 
			
		||||
                if (result > 0)
 | 
			
		||||
                    request.onSuccess()
 | 
			
		||||
                else
 | 
			
		||||
                    request.onFailure()
 | 
			
		||||
            }
 | 
			
		||||
            ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), requestCode)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
 | 
			
		||||
        withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRequestPermissionsResult(
 | 
			
		||||
            requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
 | 
			
		||||
        var success = true
 | 
			
		||||
        for (res in grantResults) {
 | 
			
		||||
            if (res != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                success = false
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        resultCallbacks[requestCode]?.apply {
 | 
			
		||||
            resultCallbacks.remove(requestCode)
 | 
			
		||||
            invoke(this@BaseActivity, if (success) 1 else -1, null)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
 | 
			
		||||
        super.onActivityResult(requestCode, resultCode, data)
 | 
			
		||||
        resultCallbacks[requestCode]?.apply {
 | 
			
		||||
            resultCallbacks.remove(requestCode)
 | 
			
		||||
            invoke(this@BaseActivity, resultCode, data)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
 | 
			
		||||
        resultCallbacks[requestCode] = listener
 | 
			
		||||
        startActivityForResult(intent, requestCode)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseFragment.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.annotation.CallSuper
 | 
			
		||||
import androidx.databinding.DataBindingUtil
 | 
			
		||||
import androidx.databinding.ViewDataBinding
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import com.topjohnwu.magisk.BR
 | 
			
		||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
 | 
			
		||||
import com.topjohnwu.magisk.model.events.EventHandler
 | 
			
		||||
import com.topjohnwu.magisk.model.events.ViewEvent
 | 
			
		||||
 | 
			
		||||
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
 | 
			
		||||
    Fragment(), EventHandler {
 | 
			
		||||
 | 
			
		||||
    protected val activity get() = requireActivity() as BaseActivity<*, *>
 | 
			
		||||
    protected lateinit var binding: Binding
 | 
			
		||||
    protected abstract val layoutRes: Int
 | 
			
		||||
    protected abstract val viewModel: ViewModel
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        viewModel.viewEvents.observe(this, viewEventObserver)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(
 | 
			
		||||
            inflater: LayoutInflater,
 | 
			
		||||
            container: ViewGroup?,
 | 
			
		||||
            savedInstanceState: Bundle?
 | 
			
		||||
    ): View? {
 | 
			
		||||
        binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
 | 
			
		||||
            setVariable(BR.viewModel, viewModel)
 | 
			
		||||
            lifecycleOwner = this@BaseFragment
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return binding.root
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @CallSuper
 | 
			
		||||
    override fun onEventDispatched(event: ViewEvent) {
 | 
			
		||||
        super.onEventDispatched(event)
 | 
			
		||||
        activity.onEventDispatched(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun onBackPressed(): Boolean = false
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.preference.*
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import org.koin.android.ext.android.inject
 | 
			
		||||
 | 
			
		||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
 | 
			
		||||
    SharedPreferences.OnSharedPreferenceChangeListener {
 | 
			
		||||
 | 
			
		||||
    protected val prefs: SharedPreferences by inject()
 | 
			
		||||
    protected val activity get() = requireActivity() as BaseActivity<*, *>
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(
 | 
			
		||||
        inflater: LayoutInflater,
 | 
			
		||||
        container: ViewGroup?,
 | 
			
		||||
        savedInstanceState: Bundle?
 | 
			
		||||
    ): View? {
 | 
			
		||||
        val v = super.onCreateView(inflater, container, savedInstanceState)
 | 
			
		||||
        prefs.registerOnSharedPreferenceChangeListener(this)
 | 
			
		||||
        return v
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        prefs.unregisterOnSharedPreferenceChangeListener(this)
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
 | 
			
		||||
        preference.isIconSpaceReserved = false
 | 
			
		||||
        if (preference is PreferenceGroup)
 | 
			
		||||
            for (i in 0 until preference.preferenceCount)
 | 
			
		||||
                setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
 | 
			
		||||
        if (preferenceScreen != null)
 | 
			
		||||
            setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
 | 
			
		||||
        super.setPreferenceScreen(preferenceScreen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
 | 
			
		||||
            object : PreferenceGroupAdapter(preferenceScreen) {
 | 
			
		||||
                @SuppressLint("RestrictedApi")
 | 
			
		||||
                override fun onPreferenceHierarchyChange(preference: Preference?) {
 | 
			
		||||
                    if (preference != null)
 | 
			
		||||
                        setAllPreferencesToAvoidHavingExtraSpace(preference)
 | 
			
		||||
                    super.onPreferenceHierarchyChange(preference)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Network
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.annotation.MainThread
 | 
			
		||||
import androidx.annotation.RequiresApi
 | 
			
		||||
import androidx.work.Data
 | 
			
		||||
import androidx.work.ListenableWorker
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
abstract class DelegateWorker {
 | 
			
		||||
 | 
			
		||||
    private lateinit var worker: ListenableWorker
 | 
			
		||||
 | 
			
		||||
    val applicationContext: Context
 | 
			
		||||
        get() = worker.applicationContext
 | 
			
		||||
 | 
			
		||||
    val id: UUID
 | 
			
		||||
        get() = worker.id
 | 
			
		||||
 | 
			
		||||
    val inputData: Data
 | 
			
		||||
        get() = worker.inputData
 | 
			
		||||
 | 
			
		||||
    val tags: Set<String>
 | 
			
		||||
        get() = worker.tags
 | 
			
		||||
 | 
			
		||||
    val triggeredContentUris: List<Uri>
 | 
			
		||||
        @RequiresApi(24)
 | 
			
		||||
        get() = worker.triggeredContentUris
 | 
			
		||||
 | 
			
		||||
    val triggeredContentAuthorities: List<String>
 | 
			
		||||
        @RequiresApi(24)
 | 
			
		||||
        get() = worker.triggeredContentAuthorities
 | 
			
		||||
 | 
			
		||||
    val network: Network?
 | 
			
		||||
        @RequiresApi(28)
 | 
			
		||||
        get() = worker.network
 | 
			
		||||
 | 
			
		||||
    val runAttemptCount: Int
 | 
			
		||||
        get() = worker.runAttemptCount
 | 
			
		||||
 | 
			
		||||
    val isStopped: Boolean
 | 
			
		||||
        get() = worker.isStopped
 | 
			
		||||
 | 
			
		||||
    abstract fun doWork(): ListenableWorker.Result
 | 
			
		||||
 | 
			
		||||
    fun onStopped() {}
 | 
			
		||||
 | 
			
		||||
    fun attachWorker(w: ListenableWorker) {
 | 
			
		||||
        worker = w
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @MainThread
 | 
			
		||||
    fun startWork(): ListenableFuture<ListenableWorker.Result> {
 | 
			
		||||
        return worker.startWork()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,29 @@
 | 
			
		||||
package com.topjohnwu.magisk.ui.base
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import com.skoumal.teanity.extensions.doOnSubscribeUi
 | 
			
		||||
import com.skoumal.teanity.viewmodel.LoadingViewModel
 | 
			
		||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
 | 
			
		||||
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.extensions.subscribeK
 | 
			
		||||
import com.topjohnwu.magisk.model.events.BackPressEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.PermissionEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
 | 
			
		||||
import com.topjohnwu.magisk.utils.KObservableField
 | 
			
		||||
import io.reactivex.Observable
 | 
			
		||||
import io.reactivex.subjects.PublishSubject
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
abstract class MagiskViewModel : LoadingViewModel() {
 | 
			
		||||
abstract class BaseViewModel(
 | 
			
		||||
    initialState: State = State.LOADING
 | 
			
		||||
) : LoadingViewModel(initialState) {
 | 
			
		||||
 | 
			
		||||
    val isConnected = KObservableField(false)
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        ReactiveNetwork.observeNetworkConnectivity(get())
 | 
			
		||||
            .subscribeK { isConnected.value = it.available() }
 | 
			
		||||
            .add()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun withView(action: Activity.() -> Unit) {
 | 
			
		||||
        ViewActionEvent(action).publish()
 | 
			
		||||
@@ -0,0 +1,78 @@
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
import androidx.databinding.Bindable
 | 
			
		||||
import com.topjohnwu.magisk.BR
 | 
			
		||||
import io.reactivex.*
 | 
			
		||||
 | 
			
		||||
abstract class LoadingViewModel(defaultState: State = State.LOADING) :
 | 
			
		||||
    StatefulViewModel<LoadingViewModel.State>(defaultState) {
 | 
			
		||||
 | 
			
		||||
    val loading @Bindable get() = state == State.LOADING
 | 
			
		||||
    val loaded @Bindable get() = state == State.LOADED
 | 
			
		||||
    val loadingFailed @Bindable get() = state == State.LOADING_FAILED
 | 
			
		||||
 | 
			
		||||
    @Deprecated(
 | 
			
		||||
        "Direct access is recommended since 0.2. This access method will be removed in 1.0",
 | 
			
		||||
        ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
 | 
			
		||||
        DeprecationLevel.WARNING
 | 
			
		||||
    )
 | 
			
		||||
    fun setLoading() {
 | 
			
		||||
        state = State.LOADING
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated(
 | 
			
		||||
        "Direct access is recommended since 0.2. This access method will be removed in 1.0",
 | 
			
		||||
        ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
 | 
			
		||||
        DeprecationLevel.WARNING
 | 
			
		||||
    )
 | 
			
		||||
    fun setLoaded() {
 | 
			
		||||
        state = State.LOADED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated(
 | 
			
		||||
        "Direct access is recommended since 0.2. This access method will be removed in 1.0",
 | 
			
		||||
        ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
 | 
			
		||||
        DeprecationLevel.WARNING
 | 
			
		||||
    )
 | 
			
		||||
    fun setLoadingFailed() {
 | 
			
		||||
        state = State.LOADING_FAILED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun notifyStateChanged() {
 | 
			
		||||
        notifyPropertyChanged(BR.loading)
 | 
			
		||||
        notifyPropertyChanged(BR.loaded)
 | 
			
		||||
        notifyPropertyChanged(BR.loadingFailed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum class State {
 | 
			
		||||
        LOADED, LOADING, LOADING_FAILED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //region Rx
 | 
			
		||||
    protected fun <T> Observable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
 | 
			
		||||
        doOnSubscribe { viewModel.state = State.LOADING }
 | 
			
		||||
            .doOnError { viewModel.state = State.LOADING_FAILED }
 | 
			
		||||
            .doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
 | 
			
		||||
    protected fun <T> Single<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
 | 
			
		||||
        doOnSubscribe { viewModel.state = State.LOADING }
 | 
			
		||||
            .doOnError { viewModel.state = State.LOADING_FAILED }
 | 
			
		||||
            .doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
 | 
			
		||||
    protected fun <T> Maybe<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
 | 
			
		||||
        doOnSubscribe { viewModel.state = State.LOADING }
 | 
			
		||||
            .doOnError { viewModel.state = State.LOADING_FAILED }
 | 
			
		||||
            .doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
            .doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
 | 
			
		||||
    protected fun <T> Flowable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
 | 
			
		||||
        doOnSubscribe { viewModel.state = State.LOADING }
 | 
			
		||||
            .doOnError { viewModel.state = State.LOADING_FAILED }
 | 
			
		||||
            .doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
 | 
			
		||||
    protected fun Completable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
 | 
			
		||||
        doOnSubscribe { viewModel.state = State.LOADING }
 | 
			
		||||
            .doOnError { viewModel.state = State.LOADING_FAILED }
 | 
			
		||||
            .doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
 | 
			
		||||
    //endregion
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
import androidx.databinding.Observable
 | 
			
		||||
import androidx.databinding.PropertyChangeRegistry
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy of [android.databinding.BaseObservable] which extends [ViewModel]
 | 
			
		||||
 */
 | 
			
		||||
abstract class ObservableViewModel : TeanityViewModel(), Observable {
 | 
			
		||||
 | 
			
		||||
    @Transient
 | 
			
		||||
    private var callbacks: PropertyChangeRegistry? = null
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
 | 
			
		||||
        if (callbacks == null) {
 | 
			
		||||
            callbacks = PropertyChangeRegistry()
 | 
			
		||||
        }
 | 
			
		||||
        callbacks?.add(callback)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
 | 
			
		||||
        callbacks?.remove(callback)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies listeners that all properties of this instance have changed.
 | 
			
		||||
     */
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    fun notifyChange() {
 | 
			
		||||
        callbacks?.notifyCallbacks(this, 0, null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies listeners that a specific property has changed. The getter for the property
 | 
			
		||||
     * that changes should be marked with [android.databinding.Bindable] to generate a field in
 | 
			
		||||
     * `BR` to be used as `fieldId`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldId The generated BR id for the Bindable field.
 | 
			
		||||
     */
 | 
			
		||||
    fun notifyPropertyChanged(fieldId: Int) {
 | 
			
		||||
        callbacks?.notifyCallbacks(this, fieldId, null)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
abstract class StatefulViewModel<State : Enum<*>>(
 | 
			
		||||
    val defaultState: State
 | 
			
		||||
) : ObservableViewModel() {
 | 
			
		||||
 | 
			
		||||
    var state: State = defaultState
 | 
			
		||||
        set(value) {
 | 
			
		||||
            field = value
 | 
			
		||||
            notifyStateChanged()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    open fun notifyStateChanged() = Unit
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import com.topjohnwu.magisk.model.events.SimpleViewEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.ViewEvent
 | 
			
		||||
import io.reactivex.disposables.CompositeDisposable
 | 
			
		||||
import io.reactivex.disposables.Disposable
 | 
			
		||||
 | 
			
		||||
abstract class TeanityViewModel : ViewModel() {
 | 
			
		||||
 | 
			
		||||
    private val disposables = CompositeDisposable()
 | 
			
		||||
    private val _viewEvents = MutableLiveData<ViewEvent>()
 | 
			
		||||
    val viewEvents: LiveData<ViewEvent> get() = _viewEvents
 | 
			
		||||
 | 
			
		||||
    override fun onCleared() {
 | 
			
		||||
        super.onCleared()
 | 
			
		||||
        disposables.clear()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun <Event : ViewEvent> Event.publish() {
 | 
			
		||||
        _viewEvents.value = this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun Int.publish() {
 | 
			
		||||
        _viewEvents.value = SimpleViewEvent(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun Disposable.add() {
 | 
			
		||||
        disposables.add(this)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,10 +4,10 @@ import android.content.Context
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.data.database.base.*
 | 
			
		||||
import com.topjohnwu.magisk.extensions.now
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.toMap
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.toPolicy
 | 
			
		||||
import com.topjohnwu.magisk.utils.now
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
@@ -62,20 +62,14 @@ class PolicyDao(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
 | 
			
		||||
        val taskResult = runCatching { toPolicy(context.packageManager) }
 | 
			
		||||
        val result = taskResult.getOrNull()
 | 
			
		||||
        val exception = taskResult.exceptionOrNull()
 | 
			
		||||
 | 
			
		||||
        Timber.e(exception)
 | 
			
		||||
 | 
			
		||||
        when (exception) {
 | 
			
		||||
            is PackageManager.NameNotFoundException -> {
 | 
			
		||||
        return runCatching { toPolicy(context.packageManager) }.getOrElse {
 | 
			
		||||
            Timber.e(it)
 | 
			
		||||
            if (it is PackageManager.NameNotFoundException) {
 | 
			
		||||
                val uid = getOrElse("uid") { null } ?: return null
 | 
			
		||||
                delete(uid).subscribe()
 | 
			
		||||
            }
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
package com.topjohnwu.magisk.data.database
 | 
			
		||||
 | 
			
		||||
import androidx.room.*
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.module.Repo
 | 
			
		||||
 | 
			
		||||
@Dao
 | 
			
		||||
abstract class RepoDao {
 | 
			
		||||
 | 
			
		||||
    val repoIDList get() = getRepoID().map { it.id }
 | 
			
		||||
 | 
			
		||||
    val repos: List<Repo> get() = when (Config.repoOrder) {
 | 
			
		||||
            Config.Value.ORDER_NAME -> getReposNameOrder()
 | 
			
		||||
            else -> getReposDateOrder()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    var etagKey: String
 | 
			
		||||
        set(etag) = addEtagRaw(RepoEtag(0, etag))
 | 
			
		||||
        get() = etagRaw()?.key.orEmpty()
 | 
			
		||||
 | 
			
		||||
    fun clear() {
 | 
			
		||||
        clearRepos()
 | 
			
		||||
        clearEtag()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT * FROM repos ORDER BY last_update DESC")
 | 
			
		||||
    protected abstract fun getReposDateOrder(): List<Repo>
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE")
 | 
			
		||||
    protected abstract fun getReposNameOrder(): List<Repo>
 | 
			
		||||
 | 
			
		||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
			
		||||
    abstract fun addRepo(repo: Repo)
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT * FROM repos WHERE id = :id")
 | 
			
		||||
    abstract fun getRepo(id: String): Repo?
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT id FROM repos")
 | 
			
		||||
    protected abstract fun getRepoID(): List<RepoID>
 | 
			
		||||
 | 
			
		||||
    @Delete
 | 
			
		||||
    abstract fun removeRepo(repo: Repo)
 | 
			
		||||
 | 
			
		||||
    @Query("DELETE FROM repos WHERE id = :id")
 | 
			
		||||
    abstract fun removeRepo(id: String)
 | 
			
		||||
 | 
			
		||||
    @Query("DELETE FROM repos WHERE id IN (:idList)")
 | 
			
		||||
    abstract fun removeRepos(idList: Collection<String>)
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT * FROM etag")
 | 
			
		||||
    protected abstract fun etagRaw(): RepoEtag?
 | 
			
		||||
 | 
			
		||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
			
		||||
    protected abstract fun addEtagRaw(etag: RepoEtag)
 | 
			
		||||
 | 
			
		||||
    @Query("DELETE FROM repos")
 | 
			
		||||
    protected abstract fun clearRepos()
 | 
			
		||||
 | 
			
		||||
    @Query("DELETE FROM etag")
 | 
			
		||||
    protected abstract fun clearEtag()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class RepoID(
 | 
			
		||||
    @PrimaryKey val id: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Entity(tableName = "etag")
 | 
			
		||||
data class RepoEtag(
 | 
			
		||||
    @PrimaryKey val id: Int,
 | 
			
		||||
    val key: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
package com.topjohnwu.magisk.data.database
 | 
			
		||||
 | 
			
		||||
import androidx.room.Database
 | 
			
		||||
import androidx.room.RoomDatabase
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.module.Repo
 | 
			
		||||
 | 
			
		||||
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
 | 
			
		||||
abstract class RepoDatabase : RoomDatabase() {
 | 
			
		||||
 | 
			
		||||
    abstract fun repoDao() : RepoDao
 | 
			
		||||
}
 | 
			
		||||
@@ -1,109 +0,0 @@
 | 
			
		||||
package com.topjohnwu.magisk.data.database
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.database.Cursor
 | 
			
		||||
import android.database.sqlite.SQLiteDatabase
 | 
			
		||||
import android.database.sqlite.SQLiteOpenHelper
 | 
			
		||||
import androidx.core.content.edit
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.Repo
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
@Deprecated("")
 | 
			
		||||
class RepoDatabaseHelper
 | 
			
		||||
constructor(context: Context) : SQLiteOpenHelper(context, "repo.db", null, DATABASE_VER) {
 | 
			
		||||
 | 
			
		||||
    private val mDb: SQLiteDatabase = writableDatabase
 | 
			
		||||
 | 
			
		||||
    val rawCursor: Cursor
 | 
			
		||||
        @Deprecated("")
 | 
			
		||||
        get() = mDb.query(TABLE_NAME, null, null, null, null, null, null)
 | 
			
		||||
 | 
			
		||||
    val repoCursor: Cursor
 | 
			
		||||
        @Deprecated("")
 | 
			
		||||
        get() {
 | 
			
		||||
            var orderBy: String? = null
 | 
			
		||||
            when (Config.repoOrder) {
 | 
			
		||||
                Config.Value.ORDER_NAME -> orderBy = "name COLLATE NOCASE"
 | 
			
		||||
                Config.Value.ORDER_DATE -> orderBy = "last_update DESC"
 | 
			
		||||
            }
 | 
			
		||||
            return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    val repoIDSet: Set<String>
 | 
			
		||||
        @Deprecated("")
 | 
			
		||||
        get() {
 | 
			
		||||
            val set = HashSet<String>(300)
 | 
			
		||||
            mDb.query(TABLE_NAME, null, null, null, null, null, null).use { c ->
 | 
			
		||||
                while (c.moveToNext()) {
 | 
			
		||||
                    set.add(c.getString(c.getColumnIndex("id")))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return set
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(db: SQLiteDatabase) {
 | 
			
		||||
        onUpgrade(db, 0, DATABASE_VER)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
 | 
			
		||||
        if (oldVersion != newVersion) {
 | 
			
		||||
            // Nuke old DB and create new table
 | 
			
		||||
            db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
 | 
			
		||||
            db.execSQL(
 | 
			
		||||
                    "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
 | 
			
		||||
                            "(id TEXT, name TEXT, version TEXT, versionCode INT, " +
 | 
			
		||||
                            "author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))")
 | 
			
		||||
            Config.prefs.edit {
 | 
			
		||||
                remove(Config.Key.ETAG_KEY)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
 | 
			
		||||
        onUpgrade(db, 0, DATABASE_VER)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun clearRepo() {
 | 
			
		||||
        mDb.delete(TABLE_NAME, null, null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun removeRepo(id: String) {
 | 
			
		||||
        mDb.delete(TABLE_NAME, "id=?", arrayOf(id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun removeRepo(repo: Repo) {
 | 
			
		||||
        removeRepo(repo.id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun removeRepo(list: Iterable<String>) {
 | 
			
		||||
        list.forEach {
 | 
			
		||||
            mDb.delete(TABLE_NAME, "id=?", arrayOf(it))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun addRepo(repo: Repo) {
 | 
			
		||||
        mDb.replace(TABLE_NAME, null, repo.contentValues)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated("")
 | 
			
		||||
    fun getRepo(id: String): Repo? {
 | 
			
		||||
        mDb.query(TABLE_NAME, null, "id=?", arrayOf(id), null, null, null).use { c ->
 | 
			
		||||
            if (c.moveToNext()) {
 | 
			
		||||
                return Repo(c)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val DATABASE_VER = 5
 | 
			
		||||
        private val TABLE_NAME = "repos"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,15 +2,14 @@ package com.topjohnwu.magisk.data.network
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
 | 
			
		||||
import com.topjohnwu.magisk.tasks.GithubRepoInfo
 | 
			
		||||
import io.reactivex.Flowable
 | 
			
		||||
import io.reactivex.Single
 | 
			
		||||
import okhttp3.ResponseBody
 | 
			
		||||
import retrofit2.http.GET
 | 
			
		||||
import retrofit2.http.Path
 | 
			
		||||
import retrofit2.http.Streaming
 | 
			
		||||
import retrofit2.http.Url
 | 
			
		||||
import retrofit2.adapter.rxjava2.Result
 | 
			
		||||
import retrofit2.http.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface GithubRawApiServices {
 | 
			
		||||
interface GithubRawServices {
 | 
			
		||||
 | 
			
		||||
    //region topjohnwu/magisk_files
 | 
			
		||||
 | 
			
		||||
@@ -20,16 +19,16 @@ interface GithubRawApiServices {
 | 
			
		||||
    @GET("$MAGISK_FILES/master/beta.json")
 | 
			
		||||
    fun fetchBetaUpdate(): Single<UpdateInfo>
 | 
			
		||||
 | 
			
		||||
    @GET("$MAGISK_FILES/master/canary_builds/release.json")
 | 
			
		||||
    @GET("$MAGISK_FILES/canary/release.json")
 | 
			
		||||
    fun fetchCanaryUpdate(): Single<UpdateInfo>
 | 
			
		||||
 | 
			
		||||
    @GET("$MAGISK_FILES/master/canary_builds/canary.json")
 | 
			
		||||
    @GET("$MAGISK_FILES/canary/debug.json")
 | 
			
		||||
    fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
 | 
			
		||||
 | 
			
		||||
    @GET
 | 
			
		||||
    fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
 | 
			
		||||
 | 
			
		||||
    @GET("$MAGISK_FILES/{$REVISION}/snet.apk")
 | 
			
		||||
    @GET("$MAGISK_FILES/{$REVISION}/snet.jar")
 | 
			
		||||
    @Streaming
 | 
			
		||||
    fun fetchSafetynet(@Path(REVISION) revision: String = Const.SNET_REVISION): Single<ResponseBody>
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +36,13 @@ interface GithubRawApiServices {
 | 
			
		||||
    @Streaming
 | 
			
		||||
    fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): Single<ResponseBody>
 | 
			
		||||
 | 
			
		||||
    @GET("$MAGISK_MASTER/scripts/module_installer.sh")
 | 
			
		||||
    @Streaming
 | 
			
		||||
    fun fetchInstaller(): Single<ResponseBody>
 | 
			
		||||
 | 
			
		||||
    @GET("$MAGISK_MODULES/{$MODULE}/master/{$FILE}")
 | 
			
		||||
    fun fetchModuleInfo(@Path(MODULE) id: String, @Path(FILE) file: String): Single<String>
 | 
			
		||||
 | 
			
		||||
    //endregion
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -47,6 +53,9 @@ interface GithubRawApiServices {
 | 
			
		||||
    @Streaming
 | 
			
		||||
    fun fetchFile(@Url url: String): Single<ResponseBody>
 | 
			
		||||
 | 
			
		||||
    @GET
 | 
			
		||||
    fun fetchString(@Url url: String): Single<String>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val REVISION = "revision"
 | 
			
		||||
@@ -59,4 +68,14 @@ interface GithubRawApiServices {
 | 
			
		||||
        private const val MAGISK_MODULES = "Magisk-Modules-Repo"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface GithubApiServices {
 | 
			
		||||
 | 
			
		||||
    @GET("repos")
 | 
			
		||||
    fun fetchRepos(@Query("page") page: Int,
 | 
			
		||||
                   @Header(Const.Key.IF_NONE_MATCH) etag: String,
 | 
			
		||||
                   @Query("sort") sort: String = "pushed",
 | 
			
		||||
                   @Query("per_page") count: Int = 100): Flowable<Result<List<GithubRepoInfo>>>
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
package com.topjohnwu.magisk.data.repository
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.data.database.PolicyDao
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
 | 
			
		||||
 | 
			
		||||
class AppRepository(private val policyDao: PolicyDao) {
 | 
			
		||||
 | 
			
		||||
    fun deleteOutdated() = policyDao.deleteOutdated()
 | 
			
		||||
    fun delete(packageName: String) = policyDao.delete(packageName)
 | 
			
		||||
    fun delete(uid: Int) = policyDao.delete(uid)
 | 
			
		||||
    fun fetch(uid: Int) = policyDao.fetch(uid)
 | 
			
		||||
    fun fetchAll() = policyDao.fetchAll()
 | 
			
		||||
    fun update(policy: MagiskPolicy) = policyDao.update(policy)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.data.repository
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.data.database.SettingsDao
 | 
			
		||||
import com.topjohnwu.magisk.data.database.StringDao
 | 
			
		||||
import com.topjohnwu.magisk.utils.trimEmptyToNull
 | 
			
		||||
import io.reactivex.schedulers.Schedulers
 | 
			
		||||
import kotlin.properties.ReadWriteProperty
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
@@ -23,8 +22,9 @@ interface DBConfig {
 | 
			
		||||
 | 
			
		||||
    fun dbStrings(
 | 
			
		||||
        name: String,
 | 
			
		||||
        default: String
 | 
			
		||||
    ) = DBStringsValue(name, default)
 | 
			
		||||
        default: String,
 | 
			
		||||
        sync: Boolean = false
 | 
			
		||||
    ) = DBStringsValue(name, default, sync)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -35,12 +35,10 @@ class DBSettingsValue(
 | 
			
		||||
 | 
			
		||||
    private var value: Int? = null
 | 
			
		||||
 | 
			
		||||
    private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
 | 
			
		||||
        if (value == null)
 | 
			
		||||
            value = thisRef.settingsDao.fetch(getKey(property), default).blockingGet()
 | 
			
		||||
            value = thisRef.settingsDao.fetch(name, default).blockingGet()
 | 
			
		||||
        return value!!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -48,7 +46,7 @@ class DBSettingsValue(
 | 
			
		||||
        synchronized(this) {
 | 
			
		||||
            this.value = value
 | 
			
		||||
        }
 | 
			
		||||
        thisRef.settingsDao.put(getKey(property), value)
 | 
			
		||||
        thisRef.settingsDao.put(name, value)
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .subscribe()
 | 
			
		||||
    }
 | 
			
		||||
@@ -70,17 +68,16 @@ class DBBoolSettings(
 | 
			
		||||
 | 
			
		||||
class DBStringsValue(
 | 
			
		||||
        private val name: String,
 | 
			
		||||
        private val default: String
 | 
			
		||||
        private val default: String,
 | 
			
		||||
        private val sync: Boolean
 | 
			
		||||
) : ReadWriteProperty<DBConfig, String> {
 | 
			
		||||
 | 
			
		||||
    private var value: String? = null
 | 
			
		||||
 | 
			
		||||
    private fun getKey(property: KProperty<*>) = name.trimEmptyToNull() ?: property.name
 | 
			
		||||
 | 
			
		||||
    @Synchronized
 | 
			
		||||
    override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
 | 
			
		||||
        if (value == null)
 | 
			
		||||
            value = thisRef.stringDao.fetch(getKey(property), default).blockingGet()
 | 
			
		||||
            value = thisRef.stringDao.fetch(name, default).blockingGet()
 | 
			
		||||
        return value!!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -88,8 +85,22 @@ class DBStringsValue(
 | 
			
		||||
        synchronized(this) {
 | 
			
		||||
            this.value = value
 | 
			
		||||
        }
 | 
			
		||||
        thisRef.stringDao.put(getKey(property), value)
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .subscribe()
 | 
			
		||||
        if (value.isEmpty()) {
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                thisRef.stringDao.delete(name).blockingAwait()
 | 
			
		||||
            } else {
 | 
			
		||||
                thisRef.stringDao.delete(name)
 | 
			
		||||
                        .subscribeOn(Schedulers.io())
 | 
			
		||||
                        .subscribe()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                thisRef.stringDao.put(name, value).blockingAwait()
 | 
			
		||||
            } else {
 | 
			
		||||
                thisRef.stringDao.put(name, value)
 | 
			
		||||
                        .subscribeOn(Schedulers.io())
 | 
			
		||||
                        .subscribe()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@ package com.topjohnwu.magisk.data.repository
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.data.database.LogDao
 | 
			
		||||
import com.topjohnwu.magisk.data.database.base.suRaw
 | 
			
		||||
import com.topjohnwu.magisk.extensions.toSingle
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.MagiskLog
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
 | 
			
		||||
import com.topjohnwu.magisk.utils.toSingle
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,25 @@
 | 
			
		||||
package com.topjohnwu.magisk.data.repository
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.pm.PackageManager
 | 
			
		||||
import com.topjohnwu.magisk.App
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.Info
 | 
			
		||||
import com.topjohnwu.magisk.data.database.base.su
 | 
			
		||||
import com.topjohnwu.magisk.data.network.GithubRawApiServices
 | 
			
		||||
import com.topjohnwu.magisk.data.network.GithubRawServices
 | 
			
		||||
import com.topjohnwu.magisk.extensions.getLabel
 | 
			
		||||
import com.topjohnwu.magisk.extensions.packageName
 | 
			
		||||
import com.topjohnwu.magisk.extensions.toSingle
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.HideTarget
 | 
			
		||||
import com.topjohnwu.magisk.utils.Utils
 | 
			
		||||
import com.topjohnwu.magisk.utils.inject
 | 
			
		||||
import com.topjohnwu.magisk.utils.toSingle
 | 
			
		||||
import com.topjohnwu.magisk.utils.writeToFile
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import io.reactivex.Single
 | 
			
		||||
 | 
			
		||||
class MagiskRepository(
 | 
			
		||||
    private val context: Context,
 | 
			
		||||
    private val apiRaw: GithubRawApiServices,
 | 
			
		||||
    private val packageManager: PackageManager
 | 
			
		||||
        private val apiRaw: GithubRawServices,
 | 
			
		||||
        private val packageManager: PackageManager
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun fetchMagisk() = fetchUpdate()
 | 
			
		||||
        .flatMap { apiRaw.fetchFile(it.magisk.link) }
 | 
			
		||||
        .map { it.writeToFile(context, FILE_MAGISK_ZIP) }
 | 
			
		||||
 | 
			
		||||
    fun fetchManager() = fetchUpdate()
 | 
			
		||||
        .flatMap { apiRaw.fetchFile(it.app.link) }
 | 
			
		||||
        .map { it.writeToFile(context, FILE_MAGISK_APK) }
 | 
			
		||||
 | 
			
		||||
    fun fetchUninstaller() = fetchUpdate()
 | 
			
		||||
        .flatMap { apiRaw.fetchFile(it.uninstaller.link) }
 | 
			
		||||
        .map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) }
 | 
			
		||||
 | 
			
		||||
    fun fetchSafetynet() = apiRaw.fetchSafetynet()
 | 
			
		||||
 | 
			
		||||
    fun fetchBootctl() = apiRaw
 | 
			
		||||
        .fetchBootctl()
 | 
			
		||||
        .map { it.writeToFile(context, FILE_BOOTCTL_SH) }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun fetchUpdate() = when (Config.updateChannel) {
 | 
			
		||||
        Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate()
 | 
			
		||||
        Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
 | 
			
		||||
@@ -57,14 +36,14 @@ class MagiskRepository(
 | 
			
		||||
        } else {
 | 
			
		||||
            Single.just(it)
 | 
			
		||||
        }
 | 
			
		||||
    }.map { Info.remote = it; it }
 | 
			
		||||
    }.doOnSuccess { Info.remote = it }
 | 
			
		||||
 | 
			
		||||
    fun fetchApps() =
 | 
			
		||||
        Single.fromCallable { packageManager.getInstalledApplications(0) }
 | 
			
		||||
            .flattenAsFlowable { it }
 | 
			
		||||
            .filter { it.enabled && !blacklist.contains(it.packageName) }
 | 
			
		||||
            .map {
 | 
			
		||||
                val label = Utils.getAppLabel(it, packageManager)
 | 
			
		||||
                val label = it.getLabel(packageManager)
 | 
			
		||||
                val icon = it.loadIcon(packageManager)
 | 
			
		||||
                HideAppInfo(it, label, icon)
 | 
			
		||||
            }
 | 
			
		||||
@@ -83,14 +62,8 @@ class MagiskRepository(
 | 
			
		||||
    private val Boolean.state get() = if (this) "add" else "rm"
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val FILE_MAGISK_ZIP = "magisk.zip"
 | 
			
		||||
        const val FILE_MAGISK_APK = "magisk.apk"
 | 
			
		||||
        const val FILE_UNINSTALLER_ZIP = "uninstaller.zip"
 | 
			
		||||
        const val FILE_SAFETY_NET_APK = "safetynet.apk"
 | 
			
		||||
        const val FILE_BOOTCTL_SH = "bootctl"
 | 
			
		||||
 | 
			
		||||
        private val blacklist = listOf(
 | 
			
		||||
            let { val app: App by inject(); app }.packageName,
 | 
			
		||||
        private val blacklist by lazy { listOf(
 | 
			
		||||
            packageName,
 | 
			
		||||
            "android",
 | 
			
		||||
            "com.android.chrome",
 | 
			
		||||
            "com.chrome.beta",
 | 
			
		||||
@@ -98,7 +71,7 @@ class MagiskRepository(
 | 
			
		||||
            "com.chrome.canary",
 | 
			
		||||
            "com.android.webview",
 | 
			
		||||
            "com.google.android.webview"
 | 
			
		||||
        )
 | 
			
		||||
        ) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user