mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-10-30 09:00:52 +01:00 
			
		
		
		
	Compare commits
	
		
			580 Commits
		
	
	
		
			manager-v7
			...
			v19.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c1602d2554 | ||
|   | 9f8d4e1022 | ||
|   | d1dfda405f | ||
|   | 28efded624 | ||
|   | 06c86ee267 | ||
|   | 5892780871 | ||
|   | 4fcdcd9a8a | ||
|   | 80d834fb55 | ||
|   | 4122ebe18f | ||
|   | 7d87777bf8 | ||
|   | 4a73d634e0 | ||
|   | 373dc10a40 | ||
|   | ed43ec8ea2 | ||
|   | 7918fc3528 | ||
|   | bf58205b0a | ||
|   | c0d1ce96d1 | ||
|   | b31d3802eb | ||
|   | be1228c3b4 | ||
|   | 15c94c6b34 | ||
|   | 202d23426a | ||
|   | fc26de48b2 | ||
|   | 76c88913f9 | ||
|   | a3a1aed723 | ||
|   | 81aa56f60f | ||
|   | 73bb850209 | ||
|   | 8dfec12330 | ||
|   | ae24397793 | ||
|   | 3b0f888407 | ||
|   | 845d1e02b0 | ||
|   | 5d357bc41f | ||
|   | 6a54672b13 | ||
|   | 3d9a15df44 | ||
|   | 449c7fda2f | ||
|   | 8b7b05da68 | ||
|   | 92400ebcab | ||
|   | 23d3e56967 | ||
|   | 6785dc4967 | ||
|   | dad20f6a2d | ||
|   | bb15671046 | ||
|   | 21984fac8b | ||
|   | f392afe87f | ||
|   | 6a243ec7bc | ||
|   | 8cd3b603df | ||
|   | 6e1aefe6d8 | ||
|   | 1c90b6eca3 | ||
|   | c33cf9f878 | ||
|   | 27cb40eec9 | ||
|   | c06081b75d | ||
|   | a7eec2f0a0 | ||
|   | 4fd0fe3194 | ||
|   | cc74593ddd | ||
|   | fdb7c5dba1 | ||
|   | 77470c7cfa | ||
|   | f0a734fdab | ||
|   | 75405b2b25 | ||
|   | 90ed4b3c49 | ||
|   | 290a17a764 | ||
|   | aaabd836e4 | ||
|   | 076e5cea3b | ||
|   | 8515971ccf | ||
|   | d86fb033ea | ||
|   | 99d7d8ddbc | ||
|   | df78fd2d41 | ||
|   | dabe6267b9 | ||
|   | 0119ebddbe | ||
|   | 3216ef9f47 | ||
|   | b79d1bcded | ||
|   | 17e234f9d5 | ||
|   | ea1f75f80e | ||
|   | 8c40db5730 | ||
|   | 6fe03d2795 | ||
|   | c595a87ccf | ||
|   | fac07c3913 | ||
|   | c63fdbbc6b | ||
|   | 2ff5d9606b | ||
|   | ed43452c1a | ||
|   | 8f28d4028f | ||
|   | b54543b18c | ||
|   | 966d6593ca | ||
|   | ad95b1c9d1 | ||
|   | 3bfa38c60a | ||
|   | 0bdbcad8be | ||
|   | 80855e89ec | ||
|   | 0850401dc4 | ||
|   | 337fda2023 | ||
|   | 64f238191e | ||
|   | eb169cb133 | ||
|   | 80cd85b061 | ||
|   | 89275270f3 | ||
|   | e7339ba619 | ||
|   | d9ad7d522c | ||
|   | 92789c3113 | ||
|   | c1c677e161 | ||
|   | 2fe917ff82 | ||
|   | 0e6c205732 | ||
|   | 125ae0a173 | ||
|   | 0245e13591 | ||
|   | d546733287 | ||
|   | c275326d59 | ||
|   | d4561507b8 | ||
|   | ef0e22cc41 | ||
|   | 62db65bf18 | ||
|   | d5371f752c | ||
|   | a5f5e94115 | ||
|   | 2624706c69 | ||
|   | d39d885ec2 | ||
|   | d83c744725 | ||
|   | 843995cdb9 | ||
|   | 9491ba77e0 | ||
|   | 58a449d437 | ||
|   | 7f55e0f05b | ||
|   | 67c3f40adb | ||
|   | ff7a0ba599 | ||
|   | b152c63102 | ||
|   | 415ff23be5 | ||
|   | b0d6de783e | ||
|   | ac28e6e5ca | ||
|   | 4f9e8d2e8a | ||
|   | 21be2f46f3 | ||
|   | a6e7680212 | ||
|   | e79e744e08 | ||
|   | 7abdac72a4 | ||
|   | 90d85eaf7d | ||
|   | e65f9740fb | ||
|   | 7538f89b56 | ||
|   | 7c755a3991 | ||
|   | 10e903c9fc | ||
|   | b018124226 | ||
|   | a9350f50c9 | ||
|   | ed7babcbf1 | ||
|   | 61ebc335c4 | ||
|   | 0167bd76f1 | ||
|   | 79d704008b | ||
|   | 0a703585b0 | ||
|   | 5d632d0d90 | ||
|   | 4eecaea601 | ||
|   | 63055818ec | ||
|   | 0beb08b687 | ||
|   | b27801a27c | ||
|   | a0cfce7cbc | ||
|   | 8b7144c986 | ||
|   | d3f5f5ee59 | ||
|   | a2a3c7f438 | ||
|   | 4496f82d5b | ||
|   | 09d531557d | ||
|   | 7fee82f731 | ||
|   | 475054c48a | ||
|   | a743d05751 | ||
|   | d1ed502e03 | ||
|   | 37744c7ab6 | ||
|   | bbc9e60a12 | ||
|   | 6c975ecc4c | ||
|   | 23e8a4ce4b | ||
|   | 50134a2f9b | ||
|   | 628b37c4fa | ||
|   | 1b4ae70a43 | ||
|   | b25c49725f | ||
|   | b245782c7e | ||
|   | a9f32baae0 | ||
|   | e7ef71865d | ||
|   | 88c4f72b37 | ||
|   | abbcdf91a5 | ||
|   | b876df6e21 | ||
|   | 4bb81f35d7 | ||
|   | ff20267b3f | ||
|   | 2c9586d811 | ||
|   | 2813d2031a | ||
|   | 4040a0242f | ||
|   | 781ec810d9 | ||
|   | 9e90a71c04 | ||
|   | 5571714b26 | ||
|   | e0d1f02ef5 | ||
|   | 1b729e5ff2 | ||
|   | 51e587d4e8 | ||
|   | ac9c55dbc1 | ||
|   | 065051a360 | ||
|   | 0893ac3141 | ||
|   | fb40e96917 | ||
|   | 4ca25f74c6 | ||
|   | 7fda917b86 | ||
|   | e6bd5f2c40 | ||
|   | 8a904ee384 | ||
|   | 00a9f18a1e | ||
|   | 8d68ebb074 | ||
|   | 5f53cfb4a9 | ||
|   | a2fa8d8be1 | ||
|   | 70a3c78ebb | ||
|   | db218407b0 | ||
|   | d52210dd90 | ||
|   | f3cd9a096a | ||
|   | e426090a18 | ||
|   | cbe64fd559 | ||
|   | 63ea7a70bd | ||
|   | fb0998f7a2 | ||
|   | a9b00dd537 | ||
|   | 52eb059515 | ||
|   | 7640246255 | ||
|   | 52c83b2916 | ||
|   | d9cded0fc9 | ||
|   | 750c42caf1 | ||
|   | bbf650c6cf | ||
|   | a25dace7e0 | ||
|   | 14ff22fbcd | ||
|   | 07eb7dda2d | ||
|   | 54d1207f92 | ||
|   | 003e44fb84 | ||
|   | 515f346dcc | ||
|   | d4058175b4 | ||
|   | 2de984ae24 | ||
|   | 761a8bf2a9 | ||
|   | 6df7006b36 | ||
|   | aceb3ee863 | ||
|   | 11d716a3c8 | ||
|   | 7cc8c014eb | ||
|   | f21241d944 | ||
|   | a181fa0652 | ||
|   | 3f748b4d2a | ||
|   | 683450f9c6 | ||
|   | 6050c4e8ba | ||
|   | 158af8819a | ||
|   | 7787bb31fa | ||
|   | a1fe3e7ccd | ||
|   | 4316028b23 | ||
|   | f2b52755d6 | ||
|   | adbd47a36c | ||
|   | ce693aa5e9 | ||
|   | ad80804461 | ||
|   | 2d55632430 | ||
|   | e81f00ef1a | ||
|   | 93fb0e3d74 | ||
|   | 71ce0de606 | ||
|   | 0407062c1d | ||
|   | f315c4416b | ||
|   | cda14af208 | ||
|   | 258f170cd7 | ||
|   | f76015d714 | ||
|   | 7e5e14163c | ||
|   | bcd1064e94 | ||
|   | 8a8441c875 | ||
|   | 15aa813416 | ||
|   | 605faccffd | ||
|   | 79f2d08c81 | ||
|   | 0568ae5391 | ||
|   | 5330dda9f8 | ||
|   | ebab126579 | ||
|   | 0e5417a13e | ||
|   | 9a968e0584 | ||
|   | ffec64d209 | ||
|   | f332746188 | ||
|   | b2fa5b551e | ||
|   | 36e83edddc | ||
|   | 6b045eadef | ||
|   | 147264822c | ||
|   | 36e4ccd800 | ||
|   | 796c16237d | ||
|   | 861ad9881c | ||
|   | 3101c538e9 | ||
|   | 42adc7382f | ||
|   | 9bb4dfad13 | ||
|   | 4e7dafb0e4 | ||
|   | bd00ae8ede | ||
|   | f309522268 | ||
|   | a6395d35db | ||
|   | a028cd5cec | ||
|   | 540000d26e | ||
|   | 888c656aa8 | ||
|   | 0efaddff23 | ||
|   | 94ba7cb0c5 | ||
|   | 2d58c725e0 | ||
|   | e035523eb8 | ||
|   | bea5308ab7 | ||
|   | f006a85fec | ||
|   | ea93013ebc | ||
|   | 8d4c407201 | ||
|   | fdeede23f7 | ||
|   | 53c5ca59b6 | ||
|   | 679db97209 | ||
|   | fbdd72273e | ||
|   | 0165602515 | ||
|   | 96127f8bd1 | ||
|   | 0dbdf336d6 | ||
|   | 48879df2da | ||
|   | b067a5bb13 | ||
|   | 4b54cf1288 | ||
|   | 6128c24f96 | ||
|   | d9c58f307f | ||
|   | b521fbeeda | ||
|   | d00a3b89f2 | ||
|   | 3d15518191 | ||
|   | 9b6535fdf5 | ||
|   | e0424fdba3 | ||
|   | 7481c53451 | ||
|   | 7219947237 | ||
|   | b72004e9cc | ||
|   | f187213568 | ||
|   | fc0df84edd | ||
|   | f24df4f43d | ||
|   | dab32e1599 | ||
|   | bc286fd4d3 | ||
|   | befe1a83b5 | ||
|   | 82ea9db9fd | ||
|   | c5758b3f2d | ||
|   | ace3708c9c | ||
|   | fc5026d268 | ||
|   | 77fd0e54be | ||
|   | 24490e0ff5 | ||
|   | da3937ff4e | ||
|   | ebe1ab982e | ||
|   | 98590cb00d | ||
|   | ff95f634f0 | ||
|   | ced9b4a8ee | ||
|   | 7af7910e78 | ||
|   | a4f5d47e72 | ||
|   | 6953cc2411 | ||
|   | 6a0b2ddee9 | ||
|   | 24f5bc98d8 | ||
|   | 5203886f0b | ||
|   | c10b376575 | ||
|   | ceb21ced2b | ||
|   | 86789a8694 | ||
|   | ca2235aee7 | ||
|   | a385e5cd92 | ||
|   | 0c7a95bdf6 | ||
|   | 036b5acf42 | ||
|   | 056dafc59f | ||
|   | a9c90718d6 | ||
|   | cc77a24502 | ||
|   | 71a91ac7a7 | ||
|   | 08a70f033a | ||
|   | 1b0c36dbd5 | ||
|   | 91da1cf817 | ||
|   | c577a9525d | ||
|   | 0149b1368d | ||
|   | cd6bcb97ef | ||
|   | df4161ffcc | ||
|   | 7a133eaf03 | ||
|   | 1cd45b53b1 | ||
|   | 5b30c77403 | ||
|   | 8248480d56 | ||
|   | 345d992d39 | ||
|   | a7f6afa4bc | ||
|   | d22c7de79a | ||
|   | 3eae9494ce | ||
|   | be7e737253 | ||
|   | b6eb912dba | ||
|   | 8049b08918 | ||
|   | d1fa5be210 | ||
|   | fdbb1af02c | ||
|   | c85a5cae88 | ||
|   | 649ef53409 | ||
|   | e784212283 | ||
|   | 66eb1078fe | ||
|   | 1c09b3642f | ||
|   | d08b1a6639 | ||
|   | 10f50e2401 | ||
|   | 8e9a7b25a1 | ||
|   | 4859ee2da9 | ||
|   | b45db44ad9 | ||
|   | e25ce63872 | ||
|   | 162eeaa0a6 | ||
|   | f36ce905aa | ||
|   | 8ac3aaf36c | ||
|   | a199b0ace1 | ||
|   | 2f2108e4e8 | ||
|   | f5f7fd9132 | ||
|   | f9ae4ab475 | ||
|   | 8de03eef3f | ||
|   | 8df942f96e | ||
|   | 9bb2243b56 | ||
|   | db06038548 | ||
|   | ecb33d3176 | ||
|   | eae1c17738 | ||
|   | ea55532e33 | ||
|   | 2a40cb60a9 | ||
|   | d371d017b7 | ||
|   | 1d9359d563 | ||
|   | 945f88105f | ||
|   | 957feca626 | ||
|   | c0447009db | ||
|   | 8893cbd64a | ||
|   | f0240b1f06 | ||
|   | e476c18c99 | ||
|   | a1b5185ecb | ||
|   | 981e90cc32 | ||
|   | da0a72e8b0 | ||
|   | b7e2e972c7 | ||
|   | 650b2ce6b1 | ||
|   | ecf3d30349 | ||
|   | 15ddd0e284 | ||
|   | 18ac6b270f | ||
|   | 3e35de9b39 | ||
|   | 1e24c72c11 | ||
|   | 217564963d | ||
|   | f2f4649ab0 | ||
|   | 4395ffec5f | ||
|   | 9a7a26407a | ||
|   | 5072a67807 | ||
|   | dce0b6c05a | ||
|   | a4a661bf34 | ||
|   | 771e500468 | ||
|   | 7e3ff03109 | ||
|   | a1827fd680 | ||
|   | 9ce334feac | ||
|   | ed11e0bff6 | ||
|   | 5111086637 | ||
|   | 20f204810e | ||
|   | 4581354e7a | ||
|   | faf4d76388 | ||
|   | a46e255709 | ||
|   | 63e2bbb4d1 | ||
|   | c3dabae237 | ||
|   | f1abcbb7fb | ||
|   | 70efddb90f | ||
|   | f24a5dfd45 | ||
|   | 081074ad9d | ||
|   | ab0cc78d2c | ||
|   | de5c902fdb | ||
|   | cf65169c99 | ||
|   | 745865ee53 | ||
|   | c134fb1939 | ||
|   | 0204d05316 | ||
|   | c345633d80 | ||
|   | a57a94040e | ||
|   | 1bde78d121 | ||
|   | bbd014ad1b | ||
|   | 1287372f5a | ||
|   | d2cb638fcd | ||
|   | bbe4b69c8d | ||
|   | 7f08c06943 | ||
|   | 8f4a6415cd | ||
|   | 0442d6d509 | ||
|   | a3fc6d2a27 | ||
|   | 7db05ac927 | ||
|   | 8bed93b3c5 | ||
|   | 915b49014f | ||
|   | c699f30831 | ||
|   | 3e73e3a906 | ||
|   | 32c65d8a88 | ||
|   | a49328edd3 | ||
|   | 9a15365a57 | ||
|   | 82c864d57e | ||
|   | 6226f875ff | ||
|   | 370015a853 | ||
|   | 6597b7adc0 | ||
|   | 4e53ebfe44 | ||
|   | 04ef1e6405 | ||
|   | b278d07b05 | ||
|   | 6c3896079d | ||
|   | e73fa57d54 | ||
|   | eaa9c7e2a0 | ||
|   | 14ae29d907 | ||
|   | e8f35b02ca | ||
|   | dee3c3e7ba | ||
|   | d8cd2031c7 | ||
|   | 7203e7df5c | ||
|   | b51feffe80 | ||
|   | b1afd554fc | ||
|   | 885e3c574b | ||
|   | 05dd5f3396 | ||
|   | ec3c43faf1 | ||
|   | e72c6685ed | ||
|   | 99d6bd8efc | ||
|   | 4c8587a9f2 | ||
|   | 54a8a05dae | ||
|   | 164a99681b | ||
|   | 0eef4eacd6 | ||
|   | 5764f0c839 | ||
|   | f28e425542 | ||
|   | d1a4f046e9 | ||
|   | 2ce1dc4afe | ||
|   | 37ac249fd7 | ||
|   | f152bea8d8 | ||
|   | 7b089b888a | ||
|   | 68f0e1fe39 | ||
|   | 8032bd0bac | ||
|   | 0c227f2917 | ||
|   | c9fa8118d1 | ||
|   | 63b18246d8 | ||
|   | 16ec37a226 | ||
|   | bd4e5bfc1a | ||
|   | 621fd0ee29 | ||
|   | 6ca8db2f0c | ||
|   | ea129fb206 | ||
|   | 3356d7b6ff | ||
|   | c84023bdc2 | ||
|   | 86f778c0aa | ||
|   | defbbdfe21 | ||
|   | 0f46493477 | ||
|   | 340bac7e42 | ||
|   | 1d3ce9fef1 | ||
|   | 4a398642b8 | ||
|   | 9c89e56c56 | ||
|   | 267c59b1f1 | ||
|   | 2ab17204c6 | ||
|   | 75939047d1 | ||
|   | 2d7f130d2c | ||
|   | f7ae72a36c | ||
|   | 391783e268 | ||
|   | 6f12c08204 | ||
|   | cb8fe70734 | ||
|   | 69d10b747a | ||
|   | da3394f34e | ||
|   | b4c2a9f49f | ||
|   | 7cee77f57a | ||
|   | f28bd1972f | ||
|   | 0f92d1de1b | ||
|   | e59c5c8780 | ||
|   | 86d8026301 | ||
|   | d67b827338 | ||
|   | 660e0dc09a | ||
|   | 3ebc886f8a | ||
|   | 5b54ef840a | ||
|   | c08b0d4974 | ||
|   | 7d652afd87 | ||
|   | 0f61c627b1 | ||
|   | 7126648404 | ||
|   | 4a5e2dc9c7 | ||
|   | 10613686ed | ||
|   | 17ab55115a | ||
|   | 2708c74ebe | ||
|   | 50ff11405f | ||
|   | 31a27838f5 | ||
|   | 2f1b0fe57f | ||
|   | 692f893e1f | ||
|   | 14aa6041ec | ||
|   | fb55fe184c | ||
|   | 6412bfc7b5 | ||
|   | 3c56f38229 | ||
|   | f4f2274c60 | ||
|   | 19ee189468 | ||
|   | a19c7215d2 | ||
|   | 8b84039f1f | ||
|   | 9430dbb96c | ||
|   | 4872df6a46 | ||
|   | 014105f0a0 | ||
|   | b106d1c501 | ||
|   | 99db0672b4 | ||
|   | d584360de2 | ||
|   | 4eed6794c7 | ||
|   | c66cabd80f | ||
|   | 24da3485bd | ||
|   | 7384d2d330 | ||
|   | e5940168fe | ||
|   | 6855baf0f8 | ||
|   | dfd16e8fef | ||
|   | 98a36819bc | ||
|   | de8bc9ca9d | ||
|   | c137f2de4f | ||
|   | 0f55fcafe8 | ||
|   | ed027ec3ee | ||
|   | b3fd79cbb9 | ||
|   | ed4df87b57 | ||
|   | 1321f097b8 | ||
|   | cfa28f0c4a | ||
|   | ab47b717b1 | ||
|   | 65ebb0d2f8 | ||
|   | 49640ce03a | ||
|   | e05cdc83f3 | ||
|   | 992a9ea2f9 | ||
|   | 228351fc13 | ||
|   | 8a5b6f2b86 | ||
|   | 71ecbb3af3 | ||
|   | 5746614ccf | ||
|   | 3a422c3f15 | ||
|   | b3242322fd | ||
|   | 9826640ae6 | ||
|   | 1f5267204b | ||
|   | ed25e1bbd6 | ||
|   | c8491d008f | ||
|   | 08e3405394 | ||
|   | 4ebfa07186 | ||
|   | 6698c189fc | ||
|   | f0639390aa | ||
|   | bbdfed2d5a | ||
|   | 7f4daa2c50 | ||
|   | baf9b67b35 | ||
|   | caf73b0b36 | ||
|   | acf87c2794 | ||
|   | 7f5f6b54fb | ||
|   | a08eb8a446 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @@ -16,3 +16,4 @@ chromeos/** binary | ||||
| *.exe binary | ||||
| *.apk binary | ||||
| *.png binary | ||||
| *.jpg binary | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -15,4 +15,3 @@ native/out | ||||
| /.idea | ||||
| /build | ||||
| /captures | ||||
| .externalNativeBuild | ||||
|   | ||||
							
								
								
									
										80
									
								
								README.MD
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								README.MD
									
									
									
									
									
								
							| @@ -1,41 +1,75 @@ | ||||
| # Magisk | ||||
| [Downloads](https://github.com/topjohnwu/Magisk/releases) | [Documentation](https://topjohnwu.github.io/Magisk/) | [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445) | ||||
|  | ||||
| [Downloads](https://github.com/topjohnwu/Magisk/releases) \| [Documentation](https://topjohnwu.github.io/Magisk/) \| [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445) | ||||
|  | ||||
| ## Introduction | ||||
|  | ||||
| Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc. | ||||
|  | ||||
| Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html). | ||||
|  | ||||
| ## 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. | ||||
|  | ||||
| ## Building Environment Requirements | ||||
| 1. Python 3: run `build.py` script | ||||
| 2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips | ||||
| 3. Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK | ||||
| 4. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME` | ||||
| 5. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes | ||||
|  | ||||
| -  Python 3: run `build.py` script | ||||
| -  Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips | ||||
| -  Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK | ||||
| -  Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME` | ||||
| -  (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes | ||||
|  | ||||
| ## Building Notes and Instructions | ||||
| 1. Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git` | ||||
| 2. Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling. | ||||
| 3. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example. | ||||
| 4. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h` | ||||
| 5. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). | ||||
|  | ||||
| -  Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git` | ||||
| -  Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling. | ||||
| -  Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example. | ||||
| -  Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h` | ||||
| -  By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). | ||||
|  | ||||
| ## Translations | ||||
|  | ||||
| Default string resources for Magisk Manager are scattered throughout | ||||
|  | ||||
| - `app/src/main/res/values/strings.xml` | ||||
| - `stub/src/main/res/values/strings.xml` | ||||
| - `shared/src/main/res/values/strings.xml` | ||||
|  | ||||
| Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`). | ||||
|  | ||||
| ## Signature Verification | ||||
|  | ||||
| Official release zips and APKs are signed with my personal private key. You can verify the key certificate to make sure the binaries you downloaded are not manipulated in anyway. | ||||
|  | ||||
| ``` bash | ||||
| # Use the keytool command from JDK to print certificates | ||||
| keytool -printcert -jarfile <APK or Magisk zip> | ||||
|  | ||||
| # The output should contain the following signature | ||||
| Owner: CN=John Wu, L=Taipei, C=TW | ||||
| Issuer: CN=John Wu, L=Taipei, C=TW | ||||
| Serial number: 50514879 | ||||
| Valid from: Sun Aug 14 13:23:44 EDT 2016 until: Tue Jul 21 13:23:44 EDT 2116 | ||||
| Certificate fingerprints: | ||||
| 	 MD5:  CE:DA:68:C1:E1:74:71:0A:EF:58:89:7D:AE:6E:AB:4F | ||||
| 	 SHA1: DC:0F:2B:61:CB:D7:E9:D3:DB:BE:06:0B:2B:87:0D:46:BB:06:02:11 | ||||
| 	 SHA256: B4:CB:83:B4:DA:D9:9F:99:7D:BE:87:2F:01:3A:A1:6C:14:EE:C4:1D:16:70:21:F3:71:F7:E1:33:0F:27:3E:E6 | ||||
| 	 Signature algorithm name: SHA256withRSA | ||||
| 	 Version: 3 | ||||
| ``` | ||||
|  | ||||
| ## License | ||||
|  | ||||
| ``` | ||||
| Magisk, including all git submodules are free software: | ||||
| you can redistribute it and/or modify it under the terms of the | ||||
| GNU General Public License as published by the Free Software Foundation, | ||||
| either version 3 of the License, or (at your option) any later version. | ||||
|     Magisk, including all git submodules are free software: | ||||
|     you can redistribute it and/or modify it under the terms of the | ||||
|     GNU General Public License as published by the Free Software Foundation, | ||||
|     either version 3 of the License, or (at your option) any later version. | ||||
|  | ||||
| This program is distributed in the hope that it will be useful, | ||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| GNU General Public License for more details. | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
| You should have received a copy of the GNU General Public License | ||||
| along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| ``` | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|   | ||||
							
								
								
									
										2
									
								
								app-core/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								app-core/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +0,0 @@ | ||||
| /build | ||||
| src/main/res/raw/util_functions.sh | ||||
| @@ -1,21 +0,0 @@ | ||||
| apply plugin: 'com.android.library' | ||||
|  | ||||
| android { | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     api project(':net') | ||||
|     api project(':signing') | ||||
|     api 'org.kamranzafar:jtar:2.3' | ||||
|  | ||||
|     def libsuVersion = '2.3.0' | ||||
|     api "com.github.topjohnwu.libsu:core:${libsuVersion}" | ||||
|     api "com.github.topjohnwu.libsu:io:${libsuVersion}" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.topjohnwu.magisk.core" > | ||||
|     <uses-permission android:name="android.permission.USE_FINGERPRINT" /> | ||||
| </manifest> | ||||
| @@ -1,61 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.Application; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.res.Configuration; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Build; | ||||
| import android.preference.PreferenceManager; | ||||
|  | ||||
| import com.topjohnwu.magisk.core.BuildConfig; | ||||
| import com.topjohnwu.magisk.database.MagiskDB; | ||||
| import com.topjohnwu.magisk.database.RepoDatabaseHelper; | ||||
| import com.topjohnwu.magisk.utils.LocaleManager; | ||||
| import com.topjohnwu.magisk.utils.RootUtils; | ||||
| import com.topjohnwu.net.Networking; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
|  | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
|  | ||||
| public class App extends Application { | ||||
|  | ||||
|     public static App self; | ||||
|     public static ThreadPoolExecutor THREAD_POOL; | ||||
|  | ||||
|     // Global resources | ||||
|     public SharedPreferences prefs; | ||||
|     public MagiskDB mDB; | ||||
|     public RepoDatabaseHelper repoDB; | ||||
|  | ||||
|     static { | ||||
|         Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER | Shell.FLAG_USE_MAGISK_BUSYBOX); | ||||
|         Shell.Config.verboseLogging(BuildConfig.DEBUG); | ||||
|         Shell.Config.addInitializers(RootUtils.class); | ||||
|         Shell.Config.setTimeout(2); | ||||
|         THREAD_POOL = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void attachBaseContext(Context base) { | ||||
|         super.attachBaseContext(base); | ||||
|         self = this; | ||||
|  | ||||
|         Context de = this; | ||||
|         if (Build.VERSION.SDK_INT >= 24) { | ||||
|             de = createDeviceProtectedStorageContext(); | ||||
|             de.moveSharedPreferencesFrom(this, PreferenceManager.getDefaultSharedPreferencesName(base)); | ||||
|         } | ||||
|         prefs = PreferenceManager.getDefaultSharedPreferences(de); | ||||
|         mDB = new MagiskDB(this); | ||||
|  | ||||
|         Networking.init(this); | ||||
|         LocaleManager.setLocale(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onConfigurationChanged(Configuration newConfig) { | ||||
|         super.onConfigurationChanged(newConfig); | ||||
|         LocaleManager.setLocale(this); | ||||
|     } | ||||
| } | ||||
| @@ -1,110 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.os.Environment; | ||||
| import android.os.Process; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
|  | ||||
| public class Const { | ||||
|  | ||||
|     public static final String DEBUG_TAG = "MagiskManager"; | ||||
|  | ||||
|     // APK content | ||||
|     public static final String ANDROID_MANIFEST = "AndroidManifest.xml"; | ||||
|  | ||||
|     public static final String SU_KEYSTORE_KEY = "su_key"; | ||||
|  | ||||
|     // Paths | ||||
|     public static final String MAGISK_PATH = "/sbin/.magisk/img"; | ||||
|     public static final File EXTERNAL_PATH; | ||||
|     public static File MAGISK_DISABLE_FILE; | ||||
|  | ||||
|     static { | ||||
|         MAGISK_DISABLE_FILE = new File("xxx"); | ||||
|         EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); | ||||
|         EXTERNAL_PATH.mkdirs(); | ||||
|     } | ||||
|  | ||||
|     public static final String BUSYBOX_PATH = "/sbin/.magisk/busybox"; | ||||
|     public static final String TMP_FOLDER_PATH = "/dev/tmp"; | ||||
|     public static final String MAGISK_LOG = "/cache/magisk.log"; | ||||
|     public static final String MANAGER_CONFIGS = ".tmp.magisk.config"; | ||||
|  | ||||
|     // Versions | ||||
|     public static final int UPDATE_SERVICE_VER = 1; | ||||
|     public static final int MIN_MODULE_VER = 1500; | ||||
|     public static final int SNET_EXT_VER = 12; | ||||
|  | ||||
|     /* A list of apps that should not be shown as hide-able */ | ||||
|     public static final List<String> HIDE_BLACKLIST =  Arrays.asList( | ||||
|             App.self.getPackageName(), | ||||
|             "com.google.android.gms" | ||||
|     ); | ||||
|  | ||||
|     public static final int USER_ID = Process.myUid() / 100000; | ||||
|  | ||||
|     public static final class MAGISK_VER { | ||||
|         public static final int MIN_SUPPORT = 18000; | ||||
|     } | ||||
|  | ||||
|     public static class ID { | ||||
|         public static final int UPDATE_SERVICE_ID = 1; | ||||
|         public static final int FETCH_ZIP = 2; | ||||
|         public static final int SELECT_BOOT = 3; | ||||
|         public static final int ONBOOT_SERVICE_ID = 6; | ||||
|  | ||||
|         // notifications | ||||
|         public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4; | ||||
|         public static final int APK_UPDATE_NOTIFICATION_ID = 5; | ||||
|         public static final int DTBO_NOTIFICATION_ID = 7; | ||||
|         public static final int HIDE_MANAGER_NOTIFICATION_ID = 8; | ||||
|         public static final String UPDATE_NOTIFICATION_CHANNEL = "update"; | ||||
|         public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress"; | ||||
|         public static final String CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"; | ||||
|     } | ||||
|  | ||||
|     public static class Url { | ||||
|         private static String getRaw(String where, String name) { | ||||
|             return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name); | ||||
|         } | ||||
|         public static final String STABLE_URL = getRaw("master", "stable.json"); | ||||
|         public static final String BETA_URL = getRaw("master", "beta.json"); | ||||
|         public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d"; | ||||
|         public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"; | ||||
|         public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"; | ||||
|         public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu"; | ||||
|         public static final String PATREON_URL = "https://www.patreon.com/topjohnwu"; | ||||
|         public static final String TWITTER_URL = "https://twitter.com/topjohnwu"; | ||||
|         public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"; | ||||
|         public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"; | ||||
|         public static final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk"); | ||||
|         public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl"); | ||||
|     } | ||||
|  | ||||
|     public static class Key { | ||||
|         // others | ||||
|         public static final String LINK_KEY = "Link"; | ||||
|         public static final String IF_NONE_MATCH = "If-None-Match"; | ||||
|         // intents | ||||
|         public static final String FROM_SPLASH = "splash"; | ||||
|         public static final String OPEN_SECTION = "section"; | ||||
|         public static final String INTENT_SET_NAME = "filename"; | ||||
|         public static final String INTENT_SET_LINK = "link"; | ||||
|         public static final String FLASH_ACTION = "action"; | ||||
|         public static final String FLASH_SET_BOOT = "boot"; | ||||
|         public static final String BROADCAST_MANAGER_UPDATE = "manager_update"; | ||||
|         public static final String BROADCAST_REBOOT = "reboot"; | ||||
|     } | ||||
|  | ||||
|     public static class Value { | ||||
|         public static final String FLASH_ZIP = "flash"; | ||||
|         public static final String PATCH_BOOT = "patch"; | ||||
|         public static final String FLASH_MAGISK = "magisk"; | ||||
|         public static final String FLASH_INACTIVE_SLOT = "slot"; | ||||
|         public static final String UNINSTALL = "uninstall"; | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,64 +0,0 @@ | ||||
| package com.topjohnwu.magisk.container; | ||||
|  | ||||
| import org.kamranzafar.jtar.TarHeader; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.util.Arrays; | ||||
|  | ||||
| public class TarEntry extends org.kamranzafar.jtar.TarEntry { | ||||
|  | ||||
|     public TarEntry(File file, String entryName) { | ||||
|         super(file, entryName); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     * Workaround missing java.nio.file.attribute.PosixFilePermission | ||||
|     * Simply just assign a default permission to the file | ||||
|     * */ | ||||
|  | ||||
|     @Override | ||||
|     public void extractTarHeader(String entryName) { | ||||
|         int permissions = file.isDirectory() ? 000755 : 000644; | ||||
|         header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory(), permissions); | ||||
|         header.userName = new StringBuffer(""); | ||||
|         header.groupName = header.userName; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     * Rewrite the header to GNU format | ||||
|     * */ | ||||
|  | ||||
|     @Override | ||||
|     public void writeEntryHeader(byte[] outbuf) { | ||||
|         super.writeEntryHeader(outbuf); | ||||
|  | ||||
|         System.arraycopy("ustar  \0".getBytes(), 0, outbuf, 257, TarHeader.USTAR_MAGICLEN); | ||||
|         getOctalBytes(header.mode, outbuf, 100, TarHeader.MODELEN); | ||||
|         getOctalBytes(header.userId, outbuf, 108, TarHeader.UIDLEN); | ||||
|         getOctalBytes(header.groupId, outbuf, 116, TarHeader.GIDLEN); | ||||
|         getOctalBytes(header.size, outbuf, 124, TarHeader.SIZELEN); | ||||
|         getOctalBytes(header.modTime, outbuf, 136, TarHeader.MODTIMELEN); | ||||
|         Arrays.fill(outbuf, 148, 148 + TarHeader.CHKSUMLEN, (byte) ' '); | ||||
|         Arrays.fill(outbuf, 329, 329 + TarHeader.USTAR_DEVLEN, (byte) '\0'); | ||||
|         Arrays.fill(outbuf, 337, 337 + TarHeader.USTAR_DEVLEN, (byte) '\0'); | ||||
|  | ||||
|         // Recalculate checksum | ||||
|         getOctalBytes(computeCheckSum(outbuf), outbuf, 148, TarHeader.CHKSUMLEN); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     * Proper octal to ASCII conversion | ||||
|     * */ | ||||
|  | ||||
|     private void getOctalBytes(long value, byte[] buf, int offset, int length) { | ||||
|         int idx = length - 1; | ||||
|  | ||||
|         buf[offset + idx] = 0; | ||||
|         --idx; | ||||
|  | ||||
|         for (long val = value; idx >= 0; --idx) { | ||||
|             buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7)); | ||||
|             val = val >> 3; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,186 +0,0 @@ | ||||
| package com.topjohnwu.magisk.database; | ||||
|  | ||||
| import android.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.text.TextUtils; | ||||
|  | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.container.Policy; | ||||
| import com.topjohnwu.magisk.container.SuLogEntry; | ||||
| import com.topjohnwu.magisk.utils.LocaleManager; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
|  | ||||
| import java.text.DateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class MagiskDB { | ||||
|  | ||||
|     private static final String POLICY_TABLE = "policies"; | ||||
|     private static final String LOG_TABLE = "logs"; | ||||
|     private static final String SETTINGS_TABLE = "settings"; | ||||
|     private static final String STRINGS_TABLE = "strings"; | ||||
|  | ||||
|     private PackageManager pm; | ||||
|  | ||||
|     public MagiskDB(Context context) { | ||||
|         pm = context.getPackageManager(); | ||||
|     } | ||||
|  | ||||
|     public void deletePolicy(Policy policy) { | ||||
|         deletePolicy(policy.uid); | ||||
|     } | ||||
|  | ||||
|     private List<String> rawSQL(String fmt, Object... args) { | ||||
|         return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut(); | ||||
|     } | ||||
|  | ||||
|     private List<ContentValues> SQL(String fmt, Object... args) { | ||||
|         List<ContentValues> list = new ArrayList<>(); | ||||
|         for (String raw : rawSQL(fmt, args)) { | ||||
|             ContentValues values = new ContentValues(); | ||||
|             String[] cols = raw.split("\\|"); | ||||
|             for (String col : cols) { | ||||
|                 String[] pair = col.split("=", 2); | ||||
|                 if (pair.length != 2) | ||||
|                     continue; | ||||
|                 values.put(pair[0], pair[1]); | ||||
|             } | ||||
|             list.add(values); | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     private String toSQL(ContentValues values) { | ||||
|         StringBuilder keys = new StringBuilder(), vals = new StringBuilder(); | ||||
|         keys.append('('); | ||||
|         vals.append("VALUES("); | ||||
|         boolean first = true; | ||||
|         for (Map.Entry<String, Object> entry : values.valueSet()) { | ||||
|             if (!first) { | ||||
|                 keys.append(','); | ||||
|                 vals.append(','); | ||||
|             } else { | ||||
|                 first = false; | ||||
|             } | ||||
|             keys.append(entry.getKey()); | ||||
|             vals.append('"'); | ||||
|             vals.append(entry.getValue()); | ||||
|             vals.append('"'); | ||||
|         } | ||||
|         keys.append(')'); | ||||
|         vals.append(')'); | ||||
|         keys.append(vals); | ||||
|         return keys.toString(); | ||||
|     } | ||||
|  | ||||
|     public void clearOutdated() { | ||||
|         rawSQL( | ||||
|                 "DELETE FROM %s WHERE until > 0 AND until < %d;" + | ||||
|                         "DELETE FROM %s WHERE time < %d", | ||||
|                 POLICY_TABLE, System.currentTimeMillis() / 1000, | ||||
|                 LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000 | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     public void deletePolicy(String pkg) { | ||||
|         rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg); | ||||
|     } | ||||
|  | ||||
|     public void deletePolicy(int uid) { | ||||
|         rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid); | ||||
|     } | ||||
|  | ||||
|     public Policy getPolicy(int uid) { | ||||
|         List<ContentValues> res = | ||||
|                 SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid); | ||||
|         if (!res.isEmpty()) { | ||||
|             try { | ||||
|                 return new Policy(res.get(0), pm); | ||||
|             } catch (PackageManager.NameNotFoundException e) { | ||||
|                 deletePolicy(uid); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public void updatePolicy(Policy policy) { | ||||
|         rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues())); | ||||
|     } | ||||
|  | ||||
|     public List<Policy> getPolicyList() { | ||||
|         List<Policy> list = new ArrayList<>(); | ||||
|         for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) { | ||||
|             try { | ||||
|                 list.add(new Policy(values, pm)); | ||||
|             } catch (PackageManager.NameNotFoundException e) { | ||||
|                 deletePolicy(values.getAsInteger("uid")); | ||||
|             } | ||||
|         } | ||||
|         Collections.sort(list); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     public List<List<SuLogEntry>> getLogs() { | ||||
|         List<List<SuLogEntry>> ret = new ArrayList<>(); | ||||
|         List<SuLogEntry> list = null; | ||||
|         String dateString = null, newString; | ||||
|         for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) { | ||||
|             Date date = new Date(values.getAsLong("time")); | ||||
|             newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date); | ||||
|             if (!TextUtils.equals(dateString, newString)) { | ||||
|                 dateString = newString; | ||||
|                 list = new ArrayList<>(); | ||||
|                 ret.add(list); | ||||
|             } | ||||
|             list.add(new SuLogEntry(values)); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     public void addLog(SuLogEntry log) { | ||||
|         rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues())); | ||||
|     } | ||||
|  | ||||
|     public void clearLogs() { | ||||
|         rawSQL("DELETE FROM %s", LOG_TABLE); | ||||
|     } | ||||
|  | ||||
|     public void setSettings(String key, int value) { | ||||
|         ContentValues data = new ContentValues(); | ||||
|         data.put("key", key); | ||||
|         data.put("value", value); | ||||
|         rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data)); | ||||
|     } | ||||
|  | ||||
|     public int getSettings(String key, int defaultValue) { | ||||
|         List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key); | ||||
|         if (res.isEmpty()) | ||||
|             return defaultValue; | ||||
|         return res.get(0).getAsInteger("value"); | ||||
|     } | ||||
|  | ||||
|     public void setStrings(String key, String value) { | ||||
|         if (value == null) { | ||||
|             rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key); | ||||
|             return; | ||||
|         } | ||||
|         ContentValues data = new ContentValues(); | ||||
|         data.put("key", key); | ||||
|         data.put("value", value); | ||||
|         rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data)); | ||||
|     } | ||||
|  | ||||
|     public String getStrings(String key, String defaultValue) { | ||||
|         List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key); | ||||
|         if (res.isEmpty()) | ||||
|             return defaultValue; | ||||
|         return res.get(0).getAsString("value"); | ||||
|     } | ||||
| } | ||||
| @@ -1,108 +0,0 @@ | ||||
| package com.topjohnwu.magisk.tasks; | ||||
|  | ||||
| import android.os.SystemClock; | ||||
|  | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.utils.Topic; | ||||
| import com.topjohnwu.net.Networking; | ||||
| import com.topjohnwu.net.Request; | ||||
| import com.topjohnwu.net.ResponseListener; | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler; | ||||
|  | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| public class CheckUpdates { | ||||
|  | ||||
|     private static Request getRequest() { | ||||
|         String url; | ||||
|         switch ((int) Config.get(Config.Key.UPDATE_CHANNEL)) { | ||||
|             case Config.Value.BETA_CHANNEL: | ||||
|                 url = Const.Url.BETA_URL; | ||||
|                 break; | ||||
|             case Config.Value.CUSTOM_CHANNEL: | ||||
|                 url = Config.get(Config.Key.CUSTOM_CHANNEL); | ||||
|                 break; | ||||
|             case Config.Value.STABLE_CHANNEL: | ||||
|             default: | ||||
|                 url = Const.Url.STABLE_URL; | ||||
|                 break; | ||||
|         } | ||||
|         return Networking.get(url); | ||||
|     } | ||||
|  | ||||
|     public static void check() { | ||||
|         getRequest().getAsJSONObject(new UpdateListener(null)); | ||||
|     } | ||||
|  | ||||
|     public static void checkNow(Runnable cb) { | ||||
|         JSONObject json = getRequest().execForJSONObject().getResult(); | ||||
|         if (json != null) | ||||
|             new UpdateListener(cb).onResponse(json); | ||||
|     } | ||||
|  | ||||
|     private static class UpdateListener implements ResponseListener<JSONObject> { | ||||
|  | ||||
|         private Runnable cb; | ||||
|         private long start; | ||||
|  | ||||
|         UpdateListener(Runnable callback) { | ||||
|             cb = callback; | ||||
|             start = SystemClock.uptimeMillis(); | ||||
|         } | ||||
|  | ||||
|         private int getInt(JSONObject json, String name, int defValue) { | ||||
|             if (json == null) | ||||
|                 return defValue; | ||||
|             try { | ||||
|                 return json.getInt(name); | ||||
|             } catch (JSONException e) { | ||||
|                 return defValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private String getString(JSONObject json, String name, String defValue) { | ||||
|             if (json == null) | ||||
|                 return defValue; | ||||
|             try { | ||||
|                 return json.getString(name); | ||||
|             } catch (JSONException e) { | ||||
|                 return defValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private JSONObject getJson(JSONObject json, String name) { | ||||
|             try { | ||||
|                 return json.getJSONObject(name); | ||||
|             } catch (JSONException e) { | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onResponse(JSONObject json) { | ||||
|             JSONObject magisk = getJson(json, "magisk"); | ||||
|             Config.remoteMagiskVersionString = getString(magisk, "version", null); | ||||
|             Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1); | ||||
|             Config.magiskLink = getString(magisk, "link", null); | ||||
|             Config.magiskNoteLink = getString(magisk, "note", null); | ||||
|             Config.magiskMD5 = getString(magisk, "md5", null); | ||||
|  | ||||
|             JSONObject manager = getJson(json, "app"); | ||||
|             Config.remoteManagerVersionString = getString(manager, "version", null); | ||||
|             Config.remoteManagerVersionCode = getInt(manager, "versionCode", -1); | ||||
|             Config.managerLink = getString(manager, "link", null); | ||||
|             Config.managerNoteLink = getString(manager, "note", null); | ||||
|  | ||||
|             JSONObject uninstaller = getJson(json, "uninstaller"); | ||||
|             Config.uninstallerLink = getString(uninstaller, "link", null); | ||||
|  | ||||
|             UiThreadHandler.handler.postAtTime(() -> Topic.publish(Topic.UPDATE_CHECK_DONE), | ||||
|                     start + 1000  /* Add artificial delay to let UI behave correctly */); | ||||
|  | ||||
|             if (cb != null) | ||||
|                 cb.run(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,85 +0,0 @@ | ||||
| package com.topjohnwu.magisk.tasks; | ||||
|  | ||||
| import android.net.Uri; | ||||
|  | ||||
| import com.topjohnwu.magisk.App; | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.ZipUtils; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
| import com.topjohnwu.superuser.ShellUtils; | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler; | ||||
|  | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.util.List; | ||||
|  | ||||
| public abstract class FlashZip { | ||||
|  | ||||
|     private Uri mUri; | ||||
|     private File tmpFile; | ||||
|     private List<String> console, logs; | ||||
|  | ||||
|     public FlashZip(Uri uri, List<String> out, List<String> err) { | ||||
|         mUri = uri; | ||||
|         console = out; | ||||
|         logs = err; | ||||
|         tmpFile = new File(App.self.getCacheDir(), "install.zip"); | ||||
|     } | ||||
|  | ||||
|     private boolean unzipAndCheck() throws IOException { | ||||
|         ZipUtils.unzip(tmpFile, tmpFile.getParentFile(), "META-INF/com/google/android", true); | ||||
|         return Shell.su("grep -q '#MAGISK' " + new File(tmpFile.getParentFile(), "updater-script")) | ||||
|                 .exec().isSuccess(); | ||||
|     } | ||||
|  | ||||
|     private boolean flash() throws IOException { | ||||
|         console.add("- Copying zip to temp directory"); | ||||
|         try (InputStream in = App.self.getContentResolver().openInputStream(mUri); | ||||
|              OutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile))) { | ||||
|             if (in == null) throw new FileNotFoundException(); | ||||
|             InputStream buf= new BufferedInputStream(in); | ||||
|             ShellUtils.pump(buf, out); | ||||
|         } catch (FileNotFoundException e) { | ||||
|             console.add("! Invalid Uri"); | ||||
|             throw e; | ||||
|         } catch (IOException e) { | ||||
|             console.add("! Cannot copy to cache"); | ||||
|             throw e; | ||||
|         } | ||||
|         try { | ||||
|             if (!unzipAndCheck()) { | ||||
|                 console.add("! This zip is not a Magisk Module!"); | ||||
|                 return false; | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             console.add("! Unzip error"); | ||||
|             throw e; | ||||
|         } | ||||
|         console.add("- Installing " + Utils.getNameFromUri(App.self, mUri)); | ||||
|         return Shell.su("cd " + tmpFile.getParent(), | ||||
|                 "BOOTMODE=true sh update-binary dummy 1 " + tmpFile) | ||||
|                 .to(console, logs) | ||||
|                 .exec().isSuccess(); | ||||
|     } | ||||
|  | ||||
|     public void exec() { | ||||
|         App.THREAD_POOL.execute(() -> { | ||||
|             boolean success = false; | ||||
|             try { | ||||
|                 success = flash(); | ||||
|             } catch (IOException ignored) {} | ||||
|             Shell.su("cd /", "rm -rf " + tmpFile.getParent() + " " + Const.TMP_FOLDER_PATH).submit(); | ||||
|             boolean finalSuccess = success; | ||||
|             UiThreadHandler.run(() -> onResult(finalSuccess)); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     protected abstract void onResult(boolean success); | ||||
| } | ||||
| @@ -1,152 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.ContextWrapper; | ||||
| import android.content.res.Configuration; | ||||
| import android.content.res.Resources; | ||||
| import android.os.Build; | ||||
|  | ||||
| import com.topjohnwu.magisk.App; | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
| import com.topjohnwu.superuser.internal.InternalUtils; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
|  | ||||
| import androidx.annotation.StringRes; | ||||
|  | ||||
| public class LocaleManager { | ||||
|     public static Locale locale = Locale.getDefault(); | ||||
|     public final static Locale defaultLocale = Locale.getDefault(); | ||||
|     public static List<Locale> locales; | ||||
|  | ||||
|     public static Locale forLanguageTag(String tag) { | ||||
|         if (Build.VERSION.SDK_INT >= 21) { | ||||
|             return Locale.forLanguageTag(tag); | ||||
|         } else { | ||||
|             String[] tok = tag.split("-"); | ||||
|             if (tok.length == 0) { | ||||
|                 return new Locale(""); | ||||
|             } | ||||
|             String language; | ||||
|             switch (tok[0]) { | ||||
|                 case "und": | ||||
|                     language = ""; // Undefined | ||||
|                     break; | ||||
|                 case "fil": | ||||
|                     language = "tl"; // Filipino | ||||
|                     break; | ||||
|                 default: | ||||
|                     language = tok[0]; | ||||
|             } | ||||
|             if ((language.length() != 2 && language.length() != 3)) | ||||
|                 return new Locale(""); | ||||
|             if (tok.length == 1) | ||||
|                 return new Locale(language); | ||||
|             String country = tok[1]; | ||||
|             if (country.length() != 2 && country.length() != 3) | ||||
|                 return new Locale(language); | ||||
|             return new Locale(language, country); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static String toLanguageTag(Locale loc) { | ||||
|         if (Build.VERSION.SDK_INT >= 21) { | ||||
|             return loc.toLanguageTag(); | ||||
|         } else { | ||||
|             String language = loc.getLanguage(); | ||||
|             String country = loc.getCountry(); | ||||
|             String variant = loc.getVariant(); | ||||
|             if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) { | ||||
|                 language = "und";       // Follow the Locale#toLanguageTag() implementation | ||||
|             } else if (language.equals("iw")) { | ||||
|                 language = "he";        // correct deprecated "Hebrew" | ||||
|             } else if (language.equals("in")) { | ||||
|                 language = "id";        // correct deprecated "Indonesian" | ||||
|             } else if (language.equals("ji")) { | ||||
|                 language = "yi";        // correct deprecated "Yiddish" | ||||
|             } | ||||
|             // ensure valid country code, if not well formed, it's omitted | ||||
|             if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) { | ||||
|                 country = ""; | ||||
|             } | ||||
|  | ||||
|             // variant subtags that begin with a letter must be at least 5 characters long | ||||
|             if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) { | ||||
|                 variant = ""; | ||||
|             } | ||||
|             StringBuilder tag = new StringBuilder(language); | ||||
|             if (!country.isEmpty()) | ||||
|                 tag.append('-').append(country); | ||||
|             if (!variant.isEmpty()) | ||||
|                 tag.append('-').append(variant); | ||||
|             return tag.toString(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void setLocale(ContextWrapper wrapper) { | ||||
|         String localeConfig = Config.get(Config.Key.LOCALE); | ||||
|         if (localeConfig.isEmpty()) { | ||||
|             locale = defaultLocale; | ||||
|         } else { | ||||
|             locale = forLanguageTag(localeConfig); | ||||
|         } | ||||
|         Locale.setDefault(locale); | ||||
|         InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale)); | ||||
|     } | ||||
|  | ||||
|     public static Context getLocaleContext(Context context, Locale locale) { | ||||
|         if (Build.VERSION.SDK_INT >= 17) { | ||||
|             Configuration config = new Configuration(context.getResources().getConfiguration()); | ||||
|             config.setLocale(locale); | ||||
|             return context.createConfigurationContext(config); | ||||
|         } else { | ||||
|             return context; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static Context getLocaleContext(Locale locale) { | ||||
|         return getLocaleContext(App.self.getBaseContext(), locale); | ||||
|     } | ||||
|  | ||||
|     public static String getString(Locale locale, @StringRes int id) { | ||||
|         return getLocaleContext(locale).getString(id); | ||||
|     } | ||||
|  | ||||
|     public static void loadAvailableLocales(@StringRes int compareId) { | ||||
|         if (Build.VERSION.SDK_INT < 17) | ||||
|             return; | ||||
|         Shell.EXECUTOR.execute(() -> { | ||||
|             locales = new ArrayList<>(); | ||||
|             HashSet<String> set = new HashSet<>(); | ||||
|             Resources res = App.self.getResources(); | ||||
|             Locale locale; | ||||
|  | ||||
|             // Add default locale | ||||
|             locales.add(Locale.ENGLISH); | ||||
|             set.add(getString(Locale.ENGLISH, compareId)); | ||||
|  | ||||
|             // Add some special locales | ||||
|             locales.add(Locale.TAIWAN); | ||||
|             set.add(getString(Locale.TAIWAN, compareId)); | ||||
|             locale = new Locale("pt", "BR"); | ||||
|             locales.add(locale); | ||||
|             set.add(getString(locale, compareId)); | ||||
|  | ||||
|             // Other locales | ||||
|             for (String s : res.getAssets().getLocales()) { | ||||
|                 locale = forLanguageTag(s); | ||||
|                 if (set.add(getString(locale, compareId))) { | ||||
|                     locales.add(locale); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b))); | ||||
|             Topic.publish(Topic.LOCALE_FETCH_DONE); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
|  | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.core.R; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
| import com.topjohnwu.superuser.ShellUtils; | ||||
| import com.topjohnwu.superuser.io.SuFile; | ||||
|  | ||||
| import java.io.InputStream; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
|  | ||||
| public class RootUtils extends Shell.Initializer { | ||||
|  | ||||
|     public static void rmAndLaunch(String rm, String launch) { | ||||
|         Shell.su(Utils.fmt("(rm_launch %d %s %s)&", Const.USER_ID, rm, launch)).exec(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onInit(Context context, @NonNull Shell shell) { | ||||
|         Shell.Job job = shell.newJob(); | ||||
|         if (shell.isRoot()) { | ||||
|             job.add(context.getResources().openRawResource(R.raw.util_functions)) | ||||
|                 .add(context.getResources().openRawResource(R.raw.utils)); | ||||
|             Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk"); | ||||
|             Config.loadMagiskInfo(); | ||||
|         } else { | ||||
|             InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils); | ||||
|             job.add(nonroot); | ||||
|         } | ||||
|  | ||||
|         job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec(); | ||||
|  | ||||
|         Config.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY")); | ||||
|         Config.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT")); | ||||
|         Config.recovery = Boolean.parseBoolean(ShellUtils.fastCmd("echo $RECOVERYMODE")); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Bundle; | ||||
| import android.os.Process; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.App; | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.magisk.container.Policy; | ||||
| import com.topjohnwu.magisk.container.SuLogEntry; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| public abstract class SuLogger { | ||||
|  | ||||
|     public void handleLogs(Intent intent) { | ||||
|  | ||||
|         int fromUid = intent.getIntExtra("from.uid", -1); | ||||
|         if (fromUid < 0) return; | ||||
|         if (fromUid == Process.myUid()) return; | ||||
|  | ||||
|         App app = App.self; | ||||
|         PackageManager pm = app.getPackageManager(); | ||||
|         Policy policy; | ||||
|  | ||||
|         boolean notify; | ||||
|         Bundle data = intent.getExtras(); | ||||
|         if (data.containsKey("notify")) { | ||||
|             notify = data.getBoolean("notify"); | ||||
|             try { | ||||
|                 policy = new Policy(fromUid, pm); | ||||
|             } catch (PackageManager.NameNotFoundException e) { | ||||
|                 return; | ||||
|             } | ||||
|         } else { | ||||
|             // Doesn't report whether notify or not, check database ourselves | ||||
|             policy = app.mDB.getPolicy(fromUid); | ||||
|             if (policy == null) | ||||
|                 return; | ||||
|             notify = policy.notification; | ||||
|         } | ||||
|  | ||||
|         policy.policy = data.getInt("policy", -1); | ||||
|         if (policy.policy < 0) | ||||
|             return; | ||||
|  | ||||
|         if (notify) | ||||
|             handleNotify(policy); | ||||
|  | ||||
|         SuLogEntry log = new SuLogEntry(policy); | ||||
|  | ||||
|         int toUid = intent.getIntExtra("to.uid", -1); | ||||
|         if (toUid < 0) return; | ||||
|         int pid = intent.getIntExtra("pid", -1); | ||||
|         if (pid < 0) return; | ||||
|         String command = intent.getStringExtra("command"); | ||||
|         if (command == null) return; | ||||
|         log.toUid = toUid; | ||||
|         log.fromPid = pid; | ||||
|         log.command = command; | ||||
|         log.date = new Date(); | ||||
|         app.mDB.addLog(log); | ||||
|     } | ||||
|  | ||||
|     private void handleNotify(Policy policy) { | ||||
|         if (policy.notification && | ||||
|                 (int) Config.get(Config.Key.SU_NOTIFICATION) == Config.Value.NOTIFICATION_TOAST) | ||||
|             Utils.toast(getMessage(policy), Toast.LENGTH_SHORT); | ||||
|     } | ||||
|  | ||||
|     public void handleNotify(Intent intent) { | ||||
|         int fromUid = intent.getIntExtra("from.uid", -1); | ||||
|         if (fromUid < 0) return; | ||||
|         if (fromUid == Process.myUid()) return; | ||||
|         try { | ||||
|             Policy policy = new Policy(fromUid, App.self.getPackageManager()); | ||||
|             policy.policy = intent.getIntExtra("policy", -1); | ||||
|             if (policy.policy >= 0) | ||||
|                 handleNotify(policy); | ||||
|         } catch (PackageManager.NameNotFoundException ignored) {} | ||||
|     } | ||||
|  | ||||
|     public abstract String getMessage(Policy policy); | ||||
| } | ||||
| @@ -1,108 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler; | ||||
|  | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
|  | ||||
| import androidx.annotation.IntDef; | ||||
|  | ||||
| public class Topic { | ||||
|  | ||||
|     public static final int MAGISK_HIDE_DONE = 0; | ||||
|     public static final int MODULE_LOAD_DONE = 1; | ||||
|     public static final int REPO_LOAD_DONE = 2; | ||||
|     public static final int UPDATE_CHECK_DONE = 3; | ||||
|     public static final int LOCALE_FETCH_DONE = 4; | ||||
|  | ||||
|     @IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE, | ||||
|             UPDATE_CHECK_DONE, LOCALE_FETCH_DONE}) | ||||
|     @Retention(RetentionPolicy.SOURCE) | ||||
|     public @interface TopicID {} | ||||
|  | ||||
|     // We will not dynamically add topics, so use arrays instead of hash tables | ||||
|     private static Store[] topicList = new Store[5]; | ||||
|  | ||||
|     public static void subscribe(Subscriber sub, @TopicID int... topics) { | ||||
|         for (int topic : topics) { | ||||
|             if (topicList[topic] == null) | ||||
|                 topicList[topic] = new Store(); | ||||
|             topicList[topic].subscribers.add(sub); | ||||
|             if (topicList[topic].published) { | ||||
|                 sub.onPublish(topic, topicList[topic].results); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void subscribe(AutoSubscriber sub) { | ||||
|         subscribe(sub, sub.getSubscribedTopics()); | ||||
|     } | ||||
|  | ||||
|     public static void unsubscribe(Subscriber sub, @TopicID int... topics) { | ||||
|         for (int topic : topics) { | ||||
|             if (topicList[topic] == null) | ||||
|                 continue; | ||||
|             topicList[topic].subscribers.remove(sub); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void unsubscribe(AutoSubscriber sub) { | ||||
|         unsubscribe(sub, sub.getSubscribedTopics()); | ||||
|     } | ||||
|  | ||||
|     public static void publish(@TopicID int topic, Object... results) { | ||||
|         publish(true, topic, results); | ||||
|     } | ||||
|  | ||||
|     public static void publish(boolean persist, @TopicID int topic, Object... results) { | ||||
|         if (topicList[topic] == null) | ||||
|             topicList[topic] = new Store(); | ||||
|         if (persist) { | ||||
|             topicList[topic].results = results; | ||||
|             topicList[topic].published = true; | ||||
|         } | ||||
|         for (Subscriber sub : topicList[topic].subscribers) { | ||||
|             UiThreadHandler.run(() -> sub.onPublish(topic, results)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void reset(@TopicID int... topics) { | ||||
|         for (int topic : topics) { | ||||
|             if (topicList[topic] == null) | ||||
|                 continue; | ||||
|             topicList[topic].published = false; | ||||
|             topicList[topic].results = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static boolean isPublished(@TopicID int... topics) { | ||||
|         for (int topic : topics) { | ||||
|             if (topicList[topic] == null) | ||||
|                 return false; | ||||
|             if (!topicList[topic].published) | ||||
|                 return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public static boolean isPublished(AutoSubscriber sub) { | ||||
|         return isPublished(sub.getSubscribedTopics()); | ||||
|     } | ||||
|  | ||||
|     private static class Store { | ||||
|         boolean published = false; | ||||
|         Set<Subscriber> subscribers = new HashSet<>(); | ||||
|         Object[] results; | ||||
|     } | ||||
|  | ||||
|     public interface Subscriber { | ||||
|         void onPublish(int topic, Object[] result); | ||||
|     } | ||||
|  | ||||
|     public interface AutoSubscriber extends Subscriber { | ||||
|         @TopicID | ||||
|         int[] getSubscribedTopics(); | ||||
|     } | ||||
| } | ||||
| @@ -1,127 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.res.Configuration; | ||||
| import android.content.res.Resources; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.provider.OpenableColumns; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.App; | ||||
| import com.topjohnwu.magisk.Config; | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.container.Module; | ||||
| import com.topjohnwu.magisk.container.ValueSortedMap; | ||||
| import com.topjohnwu.net.Networking; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler; | ||||
| import com.topjohnwu.superuser.io.SuFile; | ||||
|  | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class Utils { | ||||
|  | ||||
|     public static void toast(CharSequence msg, int duration) { | ||||
|         UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show()); | ||||
|     } | ||||
|  | ||||
|     public static void toast(int resId, int duration) { | ||||
|         UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show()); | ||||
|     } | ||||
|  | ||||
|     public static String dlString(String url) { | ||||
|         String s = Networking.get(url).execForString().getResult(); | ||||
|         return s == null ? "" : s; | ||||
|     } | ||||
|  | ||||
|     public static int getPrefsInt(SharedPreferences prefs, String key, int def) { | ||||
|         return Integer.parseInt(prefs.getString(key, String.valueOf(def))); | ||||
|     } | ||||
|  | ||||
|     public static int getPrefsInt(SharedPreferences prefs, String key) { | ||||
|         return getPrefsInt(prefs, key, 0); | ||||
|     } | ||||
|  | ||||
|     public static String getNameFromUri(Context context, Uri uri) { | ||||
|         String name = null; | ||||
|         try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) { | ||||
|             if (c != null) { | ||||
|                 int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME); | ||||
|                 if (nameIndex != -1) { | ||||
|                     c.moveToFirst(); | ||||
|                     name = c.getString(nameIndex); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (name == null) { | ||||
|             int idx = uri.getPath().lastIndexOf('/'); | ||||
|             name = uri.getPath().substring(idx + 1); | ||||
|         } | ||||
|         return name; | ||||
|     } | ||||
|  | ||||
|     public static int dpInPx(int dp) { | ||||
|         float scale = App.self.getResources().getDisplayMetrics().density; | ||||
|         return (int) (dp * scale + 0.5); | ||||
|     } | ||||
|  | ||||
|     public static String fmt(String fmt, Object... args) { | ||||
|         return String.format(Locale.US, fmt, args); | ||||
|     } | ||||
|  | ||||
|     public static String getAppLabel(ApplicationInfo info, PackageManager pm) { | ||||
|         try { | ||||
|             if (info.labelRes > 0 && Build.VERSION.SDK_INT >= 17) { | ||||
|                 Resources res = pm.getResourcesForApplication(info); | ||||
|                 Configuration config = new Configuration(); | ||||
|                 config.setLocale(LocaleManager.locale); | ||||
|                 res.updateConfiguration(config, res.getDisplayMetrics()); | ||||
|                 return res.getString(info.labelRes); | ||||
|             } | ||||
|         } catch (Exception ignored) {} | ||||
|         return info.loadLabel(pm).toString(); | ||||
|     } | ||||
|  | ||||
|     public static String getLegalFilename(CharSequence filename) { | ||||
|         return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "") | ||||
|                 .replace("$", "").replace("`", "").replace("*", "").replace("/", "_") | ||||
|                 .replace("#", "").replace("@", "").replace("\\", "_"); | ||||
|     } | ||||
|  | ||||
|     public static void loadModules() { | ||||
|         Topic.reset(Topic.MODULE_LOAD_DONE); | ||||
|         App.THREAD_POOL.execute(() -> { | ||||
|             Map<String, Module> moduleMap = new ValueSortedMap<>(); | ||||
|             SuFile path = new SuFile(Const.MAGISK_PATH); | ||||
|             SuFile[] modules = path.listFiles( | ||||
|                     (file, name) -> !name.equals("lost+found") && !name.equals(".core")); | ||||
|             for (SuFile file : modules) { | ||||
|                 if (file.isFile()) continue; | ||||
|                 Module module = new Module(Const.MAGISK_PATH + "/" + file.getName()); | ||||
|                 moduleMap.put(module.getId(), module); | ||||
|             } | ||||
|             Topic.publish(Topic.MODULE_LOAD_DONE, moduleMap); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public static boolean showSuperUser() { | ||||
|         return Shell.rootAccess() && (Const.USER_ID == 0 || | ||||
|                 (int) Config.get(Config.Key.SU_MULTIUSER_MODE) != | ||||
|                         Config.Value.MULTIUSER_MODE_OWNER_MANAGED); | ||||
|     } | ||||
|  | ||||
|     public static Context getDEContext() { | ||||
|         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? | ||||
|                 App.self.createDeviceProtectedStorageContext() : App.self; | ||||
|     } | ||||
|  | ||||
|     public static void reboot() { | ||||
|         Shell.su("/system/bin/reboot" + (Config.recovery ? " recovery" : "")).submit(); | ||||
|     } | ||||
| } | ||||
| @@ -1,65 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import com.topjohnwu.signing.JarMap; | ||||
| import com.topjohnwu.signing.SignAPK; | ||||
| import com.topjohnwu.superuser.ShellUtils; | ||||
| import com.topjohnwu.superuser.io.SuFile; | ||||
| import com.topjohnwu.superuser.io.SuFileOutputStream; | ||||
|  | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.OutputStream; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipInputStream; | ||||
|  | ||||
| public class ZipUtils { | ||||
|  | ||||
|     public static void unzip(File zip, File folder, String path, boolean junkPath) throws IOException { | ||||
|         InputStream in = new BufferedInputStream(new FileInputStream(zip)); | ||||
|         unzip(in, folder, path, junkPath); | ||||
|         in.close(); | ||||
|     } | ||||
|  | ||||
|     public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws IOException { | ||||
|         try { | ||||
|             ZipInputStream zipfile = new ZipInputStream(zip); | ||||
|             ZipEntry entry; | ||||
|             while ((entry = zipfile.getNextEntry()) != null) { | ||||
|                 if (!entry.getName().startsWith(path) || entry.isDirectory()){ | ||||
|                     // Ignore directories, only create files | ||||
|                     continue; | ||||
|                 } | ||||
|                 String name; | ||||
|                 if (junkPath) { | ||||
|                     name = entry.getName().substring(entry.getName().lastIndexOf('/') + 1); | ||||
|                 } else { | ||||
|                     name = entry.getName(); | ||||
|                 } | ||||
|                 File dest = new File(folder, name); | ||||
|                 if (!dest.getParentFile().exists() && !dest.getParentFile().mkdirs()) { | ||||
|                     dest = new SuFile(folder, name); | ||||
|                     dest.getParentFile().mkdirs(); | ||||
|                 } | ||||
|                 try (OutputStream out = new SuFileOutputStream(dest)) { | ||||
|                     ShellUtils.pump(zipfile, out); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         catch(IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             throw e; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void signZip(File input, File output) throws Exception { | ||||
|         try (JarMap map = new JarMap(input, false); | ||||
|              BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(output))) { | ||||
|             SignAPK.sign(map, out); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| # Magisk Manager | ||||
| This repo is no longer an independent component. It is merged into the [Magisk Project](https://github.com/topjohnwu/Magisk). | ||||
|  | ||||
| # Translations | ||||
| The default (English) strings are mainly in `src/full/res/values/strings.xml`; some are scattered in `src/main/res/values/strings.xml` and `src/stub/res/values/strings.xml`.   | ||||
| Translations are highly appreciated via pull requests here on Github.   | ||||
| Place translated XMLs in the corresponding locale folder. | ||||
							
								
								
									
										129
									
								
								app/build.gradle
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								app/build.gradle
									
									
									
									
									
								
							| @@ -1,81 +1,102 @@ | ||||
| apply plugin: 'com.android.application' | ||||
| apply plugin: 'kotlin-android' | ||||
| apply plugin: 'kotlin-android-extensions' | ||||
| apply plugin: 'kotlin-kapt' | ||||
|  | ||||
| def configProps = new Properties() | ||||
| def configPath = project.hasProperty('configPath') ? project.configPath : rootProject.file('config.prop') | ||||
| configProps.load(new FileInputStream(configPath)) | ||||
| kapt { | ||||
|     correctErrorTypes = true | ||||
|     useBuildCache = true | ||||
|     mapDiagnosticLocations = true | ||||
|     javacOptions { | ||||
|         option("-Xmaxerrs", 1000) | ||||
|     } | ||||
| } | ||||
|  | ||||
| android { | ||||
|     defaultConfig { | ||||
|         applicationId 'com.topjohnwu.magisk' | ||||
|         vectorDrawables.useSupportLibrary = true | ||||
|     } | ||||
|  | ||||
|     signingConfigs { | ||||
|         config { | ||||
|             storeFile rootProject.file('release-key.jks') | ||||
|             storePassword configProps['keyStorePass'] | ||||
|             keyAlias configProps['keyAlias'] | ||||
|             keyPassword configProps['keyPass'] | ||||
|         } | ||||
|         multiDexEnabled true | ||||
|         versionName configProps['appVersion'] | ||||
|         versionCode configProps['appVersionCode'] as Integer | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         debug { | ||||
|             // If keystore exists, sign the APK with custom signature | ||||
|             if (signingConfigs.config.storeFile.exists()) | ||||
|                 signingConfig signingConfigs.config | ||||
|         } | ||||
|         release { | ||||
|             minifyEnabled true | ||||
|             shrinkResources true | ||||
|             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||
|             signingConfig signingConfigs.config | ||||
|             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), | ||||
|                     'proguard-rules.pro', 'proguard-kotlin.pro' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     flavorDimensions 'mode' | ||||
|  | ||||
|     productFlavors { | ||||
|         full { | ||||
|             versionName configProps['appVersion'] | ||||
|             versionCode configProps['appVersionCode'] as Integer | ||||
|             javaCompileOptions { | ||||
|                 annotationProcessorOptions { | ||||
|                     argument('butterknife.debuggable', 'false') | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         stub { | ||||
|             versionCode 1 | ||||
|             versionName 'stub' | ||||
|         } | ||||
|     dataBinding { | ||||
|         enabled = true | ||||
|     } | ||||
|  | ||||
|     lintOptions { | ||||
|         disable 'MissingTranslation' | ||||
|     packagingOptions { | ||||
|         exclude '/META-INF/*.version' | ||||
|         exclude '/META-INF/*.kotlin_module' | ||||
|         exclude '/META-INF/rxkotlin.properties' | ||||
|         exclude '/androidsupportmultidexversion.txt' | ||||
|         exclude '/org/**' | ||||
|         exclude '/kotlin/**' | ||||
|         exclude '/kotlinx/**' | ||||
|     } | ||||
| } | ||||
|  | ||||
| androidExtensions { | ||||
|     experimental = true | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     implementation project(':net') | ||||
|     fullImplementation project(':app-core') | ||||
|     fullImplementation 'ru.noties:markwon:2.0.1' | ||||
|     fullImplementation 'com.caverock:androidsvg-aar:1.3' | ||||
|     implementation project(':shared') | ||||
|     implementation project(':signing') | ||||
|  | ||||
|     def androidXVersion = "1.0.0" | ||||
|     implementation 'androidx.core:core:1.0.1' | ||||
|     fullImplementation 'androidx.constraintlayout:constraintlayout:1.1.3' | ||||
|     fullImplementation 'androidx.appcompat:appcompat:1.0.2' | ||||
|     fullImplementation "androidx.preference:preference:${androidXVersion}" | ||||
|     fullImplementation "androidx.recyclerview:recyclerview:${androidXVersion}" | ||||
|     fullImplementation "androidx.cardview:cardview:${androidXVersion}" | ||||
|     fullImplementation "com.google.android.material:material:${androidXVersion}" | ||||
|     fullImplementation 'android.arch.work:work-runtime:1.0.0-beta03' | ||||
|     fullImplementation 'androidx.room:room-runtime:2.0.0' | ||||
|     fullImplementation 'androidx.transition:transition:1.0.1' | ||||
|     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' | ||||
|  | ||||
|     def butterKnifeVersion = '10.0.0' | ||||
|     fullImplementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}" | ||||
|     fullAnnotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" | ||||
|     def vMarkwon = '3.0.1' | ||||
|     implementation "ru.noties.markwon:core:${vMarkwon}" | ||||
|     implementation "ru.noties.markwon:html:${vMarkwon}" | ||||
|     implementation "ru.noties.markwon:image-svg:${vMarkwon}" | ||||
|  | ||||
|     def vLibsu = '2.5.0' | ||||
|     implementation "com.github.topjohnwu.libsu:core:${vLibsu}" | ||||
|     implementation "com.github.topjohnwu.libsu:io:${vLibsu}" | ||||
|  | ||||
|     def vKoin = "2.0.1" | ||||
|     implementation "org.koin:koin-core:${vKoin}" | ||||
|     implementation "org.koin:koin-android:${vKoin}" | ||||
|     implementation "org.koin:koin-androidx-viewmodel:${vKoin}" | ||||
|  | ||||
|     def vRetrofit = "2.5.0" | ||||
|     implementation "com.squareup.retrofit2:retrofit:${vRetrofit}" | ||||
|     implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}" | ||||
|     implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}" | ||||
|  | ||||
|     def vOkHttp = "3.12.0" | ||||
|     implementation "com.squareup.okhttp3:okhttp:${vOkHttp}" | ||||
|     implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}" | ||||
|  | ||||
|     def vMoshi = "1.8.0" | ||||
|     implementation "com.squareup.moshi:moshi:${vMoshi}" | ||||
|  | ||||
|     def vKotshi = "2.0.1" | ||||
|     implementation "se.ansman.kotshi:api:${vKotshi}" | ||||
|     kapt "se.ansman.kotshi:compiler:${vKotshi}" | ||||
|  | ||||
|     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-alpha05' | ||||
|     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.multidex:multidex:2.0.1' | ||||
|     implementation 'com.google.android.material:material:1.1.0-alpha06' | ||||
| } | ||||
|   | ||||
							
								
								
									
										20
									
								
								app/proguard-kotlin.pro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/proguard-kotlin.pro
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| ## So every class is case insensitive to avoid some bizare problems | ||||
| -dontusemixedcaseclassnames | ||||
|  | ||||
| ## If reflection issues come up uncomment this, that should temporarily fix it | ||||
| #-keep class kotlin.** { *; } | ||||
| #-keep class kotlin.Metadata { *; } | ||||
| #-keepclassmembers class kotlin.Metadata { | ||||
| #    public <methods>; | ||||
| #} | ||||
|  | ||||
| ## Never warn about Kotlin, it should work as-is | ||||
| -dontwarn kotlin.** | ||||
|  | ||||
| ## Removes runtime null checks - doesn't really matter if it crashes on kotlin or java NPE | ||||
| -assumenosideeffects class kotlin.jvm.internal.Intrinsics { | ||||
|     static void checkParameterIsNotNull(java.lang.Object, java.lang.String); | ||||
| } | ||||
|  | ||||
| ## Useless option for dex | ||||
| -dontpreverify | ||||
							
								
								
									
										27
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							| @@ -16,11 +16,8 @@ | ||||
| #   public *; | ||||
| #} | ||||
|  | ||||
| # BouncyCastle | ||||
| -keep,allowoptimization class org.bouncycastle.jcajce.provider.asymmetric.rsa.**SHA1** { *; } | ||||
| -keep,allowoptimization class org.bouncycastle.jcajce.provider.asymmetric.RSA** { *; } | ||||
| -keep,allowoptimization class org.bouncycastle.jcajce.provider.digest.SHA1** { *; } | ||||
| -dontwarn javax.naming.** | ||||
| # Retrofit classes | ||||
| -keep,allowobfuscation class com.topjohnwu.magisk.data.network.* | ||||
|  | ||||
| # Snet | ||||
| -keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; } | ||||
| @@ -29,13 +26,19 @@ | ||||
|   void onResponse(int); | ||||
| } | ||||
|  | ||||
| # BootSigner | ||||
| -keepclassmembers class com.topjohnwu.signer.BootSigner { *; } | ||||
| # Keep all fragment constructors | ||||
| -keepclassmembers class * extends androidx.fragment.app.Fragment { | ||||
|   public <init>(...); | ||||
| } | ||||
|  | ||||
| # SVG | ||||
| -dontwarn com.caverock.androidsvg.SVGAndroidRenderer | ||||
| # DelegateWorker | ||||
| -keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.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(...); | ||||
| } | ||||
| @@ -43,4 +46,8 @@ | ||||
| # Excessive obfuscation | ||||
| -repackageclasses 'a' | ||||
| -allowaccessmodification | ||||
| -optimizationpasses 6 | ||||
|  | ||||
| # QOL | ||||
| -dontnote ** | ||||
| -dontwarn com.caverock.androidsvg.** | ||||
| -dontwarn ru.noties.markwon.** | ||||
|   | ||||
| @@ -1,75 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     package="com.topjohnwu.magisk"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.VIBRATE" /> | ||||
|     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> | ||||
|  | ||||
|     <application | ||||
|         android:name="a.e" | ||||
|         android:theme="@style/AppTheme" | ||||
|         android:usesCleartextTraffic="true" | ||||
|         tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> | ||||
|  | ||||
|         <!-- Activities --> | ||||
|  | ||||
|         <activity | ||||
|             android:name="a.b" | ||||
|             android:configChanges="orientation|screenSize" | ||||
|             android:exported="true" /> | ||||
|         <activity | ||||
|             android:name="a.c" | ||||
|             android:configChanges="orientation|screenSize" | ||||
|             android:exported="true" | ||||
|             android:theme="@style/SplashTheme"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name="a.f" | ||||
|             android:configChanges="keyboardHidden|orientation|screenSize" | ||||
|             android:screenOrientation="nosensor" | ||||
|             android:theme="@style/AppTheme.NoDrawer" /> | ||||
|  | ||||
|         <!-- Superuser --> | ||||
|  | ||||
|         <activity | ||||
|             android:name="a.m" | ||||
|             android:directBootAware="true" | ||||
|             android:excludeFromRecents="true" | ||||
|             android:launchMode="singleTask" | ||||
|             android:taskAffinity="a.m" | ||||
|             android:theme="@style/SuRequest" /> | ||||
|  | ||||
|         <!-- Receiver --> | ||||
|  | ||||
|         <receiver | ||||
|             android:name="a.h" | ||||
|             android:directBootAware="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.BOOT_COMPLETED" /> | ||||
|                 <action android:name="android.intent.action.LOCALE_CHANGED" /> | ||||
|             </intent-filter> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.PACKAGE_REPLACED" /> | ||||
|                 <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> | ||||
|  | ||||
|                 <data android:scheme="package" /> | ||||
|             </intent-filter> | ||||
|         </receiver> | ||||
|  | ||||
|         <!-- Service --> | ||||
|  | ||||
|         <service android:name="a.j" /> | ||||
|  | ||||
|         <!-- Hardcode GMS version --> | ||||
|         <meta-data | ||||
|             android:name="com.google.android.gms.version" | ||||
|             android:value="12451000" /> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
| @@ -1,27 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import com.topjohnwu.magisk.components.DownloadModuleService; | ||||
| import com.topjohnwu.magisk.components.GeneralReceiver; | ||||
| import com.topjohnwu.magisk.components.UpdateCheckService; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class ClassMap { | ||||
|     private static Map<Class, Class> classMap = new HashMap<>(); | ||||
|  | ||||
|     static { | ||||
|         classMap.put(App.class, a.e.class); | ||||
|         classMap.put(MainActivity.class, a.b.class); | ||||
|         classMap.put(SplashActivity.class, a.c.class); | ||||
|         classMap.put(FlashActivity.class, a.f.class); | ||||
|         classMap.put(UpdateCheckService.class, a.g.class); | ||||
|         classMap.put(GeneralReceiver.class, a.h.class); | ||||
|         classMap.put(DownloadModuleService.class, a.j.class); | ||||
|         classMap.put(SuRequestActivity.class, a.m.class); | ||||
|     } | ||||
|      | ||||
|     public static <T> Class<T> get(Class c) { | ||||
|         return classMap.get(c); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,201 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
|  | ||||
| import com.google.android.material.navigation.NavigationView; | ||||
| import com.topjohnwu.magisk.components.BaseActivity; | ||||
| import com.topjohnwu.magisk.fragments.LogFragment; | ||||
| import com.topjohnwu.magisk.fragments.MagiskFragment; | ||||
| import com.topjohnwu.magisk.fragments.MagiskHideFragment; | ||||
| import com.topjohnwu.magisk.fragments.ModulesFragment; | ||||
| import com.topjohnwu.magisk.fragments.ReposFragment; | ||||
| import com.topjohnwu.magisk.fragments.SettingsFragment; | ||||
| import com.topjohnwu.magisk.fragments.SuperuserFragment; | ||||
| import com.topjohnwu.magisk.utils.Topic; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.net.Networking; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.app.ActionBarDrawerToggle; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.drawerlayout.widget.DrawerLayout; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentTransaction; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class MainActivity extends BaseActivity | ||||
|         implements NavigationView.OnNavigationItemSelectedListener, Topic.Subscriber { | ||||
|  | ||||
|     private final Handler mDrawerHandler = new Handler(); | ||||
|     private int mDrawerItem; | ||||
|     private static boolean fromShortcut = false; | ||||
|  | ||||
|     @BindView(R.id.toolbar) public Toolbar toolbar; | ||||
|     @BindView(R.id.drawer_layout) DrawerLayout drawer; | ||||
|     @BindView(R.id.nav_view) NavigationView navigationView; | ||||
|  | ||||
|     private float toolbarElevation; | ||||
|  | ||||
|     @Override | ||||
|     public int getDarkTheme() { | ||||
|         return R.style.AppTheme_Dark; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(final Bundle savedInstanceState) { | ||||
|         if (!getIntent().getBooleanExtra(Const.Key.FROM_SPLASH, false)) { | ||||
|             startActivity(new Intent(this, ClassMap.get(SplashActivity.class))); | ||||
|             finish(); | ||||
|         } | ||||
|  | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|         new MainActivity_ViewBinding(this); | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|  | ||||
|         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) { | ||||
|             @Override | ||||
|             public void onDrawerOpened(View drawerView) { | ||||
|                 super.onDrawerOpened(drawerView); | ||||
|                 super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onDrawerSlide(View drawerView, float slideOffset) { | ||||
|                 super.onDrawerSlide(drawerView, 0); // this disables the animation | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|             toolbarElevation = toolbar.getElevation(); | ||||
|         } | ||||
|  | ||||
|         drawer.addDrawerListener(toggle); | ||||
|         toggle.syncState(); | ||||
|  | ||||
|         if (savedInstanceState == null) { | ||||
|             String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION); | ||||
|             fromShortcut = section != null; | ||||
|             navigate(section); | ||||
|         } | ||||
|  | ||||
|         navigationView.setNavigationItemSelectedListener(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         checkHideSection(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBackPressed() { | ||||
|         if (drawer.isDrawerOpen(navigationView)) { | ||||
|             drawer.closeDrawer(navigationView); | ||||
|         } else if (mDrawerItem != R.id.magisk && !fromShortcut) { | ||||
|             navigate(R.id.magisk); | ||||
|         } else { | ||||
|             finish(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) { | ||||
|         mDrawerHandler.removeCallbacksAndMessages(null); | ||||
|         mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250); | ||||
|         drawer.closeDrawer(navigationView); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onPublish(int topic, Object[] result) { | ||||
|         recreate(); | ||||
|     } | ||||
|  | ||||
|     public void checkHideSection() { | ||||
|         Menu menu = navigationView.getMenu(); | ||||
|         menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() && | ||||
|                 (boolean) Config.get(Config.Key.MAGISKHIDE)); | ||||
|         menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Config.magiskVersionCode >= 0); | ||||
|         menu.findItem(R.id.downloads).setVisible(Networking.checkNetworkStatus(this) | ||||
|                 && Shell.rootAccess() && Config.magiskVersionCode >= 0); | ||||
|         menu.findItem(R.id.log).setVisible(Shell.rootAccess()); | ||||
|         menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser()); | ||||
|     } | ||||
|  | ||||
|     public void navigate(String item) { | ||||
|         int itemId = R.id.magisk; | ||||
|         if (item != null) { | ||||
|             switch (item) { | ||||
|                 case "superuser": | ||||
|                     itemId = R.id.superuser; | ||||
|                     break; | ||||
|                 case "modules": | ||||
|                     itemId = R.id.modules; | ||||
|                     break; | ||||
|                 case "downloads": | ||||
|                     itemId = R.id.downloads; | ||||
|                     break; | ||||
|                 case "magiskhide": | ||||
|                     itemId = R.id.magiskhide; | ||||
|                     break; | ||||
|                 case "log": | ||||
|                     itemId = R.id.log; | ||||
|                     break; | ||||
|                 case "settings": | ||||
|                     itemId = R.id.settings; | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|         navigate(itemId); | ||||
|     } | ||||
|  | ||||
|     public void navigate(int itemId) { | ||||
|         mDrawerItem = itemId; | ||||
|         navigationView.setCheckedItem(itemId); | ||||
|         switch (itemId) { | ||||
|             case R.id.magisk: | ||||
|                 fromShortcut = false; | ||||
|                 displayFragment(new MagiskFragment(), true); | ||||
|                 break; | ||||
|             case R.id.superuser: | ||||
|                 displayFragment(new SuperuserFragment(), true); | ||||
|                 break; | ||||
|             case R.id.modules: | ||||
|                 displayFragment(new ModulesFragment(), true); | ||||
|                 break; | ||||
|             case R.id.downloads: | ||||
|                 displayFragment(new ReposFragment(), true); | ||||
|                 break; | ||||
|             case R.id.magiskhide: | ||||
|                 displayFragment(new MagiskHideFragment(), true); | ||||
|                 break; | ||||
|             case R.id.log: | ||||
|                 displayFragment(new LogFragment(), false); | ||||
|                 break; | ||||
|             case R.id.settings: | ||||
|                 displayFragment(new SettingsFragment(), true); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) { | ||||
|         supportInvalidateOptionsMenu(); | ||||
|         getSupportFragmentManager() | ||||
|                 .beginTransaction() | ||||
|                 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) | ||||
|                 .replace(R.id.content_frame, navFragment) | ||||
|                 .commitNow(); | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|             toolbar.setElevation(setElevation ? toolbarElevation : 0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,90 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Bundle; | ||||
| import android.text.TextUtils; | ||||
|  | ||||
| import com.topjohnwu.magisk.components.BaseActivity; | ||||
| import com.topjohnwu.magisk.database.RepoDatabaseHelper; | ||||
| import com.topjohnwu.magisk.tasks.UpdateRepos; | ||||
| import com.topjohnwu.magisk.uicomponents.Notifications; | ||||
| import com.topjohnwu.magisk.uicomponents.Shortcuts; | ||||
| import com.topjohnwu.magisk.utils.AppUtils; | ||||
| import com.topjohnwu.magisk.utils.LocaleManager; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.net.Networking; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
|  | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
|  | ||||
| public class SplashActivity extends BaseActivity { | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|  | ||||
|         Shell.getShell(shell -> { | ||||
|             if (Config.magiskVersionCode > 0 && | ||||
|                     Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) { | ||||
|                 new AlertDialog.Builder(this) | ||||
|                         .setTitle(R.string.unsupport_magisk_title) | ||||
|                         .setMessage(R.string.unsupport_magisk_message) | ||||
|                         .setNegativeButton(R.string.ok, null) | ||||
|                         .setOnDismissListener(dialog -> finish()) | ||||
|                         .show(); | ||||
|             } else { | ||||
|                 initAndStart(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private void initAndStart() { | ||||
|         String pkg = Config.get(Config.Key.SU_MANAGER); | ||||
|         if (pkg != null && getPackageName().equals(BuildConfig.APPLICATION_ID)) { | ||||
|             Config.remove(Config.Key.SU_MANAGER); | ||||
|             Shell.su("pm uninstall " + pkg).submit(); | ||||
|         } | ||||
|         if (TextUtils.equals(pkg, getPackageName())) { | ||||
|             try { | ||||
|                 // We are the manager, remove com.topjohnwu.magisk as it could be malware | ||||
|                 getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0); | ||||
|                 Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit(); | ||||
|             } catch (PackageManager.NameNotFoundException ignored) {} | ||||
|         } | ||||
|  | ||||
|         // Dynamic detect all locales | ||||
|         LocaleManager.loadAvailableLocales(R.string.app_changelog); | ||||
|  | ||||
|         // Set default configs | ||||
|         Config.initialize(); | ||||
|  | ||||
|         // Create notification channel on Android O | ||||
|         Notifications.setup(this); | ||||
|  | ||||
|         // Schedule periodic update checks | ||||
|         AppUtils.scheduleUpdateCheck(); | ||||
|  | ||||
|         // Setup shortcuts | ||||
|         Shortcuts.setup(this); | ||||
|  | ||||
|         // Create repo database | ||||
|         app.repoDB = new RepoDatabaseHelper(this); | ||||
|  | ||||
|         // Magisk working as expected | ||||
|         if (Shell.rootAccess() && Config.magiskVersionCode > 0) { | ||||
|             // Load modules | ||||
|             Utils.loadModules(); | ||||
|             // Load repos | ||||
|             if (Networking.checkNetworkStatus(this)) | ||||
|                 new UpdateRepos().exec(); | ||||
|         } | ||||
|  | ||||
|         Intent intent = new Intent(this, ClassMap.get(MainActivity.class)); | ||||
|         intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION)); | ||||
|         intent.putExtra(Const.Key.FROM_SPLASH, true); | ||||
|         intent.putExtra(BaseActivity.INTENT_PERM, getIntent().getStringExtra(BaseActivity.INTENT_PERM)); | ||||
|         startActivity(intent); | ||||
|         finish(); | ||||
|     } | ||||
| } | ||||
| @@ -1,235 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.hardware.fingerprint.FingerprintManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.CountDownTimer; | ||||
| import android.text.TextUtils; | ||||
| import android.view.View; | ||||
| import android.view.Window; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.Button; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.Spinner; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.components.BaseActivity; | ||||
| import com.topjohnwu.magisk.container.Policy; | ||||
| import com.topjohnwu.magisk.utils.FingerprintHelper; | ||||
| import com.topjohnwu.magisk.utils.SuConnector; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.content.res.AppCompatResources; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class SuRequestActivity extends BaseActivity { | ||||
|     @BindView(R.id.su_popup) LinearLayout suPopup; | ||||
|     @BindView(R.id.timeout) Spinner timeout; | ||||
|     @BindView(R.id.app_icon) ImageView appIcon; | ||||
|     @BindView(R.id.app_name) TextView appNameView; | ||||
|     @BindView(R.id.package_name) TextView packageNameView; | ||||
|     @BindView(R.id.grant_btn) Button grant_btn; | ||||
|     @BindView(R.id.deny_btn) Button deny_btn; | ||||
|     @BindView(R.id.fingerprint) ImageView fingerprintImg; | ||||
|     @BindView(R.id.warning) TextView warning; | ||||
|  | ||||
|     private SuConnector connector; | ||||
|     private Policy policy; | ||||
|     private CountDownTimer timer; | ||||
|     private FingerprintHelper fingerprintHelper; | ||||
|     private SharedPreferences timeoutPrefs; | ||||
|  | ||||
|     @Override | ||||
|     public int getDarkTheme() { | ||||
|         return R.style.SuRequest_Dark; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void finish() { | ||||
|         if (timer != null) | ||||
|             timer.cancel(); | ||||
|         if (fingerprintHelper != null) | ||||
|             fingerprintHelper.cancel(); | ||||
|         super.finish(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBackPressed() { | ||||
|         if (policy != null) { | ||||
|             handleAction(Policy.DENY); | ||||
|         } else { | ||||
|             finish(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         supportRequestWindowFeature(Window.FEATURE_NO_TITLE); | ||||
|  | ||||
|         PackageManager pm = getPackageManager(); | ||||
|         app.mDB.clearOutdated(); | ||||
|         timeoutPrefs = Utils.getDEContext().getSharedPreferences("su_timeout", 0); | ||||
|  | ||||
|         // Get policy | ||||
|         Intent intent = getIntent(); | ||||
|         try { | ||||
|             String socketName = intent.getStringExtra("socket"); | ||||
|             connector = new SuConnector(socketName) { | ||||
|                 @Override | ||||
|                 protected void onResponse() throws IOException { | ||||
|                     out.writeInt(policy.policy); | ||||
|                 } | ||||
|             }; | ||||
|             Bundle bundle = connector.readSocketInput(); | ||||
|             int uid = Integer.parseInt(bundle.getString("uid")); | ||||
|             policy = app.mDB.getPolicy(uid); | ||||
|             if (policy == null) { | ||||
|                 policy = new Policy(uid, pm); | ||||
|             } | ||||
|         } catch (IOException | PackageManager.NameNotFoundException e) { | ||||
|             e.printStackTrace(); | ||||
|             finish(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Never allow com.topjohnwu.magisk (could be malware) | ||||
|         if (TextUtils.equals(policy.packageName, BuildConfig.APPLICATION_ID)) { | ||||
|             finish(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         switch ((int) Config.get(Config.Key.SU_AUTO_RESPONSE)) { | ||||
|             case Config.Value.SU_AUTO_DENY: | ||||
|                 handleAction(Policy.DENY, 0); | ||||
|                 return; | ||||
|             case Config.Value.SU_AUTO_ALLOW: | ||||
|                 handleAction(Policy.ALLOW, 0); | ||||
|                 return; | ||||
|             case Config.Value.SU_PROMPT: | ||||
|             default: | ||||
|         } | ||||
|  | ||||
|         // If not interactive, response directly | ||||
|         if (policy.policy != Policy.INTERACTIVE) { | ||||
|             handleAction(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         setContentView(R.layout.activity_request); | ||||
|         new SuRequestActivity_ViewBinding(this); | ||||
|  | ||||
|         appIcon.setImageDrawable(policy.info.loadIcon(pm)); | ||||
|         appNameView.setText(policy.appName); | ||||
|         packageNameView.setText(policy.packageName); | ||||
|         if (Build.VERSION.SDK_INT >= 17) { | ||||
|             warning.setCompoundDrawablesRelativeWithIntrinsicBounds( | ||||
|                     AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null); | ||||
|         } else { | ||||
|             warning.setCompoundDrawablesWithIntrinsicBounds( | ||||
|                     AppCompatResources.getDrawable(this, R.drawable.ic_warning), null, null, null); | ||||
|         } | ||||
|  | ||||
|         ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, | ||||
|                 R.array.allow_timeout, android.R.layout.simple_spinner_item); | ||||
|         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||||
|         timeout.setAdapter(adapter); | ||||
|         timeout.setSelection(timeoutPrefs.getInt(policy.packageName, 0)); | ||||
|  | ||||
|         timer = new CountDownTimer((int) Config.get(Config.Key.SU_REQUEST_TIMEOUT) * 1000, 1000) { | ||||
|             @Override | ||||
|             public void onTick(long millisUntilFinished) { | ||||
|                 deny_btn.setText(getString(R.string.deny_with_str, "(" + millisUntilFinished / 1000 + ")")); | ||||
|             } | ||||
|             @Override | ||||
|             public void onFinish() { | ||||
|                 deny_btn.setText(getString(R.string.deny_with_str, "(0)")); | ||||
|                 handleAction(Policy.DENY); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         boolean useFP = FingerprintHelper.useFingerprint(); | ||||
|  | ||||
|         if (useFP) { | ||||
|             try { | ||||
|                 fingerprintHelper = new FingerprintHelper() { | ||||
|                     @Override | ||||
|                     public void onAuthenticationError(int errorCode, CharSequence errString) { | ||||
|                         warning.setText(errString); | ||||
|                     } | ||||
|  | ||||
|                     @Override | ||||
|                     public void onAuthenticationHelp(int helpCode, CharSequence helpString) { | ||||
|                         warning.setText(helpString); | ||||
|                     } | ||||
|  | ||||
|                     @Override | ||||
|                     public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { | ||||
|                         handleAction(Policy.ALLOW); | ||||
|                     } | ||||
|  | ||||
|                     @Override | ||||
|                     public void onAuthenticationFailed() { | ||||
|                         warning.setText(R.string.auth_fail); | ||||
|                     } | ||||
|                 }; | ||||
|                 fingerprintHelper.authenticate(); | ||||
|             } catch (Exception e) { | ||||
|                 e.printStackTrace(); | ||||
|                 useFP = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!useFP) { | ||||
|             grant_btn.setOnClickListener(v -> { | ||||
|                 handleAction(Policy.ALLOW); | ||||
|                 timer.cancel(); | ||||
|             }); | ||||
|             grant_btn.requestFocus(); | ||||
|         } | ||||
|  | ||||
|         grant_btn.setVisibility(useFP ? View.GONE : View.VISIBLE); | ||||
|         fingerprintImg.setVisibility(useFP ? View.VISIBLE : View.GONE); | ||||
|  | ||||
|         deny_btn.setOnClickListener(v -> { | ||||
|             handleAction(Policy.DENY); | ||||
|             timer.cancel(); | ||||
|         }); | ||||
|         suPopup.setOnClickListener(v -> cancelTimeout()); | ||||
|         timeout.setOnTouchListener((v, event) -> cancelTimeout()); | ||||
|         timer.start(); | ||||
|     } | ||||
|  | ||||
|     private boolean cancelTimeout() { | ||||
|         timer.cancel(); | ||||
|         deny_btn.setText(getString(R.string.deny)); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private void handleAction() { | ||||
|         connector.response(); | ||||
|         finish(); | ||||
|     } | ||||
|  | ||||
|     private void handleAction(int action) { | ||||
|         int pos = timeout.getSelectedItemPosition(); | ||||
|         timeoutPrefs.edit().putInt(policy.packageName, pos).apply(); | ||||
|         handleAction(action, Config.Value.TIMEOUT_LIST[pos]); | ||||
|     } | ||||
|  | ||||
|     private void handleAction(int action, int time) { | ||||
|         policy.policy = action; | ||||
|         if (time >= 0) { | ||||
|             policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60); | ||||
|             app.mDB.updatePolicy(policy); | ||||
|         } | ||||
|         handleAction(); | ||||
|     } | ||||
| } | ||||
| @@ -1,169 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.AsyncTask; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.Const; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Topic; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.WorkerThread; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> { | ||||
|  | ||||
|     private static PackageInfo PLATFORM; | ||||
|  | ||||
|     private List<PackageInfo> fullList, showList; | ||||
|     private List<String> hideList; | ||||
|     private PackageManager pm; | ||||
|     private boolean showSystem; | ||||
|  | ||||
|     public ApplicationAdapter(Context context) { | ||||
|         fullList = showList = Collections.emptyList(); | ||||
|         hideList = Collections.emptyList(); | ||||
|         pm = context.getPackageManager(); | ||||
|         showSystem = false; | ||||
|         if (PLATFORM == null) { | ||||
|             try { | ||||
|                 PLATFORM = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES); | ||||
|             } catch (PackageManager.NameNotFoundException ignored) {} | ||||
|         } | ||||
|         AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | ||||
|         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false); | ||||
|         return new ViewHolder(v); | ||||
|     } | ||||
|  | ||||
|     @WorkerThread | ||||
|     private void loadApps() { | ||||
|         fullList = pm.getInstalledPackages(PackageManager.GET_SIGNATURES); | ||||
|         hideList = Shell.su("magiskhide --ls").exec().getOut(); | ||||
|         for (Iterator<PackageInfo> i = fullList.iterator(); i.hasNext(); ) { | ||||
|             PackageInfo info = i.next(); | ||||
|             if (Const.HIDE_BLACKLIST.contains(info.packageName) || | ||||
|                     /* Do not show disabled apps */ | ||||
|                     !info.applicationInfo.enabled || | ||||
|                     /* Never show platform apps */ | ||||
|                     PLATFORM.signatures[0].equals(info.signatures[0])) { | ||||
|                 i.remove(); | ||||
|             } | ||||
|         } | ||||
|         Collections.sort(fullList, (a, b) -> { | ||||
|             boolean ah = hideList.contains(a.packageName); | ||||
|             boolean bh = hideList.contains(b.packageName); | ||||
|             if (ah == bh) { | ||||
|                 return Utils.getAppLabel(a.applicationInfo, pm) | ||||
|                         .compareToIgnoreCase(Utils.getAppLabel(b.applicationInfo, pm)); | ||||
|             } else if (ah) { | ||||
|                 return -1; | ||||
|             } else { | ||||
|                 return 1; | ||||
|             } | ||||
|         }); | ||||
|         Topic.publish(false, Topic.MAGISK_HIDE_DONE); | ||||
|     } | ||||
|  | ||||
|     public void setShowSystem(boolean b) { | ||||
|         showSystem = b; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull ViewHolder holder, int position) { | ||||
|         ApplicationInfo info = showList.get(position).applicationInfo; | ||||
|  | ||||
|         holder.appIcon.setImageDrawable(info.loadIcon(pm)); | ||||
|         holder.appName.setText(Utils.getAppLabel(info, pm)); | ||||
|         holder.appPackage.setText(info.packageName); | ||||
|  | ||||
|         holder.checkBox.setOnCheckedChangeListener(null); | ||||
|         holder.checkBox.setChecked(hideList.contains(info.packageName)); | ||||
|         holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> { | ||||
|             if (isChecked) { | ||||
|                 Shell.su("magiskhide --add " + info.packageName).submit(); | ||||
|                 hideList.add(info.packageName); | ||||
|             } else { | ||||
|                 Shell.su("magiskhide --rm " + info.packageName).submit(); | ||||
|                 hideList.remove(info.packageName); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return showList.size(); | ||||
|     } | ||||
|  | ||||
|     private boolean contains(String s, String filter) { | ||||
|         return s.toLowerCase().contains(filter); | ||||
|     } | ||||
|  | ||||
|     // Show if have launch intent or not system app | ||||
|     private boolean systemFilter(PackageInfo info) { | ||||
|         if (showSystem) | ||||
|             return true; | ||||
|         return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || | ||||
|                 pm.getLaunchIntentForPackage(info.packageName) != null; | ||||
|     } | ||||
|  | ||||
|     public void filter(String constraint) { | ||||
|         AsyncTask.SERIAL_EXECUTOR.execute(() -> { | ||||
|             showList = new ArrayList<>(); | ||||
|             if (constraint == null || constraint.length() == 0) { | ||||
|                 for (PackageInfo info : fullList) { | ||||
|                     if (systemFilter(info)) | ||||
|                         showList.add(info); | ||||
|                 } | ||||
|             } else { | ||||
|                 String filter = constraint.toLowerCase(); | ||||
|                 for (PackageInfo info : fullList) { | ||||
|                     if ((contains(Utils.getAppLabel(info.applicationInfo, pm), filter) || | ||||
|                             contains(info.packageName, filter)) && systemFilter(info)) { | ||||
|                         showList.add(info); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             UiThreadHandler.run(this::notifyDataSetChanged); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void refresh() { | ||||
|         AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps); | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.app_icon) ImageView appIcon; | ||||
|         @BindView(R.id.app_name) TextView appName; | ||||
|         @BindView(R.id.package_name) TextView appPackage; | ||||
|         @BindView(R.id.checkbox) CheckBox checkBox; | ||||
|  | ||||
|         ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,126 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.text.TextUtils; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.google.android.material.snackbar.Snackbar; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.container.Module; | ||||
| import com.topjohnwu.magisk.uicomponents.SnackbarMaker; | ||||
| import com.topjohnwu.superuser.Shell; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> { | ||||
|  | ||||
|     private final List<Module> mList; | ||||
|  | ||||
|     public ModulesAdapter(List<Module> list) { | ||||
|         mList = list; | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false); | ||||
|         return new ViewHolder(view); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(final ViewHolder holder, int position) { | ||||
|         Context context = holder.itemView.getContext(); | ||||
|         final Module module = mList.get(position); | ||||
|  | ||||
|         String version = module.getVersion(); | ||||
|         String author = module.getAuthor(); | ||||
|         String description = module.getDescription(); | ||||
|         String noInfo = context.getString(R.string.no_info_provided); | ||||
|  | ||||
|         holder.title.setText(module.getName()); | ||||
|         holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version); | ||||
|         holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author)); | ||||
|         holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description); | ||||
|  | ||||
|         holder.checkBox.setOnCheckedChangeListener(null); | ||||
|         holder.checkBox.setChecked(module.isEnabled()); | ||||
|         holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> { | ||||
|             int snack; | ||||
|             if (isChecked) { | ||||
|                 module.removeDisableFile(); | ||||
|                 snack = R.string.disable_file_removed; | ||||
|             } else { | ||||
|                 module.createDisableFile(); | ||||
|                 snack = R.string.disable_file_created; | ||||
|             } | ||||
|             SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show(); | ||||
|         }); | ||||
|  | ||||
|         holder.delete.setOnClickListener(v -> { | ||||
|             boolean removed = module.willBeRemoved(); | ||||
|             int snack; | ||||
|             if (removed) { | ||||
|                 module.deleteRemoveFile(); | ||||
|                 snack = R.string.remove_file_deleted; | ||||
|             } else { | ||||
|                 module.createRemoveFile(); | ||||
|                 snack = R.string.remove_file_created; | ||||
|             } | ||||
|             SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show(); | ||||
|             updateDeleteButton(holder, module); | ||||
|         }); | ||||
|  | ||||
|         if (module.isUpdated()) { | ||||
|             holder.notice.setVisibility(View.VISIBLE); | ||||
|             holder.notice.setText(R.string.update_file_created); | ||||
|             holder.delete.setEnabled(false); | ||||
|         } else { | ||||
|             updateDeleteButton(holder, module); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateDeleteButton(ViewHolder holder, Module module) { | ||||
|         holder.notice.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE); | ||||
|  | ||||
|         if (module.willBeRemoved()) { | ||||
|             holder.delete.setImageResource(R.drawable.ic_undelete); | ||||
|         } else { | ||||
|             holder.delete.setImageResource(R.drawable.ic_delete); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return mList.size(); | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.title) TextView title; | ||||
|         @BindView(R.id.version_name) TextView versionName; | ||||
|         @BindView(R.id.description) TextView description; | ||||
|         @BindView(R.id.notice) TextView notice; | ||||
|         @BindView(R.id.checkbox) CheckBox checkBox; | ||||
|         @BindView(R.id.author) TextView author; | ||||
|         @BindView(R.id.delete) ImageView delete; | ||||
|  | ||||
|         ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             new ModulesAdapter$ViewHolder_ViewBinding(this, itemView); | ||||
|  | ||||
|             if (!Shell.rootAccess()) { | ||||
|                 checkBox.setEnabled(false); | ||||
|                 delete.setEnabled(false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,161 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.google.android.material.snackbar.Snackbar; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.container.Policy; | ||||
| import com.topjohnwu.magisk.database.MagiskDB; | ||||
| import com.topjohnwu.magisk.dialogs.CustomAlertDialog; | ||||
| import com.topjohnwu.magisk.dialogs.FingerprintAuthDialog; | ||||
| import com.topjohnwu.magisk.uicomponents.ArrowExpandedViewHolder; | ||||
| import com.topjohnwu.magisk.uicomponents.ExpandableViewHolder; | ||||
| import com.topjohnwu.magisk.uicomponents.SnackbarMaker; | ||||
| import com.topjohnwu.magisk.utils.FingerprintHelper; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.widget.SwitchCompat; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> { | ||||
|  | ||||
|     private List<Policy> policyList; | ||||
|     private MagiskDB dbHelper; | ||||
|     private PackageManager pm; | ||||
|     private boolean[] expandList; | ||||
|  | ||||
|     public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) { | ||||
|         policyList = list; | ||||
|         expandList = new boolean[policyList.size()]; | ||||
|         dbHelper = db; | ||||
|         this.pm = pm; | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false); | ||||
|         return new ViewHolder(v); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(ViewHolder holder, int position) { | ||||
|         Policy policy = policyList.get(position); | ||||
|  | ||||
|         holder.settings.setExpanded(expandList[position]); | ||||
|         holder.trigger.setOnClickListener(view -> { | ||||
|             if (holder.settings.isExpanded()) { | ||||
|                 holder.settings.collapse(); | ||||
|                 expandList[position] = false; | ||||
|             } else { | ||||
|                 holder.settings.expand(); | ||||
|                 expandList[position] = true; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         holder.appName.setText(policy.appName); | ||||
|         holder.packageName.setText(policy.packageName); | ||||
|         holder.appIcon.setImageDrawable(policy.info.loadIcon(pm)); | ||||
|  | ||||
|         holder.notificationSwitch.setOnCheckedChangeListener(null); | ||||
|         holder.loggingSwitch.setOnCheckedChangeListener(null); | ||||
|  | ||||
|         holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW); | ||||
|         holder.notificationSwitch.setChecked(policy.notification); | ||||
|         holder.loggingSwitch.setChecked(policy.logging); | ||||
|  | ||||
|         holder.masterSwitch.setOnClickListener(v -> { | ||||
|             boolean isChecked = holder.masterSwitch.isChecked(); | ||||
|             Runnable r = () -> { | ||||
|                 if ((isChecked && policy.policy == Policy.DENY) || | ||||
|                         (!isChecked && policy.policy == Policy.ALLOW)) { | ||||
|                     policy.policy = isChecked ? Policy.ALLOW : Policy.DENY; | ||||
|                     String message = v.getContext().getString( | ||||
|                             isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName); | ||||
|                     SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show(); | ||||
|                     dbHelper.updatePolicy(policy); | ||||
|                 } | ||||
|             }; | ||||
|             if (FingerprintHelper.useFingerprint()) { | ||||
|                 holder.masterSwitch.setChecked(!isChecked); | ||||
|                 new FingerprintAuthDialog((Activity) v.getContext(), () -> { | ||||
|                     holder.masterSwitch.setChecked(isChecked); | ||||
|                     r.run(); | ||||
|                 }).show(); | ||||
|             } else { | ||||
|                 r.run(); | ||||
|             } | ||||
|         }); | ||||
|         holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> { | ||||
|             if ((isChecked && !policy.notification) || | ||||
|                     (!isChecked && policy.notification)) { | ||||
|                 policy.notification = isChecked; | ||||
|                 String message = v.getContext().getString( | ||||
|                         isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName); | ||||
|                 SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show(); | ||||
|                 dbHelper.updatePolicy(policy); | ||||
|             } | ||||
|         }); | ||||
|         holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> { | ||||
|             if ((isChecked && !policy.logging) || | ||||
|                     (!isChecked && policy.logging)) { | ||||
|                 policy.logging = isChecked; | ||||
|                 String message = v.getContext().getString( | ||||
|                         isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName); | ||||
|                 SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show(); | ||||
|                 dbHelper.updatePolicy(policy); | ||||
|             } | ||||
|         }); | ||||
|         holder.delete.setOnClickListener(v -> new CustomAlertDialog((Activity) v.getContext()) | ||||
|                 .setTitle(R.string.su_revoke_title) | ||||
|                 .setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName)) | ||||
|                 .setPositiveButton(R.string.yes, (dialog, which) -> { | ||||
|                     policyList.remove(position); | ||||
|                     notifyItemRemoved(position); | ||||
|                     notifyItemRangeChanged(position, policyList.size()); | ||||
|                     SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName), | ||||
|                             Snackbar.LENGTH_SHORT).show(); | ||||
|                     dbHelper.deletePolicy(policy); | ||||
|                 }) | ||||
|                 .setNegativeButton(R.string.no_thanks, null) | ||||
|                 .setCancelable(true) | ||||
|                 .show()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return policyList.size(); | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.app_name) TextView appName; | ||||
|         @BindView(R.id.package_name) TextView packageName; | ||||
|         @BindView(R.id.app_icon) ImageView appIcon; | ||||
|         @BindView(R.id.master_switch) SwitchCompat masterSwitch; | ||||
|         @BindView(R.id.notification_switch) SwitchCompat notificationSwitch; | ||||
|         @BindView(R.id.logging_switch) SwitchCompat loggingSwitch; | ||||
|         @BindView(R.id.expand_layout) ViewGroup expandLayout; | ||||
|         @BindView(R.id.arrow) ImageView arrow; | ||||
|         @BindView(R.id.trigger) View trigger; | ||||
|         @BindView(R.id.delete) ImageView delete; | ||||
|         @BindView(R.id.more_info) ImageView moreInfo; | ||||
|  | ||||
|         ExpandableViewHolder settings; | ||||
|  | ||||
|         public ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             new PolicyAdapter$ViewHolder_ViewBinding(this, itemView); | ||||
|             settings = new ArrowExpandedViewHolder(expandLayout, arrow); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,205 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.database.Cursor; | ||||
| import android.os.Build; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Pair; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.ClassMap; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.components.BaseActivity; | ||||
| import com.topjohnwu.magisk.components.DownloadModuleService; | ||||
| import com.topjohnwu.magisk.container.Module; | ||||
| import com.topjohnwu.magisk.container.Repo; | ||||
| import com.topjohnwu.magisk.database.RepoDatabaseHelper; | ||||
| import com.topjohnwu.magisk.dialogs.CustomAlertDialog; | ||||
| import com.topjohnwu.magisk.uicomponents.MarkDownWindow; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| import butterknife.BindView; | ||||
|  | ||||
| public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder> { | ||||
|  | ||||
|     private static final int UPDATES = 0; | ||||
|     private static final int INSTALLED = 1; | ||||
|     private static final int OTHERS = 2; | ||||
|  | ||||
|     private Cursor repoCursor = null; | ||||
|     private Map<String, Module> moduleMap; | ||||
|     private RepoDatabaseHelper repoDB; | ||||
|     private List<Pair<Integer, List<Repo>>> repoPairs; | ||||
|  | ||||
|     public ReposAdapter(RepoDatabaseHelper db, Map<String, Module> map) { | ||||
|         repoDB = db; | ||||
|         moduleMap = map; | ||||
|         repoPairs = new ArrayList<>(); | ||||
|         notifyDBChanged(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public int getSectionCount() { | ||||
|         return repoPairs.size(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount(int section) { | ||||
|         return repoPairs.get(section).second.size(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public SectionHolder onCreateSectionViewHolder(ViewGroup parent) { | ||||
|         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false); | ||||
|         return new SectionHolder(v); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { | ||||
|         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false); | ||||
|         return new RepoHolder(v); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindSectionViewHolder(SectionHolder holder, int section) { | ||||
|         switch (repoPairs.get(section).first) { | ||||
|             case UPDATES: | ||||
|                 holder.sectionText.setText(R.string.update_available); | ||||
|                 break; | ||||
|             case INSTALLED: | ||||
|                 holder.sectionText.setText(R.string.installed); | ||||
|                 break; | ||||
|             case OTHERS: | ||||
|                 holder.sectionText.setText(R.string.not_installed); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindItemViewHolder(RepoHolder holder, int section, int position) { | ||||
|         Repo repo = repoPairs.get(section).second.get(position); | ||||
|         Context context = holder.itemView.getContext(); | ||||
|  | ||||
|         String name = repo.getName(); | ||||
|         String version = repo.getVersion(); | ||||
|         String author = repo.getAuthor(); | ||||
|         String description = repo.getDescription(); | ||||
|         String noInfo = context.getString(R.string.no_info_provided); | ||||
|  | ||||
|         holder.title.setText(TextUtils.isEmpty(name) ? noInfo : name); | ||||
|         holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version); | ||||
|         holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author)); | ||||
|         holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description); | ||||
|         holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString())); | ||||
|  | ||||
|         holder.infoLayout.setOnClickListener(v -> | ||||
|                 MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl())); | ||||
|  | ||||
|         holder.downloadImage.setOnClickListener(v -> { | ||||
|             new CustomAlertDialog((BaseActivity) context) | ||||
|                 .setTitle(context.getString(R.string.repo_install_title, repo.getName())) | ||||
|                 .setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename())) | ||||
|                 .setCancelable(true) | ||||
|                 .setPositiveButton(R.string.install, (d, i) -> | ||||
|                         startDownload((BaseActivity) context, repo, true)) | ||||
|                 .setNeutralButton(R.string.download, (d, i) -> | ||||
|                         startDownload((BaseActivity) context, repo, false)) | ||||
|                 .setNegativeButton(R.string.no_thanks, null) | ||||
|                 .show(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private void startDownload(BaseActivity activity, Repo repo, Boolean install) { | ||||
|         activity.runWithExternalRW(() -> { | ||||
|             Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class)) | ||||
|                     .putExtra("repo", repo).putExtra("install", install); | ||||
|             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|                 activity.startForegroundService(intent); | ||||
|             } else { | ||||
|                 activity.startService(intent); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     public void notifyDBChanged() { | ||||
|         if (repoCursor != null) | ||||
|             repoCursor.close(); | ||||
|         repoCursor = repoDB.getRepoCursor(); | ||||
|         filter(""); | ||||
|     } | ||||
|  | ||||
|     public void filter(String s) { | ||||
|         List<Repo> updates = new ArrayList<>(); | ||||
|         List<Repo> installed = new ArrayList<>(); | ||||
|         List<Repo> others = new ArrayList<>(); | ||||
|  | ||||
|         repoPairs.clear(); | ||||
|         while (repoCursor.moveToNext()) { | ||||
|             Repo repo = new Repo(repoCursor); | ||||
|             if (repo.getName().toLowerCase().contains(s.toLowerCase()) | ||||
|                     || repo.getAuthor().toLowerCase().contains(s.toLowerCase()) | ||||
|                     || repo.getDescription().toLowerCase().contains(s.toLowerCase()) | ||||
|                     ) { | ||||
|                 // Passed the repoFilter | ||||
|                 Module module = moduleMap.get(repo.getId()); | ||||
|                 if (module != null) { | ||||
|                     if (repo.getVersionCode() > module.getVersionCode()) { | ||||
|                         // Updates | ||||
|                         updates.add(repo); | ||||
|                     } else { | ||||
|                         installed.add(repo); | ||||
|                     } | ||||
|                 } else { | ||||
|                     others.add(repo); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         repoCursor.moveToFirst(); | ||||
|  | ||||
|         if (!updates.isEmpty()) | ||||
|             repoPairs.add(new Pair<>(UPDATES, updates)); | ||||
|         if (!installed.isEmpty()) | ||||
|             repoPairs.add(new Pair<>(INSTALLED, installed)); | ||||
|         if (!others.isEmpty()) | ||||
|             repoPairs.add(new Pair<>(OTHERS, others)); | ||||
|  | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     static class SectionHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.section_text) TextView sectionText; | ||||
|  | ||||
|         SectionHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             new ReposAdapter$SectionHolder_ViewBinding(this, itemView); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static class RepoHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.title) TextView title; | ||||
|         @BindView(R.id.version_name) TextView versionName; | ||||
|         @BindView(R.id.description) TextView description; | ||||
|         @BindView(R.id.author) TextView author; | ||||
|         @BindView(R.id.info_layout) View infoLayout; | ||||
|         @BindView(R.id.download) ImageView downloadImage; | ||||
|         @BindView(R.id.update_time) TextView updateTime; | ||||
|  | ||||
|         RepoHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             new ReposAdapter$RepoHolder_ViewBinding(this, itemView); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user