mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-10-30 09:00:52 +01:00 
			
		
		
		
	Compare commits
	
		
			588 Commits
		
	
	
		
			manager-v3
			...
			manager-v5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 759e905c3c | ||
|   | 8bf7e42913 | ||
|   | 0dcd073554 | ||
|   | 2fe35d578d | ||
|   | 8d139e156e | ||
|   | 7c2849356a | ||
|   | 0025ffd1c0 | ||
|   | 2ef7146642 | ||
|   | 1b27e69e40 | ||
|   | 8e7b757efd | ||
|   | 1ab543cea1 | ||
|   | a3f86903e4 | ||
|   | c239c305ab | ||
|   | 2e02af994e | ||
|   | 836d9afe17 | ||
|   | 007a352742 | ||
|   | e526e5659e | ||
|   | 4a5227c7bf | ||
|   | c2c151ec4c | ||
|   | 452096e7e4 | ||
|   | 50c2a9859e | ||
|   | 677b667307 | ||
|   | 1adf331268 | ||
|   | 349b3e961b | ||
|   | 96650c06f0 | ||
|   | 26038a0a07 | ||
|   | 6a148b5dd9 | ||
|   | 0e109ef979 | ||
|   | de2285d5e9 | ||
|   | b2483ba437 | ||
|   | a82a5e5a49 | ||
|   | d161a02e71 | ||
|   | d2b6a700b1 | ||
|   | af203cef24 | ||
|   | 673e917e76 | ||
|   | a3bd41db54 | ||
|   | 0d9527921a | ||
|   | f0e4aec0af | ||
|   | b0d65b5edd | ||
|   | 75532ef591 | ||
|   | 9a6d1bd700 | ||
|   | a7ed6c15d3 | ||
|   | 5ee49ba065 | ||
|   | d34bd47bea | ||
|   | f17792380b | ||
|   | c11920110e | ||
|   | ec5a993fea | ||
|   | d250c2cc89 | ||
|   | 767e73f40c | ||
|   | 3f699c9d2f | ||
|   | 50dbd9befd | ||
|   | 760e01bf92 | ||
|   | 543f435b1e | ||
|   | 91337218b3 | ||
|   | afff3c0a49 | ||
|   | a1871e4bc3 | ||
|   | 3aa0294cd4 | ||
|   | 310b266251 | ||
|   | 21b1b5098e | ||
|   | a3a4a5d8a5 | ||
|   | 270536f33c | ||
|   | 66bb433cc6 | ||
|   | bd4ef1a03a | ||
|   | aa2d9a3bf1 | ||
|   | fd6cbb138c | ||
|   | aa75c8e5e4 | ||
|   | c461fc6daa | ||
|   | 96eaa833f5 | ||
|   | 863b13a694 | ||
|   | e6fea4e6dd | ||
|   | 83bfc13056 | ||
|   | bc4f09209b | ||
|   | 967ca17238 | ||
|   | 595c72147c | ||
|   | f3c3b5a649 | ||
|   | 1cd2c5e653 | ||
|   | b2873dd44b | ||
|   | bb80ab4026 | ||
|   | 80cabb338b | ||
|   | 2c69e2c151 | ||
|   | c1dd23f5e0 | ||
|   | f93624a41c | ||
|   | 9f4559a059 | ||
|   | fd05cad303 | ||
|   | d58b06e493 | ||
|   | 2f0b549027 | ||
|   | 87dbd7e541 | ||
|   | 96e5da36be | ||
|   | 43745edac0 | ||
|   | f5ceee547c | ||
|   | b612bce779 | ||
|   | 2e88e5e9c7 | ||
|   | 9a7aa25c90 | ||
|   | c4420fe932 | ||
|   | a5260f3a95 | ||
|   | 47ccf4b1f5 | ||
|   | a356b21895 | ||
|   | 614a36c888 | ||
|   | f520fe36bd | ||
|   | 7273a1c34d | ||
|   | dc45cbce37 | ||
|   | 708d8f75c0 | ||
|   | bd37d90228 | ||
|   | b1ad691464 | ||
|   | f4e7baf31e | ||
|   | c0e60c41f2 | ||
|   | c8dad43e00 | ||
|   | a8f124704d | ||
|   | eed2816491 | ||
|   | a6334b3e35 | ||
|   | 334beebfeb | ||
|   | 13dad848bd | ||
|   | e518f4cef8 | ||
|   | c8fd5da2da | ||
|   | 3a74729ecc | ||
|   | 49c672ac4d | ||
|   | b570cb5b77 | ||
|   | 97bf388471 | ||
|   | 1a32aaea6f | ||
|   | 4635883dec | ||
|   | 3ba6db4a50 | ||
|   | 2f1de25747 | ||
|   | f60fd42ac0 | ||
|   | ecc8f9c792 | ||
|   | e295dfdcf7 | ||
|   | fc42c25390 | ||
|   | 27d5858e06 | ||
|   | e1ef732b60 | ||
|   | 9840b95c21 | ||
|   | a6f8446d81 | ||
|   | c1c844c830 | ||
|   | 389299afd1 | ||
|   | 826543a291 | ||
|   | 4ac83cfded | ||
|   | 64c363ce53 | ||
|   | cca4347bf9 | ||
|   | 3ae3d4926a | ||
|   | 36025d6d9f | ||
|   | e171362e3e | ||
|   | 3e0bf2ae15 | ||
|   | 07aa9f4b8b | ||
|   | b2d9f3fc64 | ||
|   | 5fb3e9167e | ||
|   | 99c74b31be | ||
|   | ce5b13824e | ||
|   | c39170c42e | ||
|   | fd19fbf300 | ||
|   | 166469827f | ||
|   | a34ed538b6 | ||
|   | 5f22d3e055 | ||
|   | fdd700f3e5 | ||
|   | adf930f126 | ||
|   | 05f41928cd | ||
|   | 2ee0829871 | ||
|   | 743560825d | ||
|   | e3d84ac349 | ||
|   | 266c832b30 | ||
|   | f5374a024e | ||
|   | 4956d826fb | ||
|   | f5cc2af5d0 | ||
|   | 5880d4a6ec | ||
|   | ae05dce958 | ||
|   | 9ebe372a9a | ||
|   | e6e04cc5b3 | ||
|   | 12352510fd | ||
|   | 2b3d927937 | ||
|   | a8890740f5 | ||
|   | f60d7ee54b | ||
|   | 896ca2ef6b | ||
|   | c036f6d529 | ||
|   | 6f457c0c59 | ||
|   | 13bf1b27b4 | ||
|   | f742bb1c47 | ||
|   | aa0b9e2db2 | ||
|   | c10076f7ed | ||
|   | bcd92499f2 | ||
|   | b2bb0d4f72 | ||
|   | e140481f14 | ||
|   | 186bd11463 | ||
|   | a0490d6687 | ||
|   | beef740ade | ||
|   | 2ac7786a90 | ||
|   | a3fb5e910f | ||
|   | 319afe86b5 | ||
|   | 762ab66b86 | ||
|   | 0c239a42de | ||
|   | e9322fba26 | ||
|   | 39b6df27b3 | ||
|   | b1ee284e7f | ||
|   | e986332bf2 | ||
|   | 48f9b27381 | ||
|   | 42a6e0dd10 | ||
|   | d4798b02ac | ||
|   | 963edfe8ab | ||
|   | 53237f3ae0 | ||
|   | 64da9281a4 | ||
|   | ab7fd9799d | ||
|   | f6bcc84251 | ||
|   | 35dc3d9df9 | ||
|   | 566714a75d | ||
|   | c92f30b122 | ||
|   | 294ad094c4 | ||
|   | c1a0f520f9 | ||
|   | 773c24b7fc | ||
|   | 8f926c7ca9 | ||
|   | c562cbc2bb | ||
|   | 3fbbb0865a | ||
|   | 7d5f612a48 | ||
|   | 4a5a36440b | ||
|   | 43dd5cfea1 | ||
|   | 7b5fec1842 | ||
|   | 5762ded601 | ||
|   | a3abb86daa | ||
|   | 4f5c656b05 | ||
|   | a31cddbe7b | ||
|   | b4ecd93f1c | ||
|   | 0acc23e058 | ||
|   | cdd5f9b628 | ||
|   | 4c9f5f4655 | ||
|   | b80ba13cb4 | ||
|   | 8260bdc09c | ||
|   | 24f856e02b | ||
|   | 3aa619b928 | ||
|   | 4cb5e98d94 | ||
|   | 272910575e | ||
|   | a15a62f4bc | ||
|   | 53cf11db8c | ||
|   | 01052fbe47 | ||
|   | a5e1e075c7 | ||
|   | 6be32ac688 | ||
|   | b362c0ef38 | ||
|   | bba9969e31 | ||
|   | 007ba24809 | ||
|   | df21539311 | ||
|   | 2592cb6019 | ||
|   | f7df17a7ed | ||
|   | 62f42b72f8 | ||
|   | a1ba4fda6f | ||
|   | 1c06b04c45 | ||
|   | 2ee22fd374 | ||
|   | 4c230d9e61 | ||
|   | 727294fbbe | ||
|   | 478c43969b | ||
|   | 79b5303350 | ||
|   | ce4b742b25 | ||
|   | a9dc15bda5 | ||
|   | ba6387ff5c | ||
|   | 8fa98508b7 | ||
|   | decdbaecf9 | ||
|   | 6d87cf9be0 | ||
|   | 94f434c4a6 | ||
|   | 7ba867c30b | ||
|   | 3424395e10 | ||
|   | 926c7359a2 | ||
|   | ec0af99a2e | ||
|   | b4d948886c | ||
|   | 4d8d79372a | ||
|   | 04a589722c | ||
|   | d4a10e2873 | ||
|   | 4998ad6c7e | ||
|   | a07ca5ff50 | ||
|   | f07e7571ab | ||
|   | 834c16485c | ||
|   | 04a4265ef3 | ||
|   | 0ec473195d | ||
|   | 0bf09256b0 | ||
|   | db8fd2c913 | ||
|   | dbe6e5b3d7 | ||
|   | cc81cd446b | ||
|   | 439c7118f1 | ||
|   | d8154a5815 | ||
|   | 4e3787bc0d | ||
|   | 02e0955924 | ||
|   | a78950e822 | ||
|   | 1ce1a94a35 | ||
|   | 977b6d9f67 | ||
|   | b5e6dbd797 | ||
|   | 833e6688f1 | ||
|   | bc22c9f84f | ||
|   | 2149a7d116 | ||
|   | 29175d2c17 | ||
|   | 803454d5c8 | ||
|   | 36cf32dc42 | ||
|   | 657f4ab303 | ||
|   | ea6552615d | ||
|   | 4bf3287fce | ||
|   | 832c2034c2 | ||
|   | b0aa26e1f1 | ||
|   | e52baeb967 | ||
|   | 8268eb9a83 | ||
|   | 3cc458abd9 | ||
|   | 337b4c4268 | ||
|   | 001f8657f6 | ||
|   | ea884e7fa1 | ||
|   | 1b1394cf5d | ||
|   | 1eef930dbb | ||
|   | 1e175e74ed | ||
|   | 75a46c365e | ||
|   | 8e7b8825f5 | ||
|   | 2ecbca303b | ||
|   | 8195a4d616 | ||
|   | 7ba40f925f | ||
|   | 345cd1795f | ||
|   | 959aaee045 | ||
|   | 53477f0f59 | ||
|   | 5716218f41 | ||
|   | 9df6b9d5c0 | ||
|   | ec46031d36 | ||
|   | 55b84d166a | ||
|   | 34ae8bacec | ||
|   | cb4e5ca0f7 | ||
|   | 0ba45468c4 | ||
|   | 710502784e | ||
|   | 0275a8558d | ||
|   | 58acc75cf6 | ||
|   | 874ababb9f | ||
|   | 3771e6b0cd | ||
|   | 33eaefa966 | ||
|   | cd7e236d57 | ||
|   | 54c0b7c7d5 | ||
|   | a2177daec2 | ||
|   | 628386b453 | ||
|   | b222bfb3e0 | ||
|   | ab199d883d | ||
|   | 356065d1ee | ||
|   | 76e7c5623d | ||
|   | 085fba050a | ||
|   | 295334d3ac | ||
|   | 36124ddca4 | ||
|   | bd6585765e | ||
|   | c325deb4ed | ||
|   | 73bb0b10ee | ||
|   | 72820b162c | ||
|   | 89e5b8d057 | ||
|   | da4f53ebbb | ||
|   | 8458553b74 | ||
|   | 55ecc41d06 | ||
|   | 28fcdf2cbb | ||
|   | 24087679a8 | ||
|   | 5ac6a8cb4a | ||
|   | 668d85d14e | ||
|   | c11a3dc95c | ||
|   | 56f57c20a2 | ||
|   | 240d14779a | ||
|   | 3550d1e61c | ||
|   | 6513ad249c | ||
|   | 50297b1880 | ||
|   | f189b78b9e | ||
|   | 5c0250f495 | ||
|   | 2093f726e9 | ||
|   | 10efe3859d | ||
|   | 6933bcf7bb | ||
|   | 2ea046cd80 | ||
|   | f4097a372b | ||
|   | 87ea2a2bef | ||
|   | cc14a1c361 | ||
|   | bcdface60d | ||
|   | 4dc9419d2e | ||
|   | d2bcac813e | ||
|   | 080c37a7f6 | ||
|   | f9a3838db6 | ||
|   | 1e61db104b | ||
|   | 30a9c7718d | ||
|   | 34b052b5d3 | ||
|   | aaa12853ad | ||
|   | b0ab55b0bf | ||
|   | d2f8496f4e | ||
|   | 1a69b16d36 | ||
|   | b5e8673e62 | ||
|   | 264c6a50b6 | ||
|   | 493642eb38 | ||
|   | 28d42b9164 | ||
|   | 42f29062ca | ||
|   | c4377ed6c2 | ||
|   | 7d283ed65f | ||
|   | bf1f941e50 | ||
|   | 789fef34ba | ||
|   | 1daf5a611c | ||
|   | 6aed1db67e | ||
|   | cf68854770 | ||
|   | 711392c73b | ||
|   | 9573c32481 | ||
|   | a15f80f79d | ||
|   | 23e7475f06 | ||
|   | 1eb571b787 | ||
|   | dd3b716d85 | ||
|   | 28649c07e3 | ||
|   | 961e02be0d | ||
|   | a161491bfd | ||
|   | e0b4d1c1e4 | ||
|   | fd4aaab137 | ||
|   | 42d14d5ca2 | ||
|   | d3ff482c9b | ||
|   | f682368eeb | ||
|   | 4a5d033efb | ||
|   | 343161b195 | ||
|   | bc576a9659 | ||
|   | 19e407fcc4 | ||
|   | bc7327d004 | ||
|   | 666fa1c797 | ||
|   | 0eda4a7821 | ||
|   | 862058fd2b | ||
|   | 69e5bcd57d | ||
|   | efeddda328 | ||
|   | ff6938280e | ||
|   | 1e4425b30f | ||
|   | b5d1d8cdad | ||
|   | 029be5ccca | ||
|   | 29c2d785b5 | ||
|   | abda8cfa32 | ||
|   | 44e7d79d4c | ||
|   | 9a1dc8ee0e | ||
|   | 27879c3f01 | ||
|   | 29096eb5d7 | ||
|   | a573baea03 | ||
|   | 5af07c4531 | ||
|   | 44e36feb09 | ||
|   | 2a7d996881 | ||
|   | 738f943a68 | ||
|   | 47e62a5681 | ||
|   | 1ecbfd7590 | ||
|   | 67c139a04b | ||
|   | 31cc008249 | ||
|   | 9cb026439d | ||
|   | e6f10176c6 | ||
|   | 0917c79470 | ||
|   | 597baa986d | ||
|   | 75cc4b4843 | ||
|   | aac088d496 | ||
|   | a822e5bbc5 | ||
|   | c527249c21 | ||
|   | 9ef798f534 | ||
|   | e69b99f089 | ||
|   | 55b8079e86 | ||
|   | e272dbe9af | ||
|   | 962f8354ac | ||
|   | 20e4a960f7 | ||
|   | 82249cb50a | ||
|   | fad417e553 | ||
|   | 5ba692f50c | ||
|   | 907e01e524 | ||
|   | b8ed23efa7 | ||
|   | 2b3bbf7e67 | ||
|   | 464fe627a3 | ||
|   | 6a9e39c470 | ||
|   | 7fec9a3cc6 | ||
|   | 008f6ef462 | ||
|   | 2440c108ca | ||
|   | 430baad8a4 | ||
|   | 51132e74b4 | ||
|   | a4f33e106a | ||
|   | baba3190e0 | ||
|   | 47b13aa5ea | ||
|   | ae88d3054d | ||
|   | 411b600e14 | ||
|   | 0a0ad9a184 | ||
|   | 234bead59e | ||
|   | 76de310986 | ||
|   | 817f050bcd | ||
|   | 60ae685d1e | ||
|   | 4c7bdbb284 | ||
|   | 435251ca41 | ||
|   | 324a0dd38f | ||
|   | cc77d93918 | ||
|   | 0ea7d8bd8c | ||
|   | 849b217143 | ||
|   | 9af6efba59 | ||
|   | 079d6f06ef | ||
|   | 9cf0757689 | ||
|   | b54c438948 | ||
|   | c3ff4bfdad | ||
|   | 5d62e066e2 | ||
|   | e94219c5a3 | ||
|   | 8ed9634adf | ||
|   | 0aefa9599f | ||
|   | e279cf0575 | ||
|   | a3f0ef8e77 | ||
|   | 8eba05ed4a | ||
|   | 2f78155723 | ||
|   | 6785221479 | ||
|   | 9bc410dd3d | ||
|   | 2491ab6bf9 | ||
|   | f615ed40cd | ||
|   | 430f2cafc1 | ||
|   | 0ad049da88 | ||
|   | 2c7691567b | ||
|   | 1d70d0fe94 | ||
|   | ac44f05811 | ||
|   | d99252f394 | ||
|   | b58c7ba7c5 | ||
|   | 8c5acd1a0a | ||
|   | b9b1ebf18c | ||
|   | 8ca132cef0 | ||
|   | a03bb90754 | ||
|   | d1c939f48a | ||
|   | 21b11f1b48 | ||
|   | 23c84a7803 | ||
|   | f9ab060403 | ||
|   | df7a5bf149 | ||
|   | c4afa069df | ||
|   | 1bfafdb44f | ||
|   | 1ef5bd7076 | ||
|   | 29176fa4f4 | ||
|   | 958c95732b | ||
|   | 44b0d4127c | ||
|   | 1418ec2416 | ||
|   | b51978f51c | ||
|   | b07361580a | ||
|   | d1b5ebad7d | ||
|   | f4ce813de9 | ||
|   | b44ac994d8 | ||
|   | 333948814c | ||
|   | 1a51ad6e01 | ||
|   | 22a5c11f0d | ||
|   | 51b22d1ad4 | ||
|   | bef5969580 | ||
|   | c6bf7bb9cd | ||
|   | 2a84d92cbf | ||
|   | 62de36b0da | ||
|   | 03a9aaeff7 | ||
|   | 45765e292d | ||
|   | 6e28a26015 | ||
|   | 9150bf720d | ||
|   | 845864679c | ||
|   | b3b2149ebb | ||
|   | 0886dca385 | ||
|   | 53198ba4a7 | ||
|   | a9652ee1fd | ||
|   | 75caf2f01c | ||
|   | 65bab2666e | ||
|   | 6d93ae399a | ||
|   | 7239c2e31a | ||
|   | 43b7ef8110 | ||
|   | 99ef0b8cb4 | ||
|   | 0efb4da0ee | ||
|   | ed7920d61e | ||
|   | c0379c8e25 | ||
|   | 00a0e64fdd | ||
|   | 0dc60debea | ||
|   | c44ae5888c | ||
|   | b9495cd1bb | ||
|   | bfec381933 | ||
|   | 2dddb8df69 | ||
|   | d30397e9c0 | ||
|   | d9597549fd | ||
|   | 13512b4146 | ||
|   | 49e546919a | ||
|   | 586015c2ed | ||
|   | 4a7e067d1a | ||
|   | 9bc0b7f183 | ||
|   | cd4dfc9861 | ||
|   | 09bdbc1224 | ||
|   | 978b3a64c5 | ||
|   | 651547ef20 | ||
|   | b4d95977d0 | ||
|   | 5d8bb897db | ||
|   | 84c8ecb372 | ||
|   | 61abe5b948 | ||
|   | a5b573eaaa | ||
|   | cbb32f82eb | ||
|   | ca9334b2df | ||
|   | 959ed7f866 | ||
|   | a5c0411be0 | ||
|   | 32e1303742 | ||
|   | 7263b6fe89 | ||
|   | 46a4070f84 | ||
|   | c3c155a1ed | ||
|   | b067105660 | ||
|   | 15ca18848e | ||
|   | 67c9e2ead6 | ||
|   | 3681177be4 | ||
|   | 6eb814ef0b | ||
|   | bcc695234c | ||
|   | ad16a6fc1b | ||
|   | 478b7eeb65 | ||
|   | 151a153dc9 | ||
|   | ad131854ca | ||
|   | 0bd0eb9e59 | ||
|   | cf16fd0104 | ||
|   | 21b00ac6ca | ||
|   | 57e6f3080c | ||
|   | 89744100ce | ||
|   | a718f9bbfd | ||
|   | e81bc4f044 | ||
|   | 4dbacd79ae | ||
|   | ae74d54451 | ||
|   | dc316c5669 | ||
|   | e9f04256c9 | 
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,10 @@ | ||||
| /local.properties | ||||
| .idea/ | ||||
| /build | ||||
| app/app-release.apk | ||||
| app/release | ||||
| *.hprof | ||||
| app/.externalNativeBuild/ | ||||
| .externalNativeBuild/ | ||||
| *.sh | ||||
| public.certificate.x509.pem | ||||
| private.key.pk8 | ||||
| *.apk | ||||
|   | ||||
| @@ -1,4 +1,2 @@ | ||||
| # Magisk Manager | ||||
| The project should be built with Android Studio version 2.2.0+   | ||||
| I use Java 8 features, which requires Jack compiler and it's only available in 2.2.0+   | ||||
| Also, you need to install CMake and NDK to build the zipadjust library for zip preprocessing | ||||
| This repo is no longer an independent component. It is a submodule of the [Magisk Project](https://github.com/topjohnwu/Magisk). | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								app/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /build | ||||
| @@ -1,5 +0,0 @@ | ||||
| cmake_minimum_required(VERSION 3.6) | ||||
| add_library(zipadjust SHARED src/main/jni/zipadjust.c) | ||||
| find_library(libz z) | ||||
| find_library(liblog log) | ||||
| target_link_libraries(zipadjust ${libz} ${liblog}) | ||||
| @@ -1,64 +0,0 @@ | ||||
| apply plugin: 'com.android.application' | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 25 | ||||
|     buildToolsVersion "25.0.2" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "com.topjohnwu.magisk" | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 25 | ||||
|         versionCode 12 | ||||
|         versionName "3.1" | ||||
|         jackOptions { | ||||
|             enabled true | ||||
|         } | ||||
|         ndk { | ||||
|             moduleName 'zipadjust' | ||||
|             abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     compileOptions { | ||||
|         incremental false | ||||
|     } | ||||
|  | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled true | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
|     compileOptions { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility JavaVersion.VERSION_1_8 | ||||
|     } | ||||
|     dexOptions { | ||||
|         preDexLibraries = false | ||||
|     } | ||||
|     externalNativeBuild { | ||||
|         cmake { | ||||
|             path 'CMakeLists.txt' | ||||
|         } | ||||
|     } | ||||
| } | ||||
| repositories { | ||||
|     jcenter() | ||||
|     maven { url "https://jitpack.io" } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     compile fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     compile 'com.android.support:recyclerview-v7:25.1.0' | ||||
|     compile 'com.android.support:cardview-v7:25.1.0' | ||||
|     compile 'com.android.support:design:25.1.0' | ||||
|     compile 'com.jakewharton:butterknife:8.4.0' | ||||
|     compile 'com.google.code.gson:gson:2.8.0' | ||||
|     compile 'com.github.clans:fab:1.6.4' | ||||
|     compile 'com.madgag.spongycastle:core:1.54.0.0' | ||||
|     compile 'com.madgag.spongycastle:prov:1.54.0.0' | ||||
|     compile 'com.madgag.spongycastle:pkix:1.54.0.0' | ||||
|     compile 'com.madgag.spongycastle:pg:1.54.0.0' | ||||
|     compile 'com.google.android.gms:play-services-safetynet:9.0.1' | ||||
|     annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' | ||||
| } | ||||
							
								
								
									
										46
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							| @@ -1,46 +0,0 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # By default, the flags in this file are appended to flags specified | ||||
| # in /Users/topjohnwu/Library/Android/sdk/tools/proguard/proguard-android.txt | ||||
| # You can edit the include path and order by changing the proguardFiles | ||||
| # directive in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
|  | ||||
| # Add any project specific keep options here: | ||||
|  | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
|  | ||||
| # Gson uses generic type information stored in a class file when working with fields. Proguard | ||||
| # removes such information by default, so configure it to keep all of it. | ||||
| -keepattributes Signature | ||||
|  | ||||
| # For using GSON @Expose annotation | ||||
| -keepattributes *Annotation* | ||||
|  | ||||
| # Gson specific classes | ||||
| -keep class sun.misc.Unsafe { *; } | ||||
| -keep class com.google.gson.** { *; } | ||||
|  | ||||
| # Application classes that will be serialized/deserialized over Gson | ||||
| -keep class com.topjohnwu.magisk.module.** { *; } | ||||
| -keep class com.topjohnwu.magisk.utils.ModuleHelper$ValueSortedMap { *; } | ||||
|  | ||||
| # Prevent proguard from stripping interface information from TypeAdapterFactory, | ||||
| # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) | ||||
| -keep class * implements com.google.gson.TypeAdapterFactory | ||||
| -keep class * implements com.google.gson.JsonSerializer | ||||
| -keep class * implements com.google.gson.JsonDeserializer | ||||
|  | ||||
| -keep class android.support.v7.internal.** { *; } | ||||
| -keep interface android.support.v7.internal.** { *; } | ||||
| -keep class android.support.v7.** { *; } | ||||
| -keep interface android.support.v7.** { *; } | ||||
|  | ||||
| # SpongyCastle | ||||
| -keep class org.spongycastle.** {*;} | ||||
| @@ -1,61 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest package="com.topjohnwu.magisk" | ||||
|           xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|  | ||||
|           xmlns:tools="http://schemas.android.com/tools"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||
|     <uses-permission | ||||
|         android:name="android.permission.PACKAGE_USAGE_STATS" | ||||
|         tools:ignore="ProtectedPermissions" /> | ||||
|  | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/AppTheme" | ||||
|         tools:ignore="AllowBackup,GoogleAppIndexingWarning"> | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:configChanges="orientation|screenSize" | ||||
|             android:exported="true"/> | ||||
|  | ||||
|         <activity | ||||
|             android:name=".SplashActivity" | ||||
|             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=".AboutActivity" | ||||
|             android:theme="@style/AppTheme.Transparent"/> | ||||
|         <activity | ||||
|             android:name=".SettingsActivity" | ||||
|             android:theme="@style/AppTheme.Transparent" /> | ||||
|  | ||||
|         <provider | ||||
|             android:name="android.support.v4.content.FileProvider" | ||||
|             android:authorities="com.topjohnwu.magisk.provider" | ||||
|             android:exported="false" | ||||
|             android:grantUriPermissions="true"> | ||||
|             <meta-data | ||||
|                 android:name="android.support.FILE_PROVIDER_PATHS" | ||||
|                 android:resource="@xml/file_paths" /> | ||||
|         </provider> | ||||
|  | ||||
|         <meta-data | ||||
|             android:name="com.google.android.gms.version" | ||||
|             android:value="@integer/google_play_services_version" /> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
										
											Binary file not shown.
										
									
								
							| @@ -1,27 +0,0 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD | ||||
| VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g | ||||
| VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE | ||||
| AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe | ||||
| Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET | ||||
| MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G | ||||
| A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p | ||||
| ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI | ||||
| hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM | ||||
| qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 | ||||
| wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy | ||||
| 4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU | ||||
| RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s | ||||
| zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw | ||||
| HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ | ||||
| AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE | ||||
| CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH | ||||
| QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG | ||||
| CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud | ||||
| EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa | ||||
| J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y | ||||
| LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe | ||||
| +ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX | ||||
| 31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr | ||||
| sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= | ||||
| -----END CERTIFICATE----- | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 42 KiB | 
| @@ -1,152 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.AlertDialog; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.text.Html; | ||||
| import android.text.Spanned; | ||||
| import android.text.TextUtils; | ||||
| import android.text.method.LinkMovementMethod; | ||||
| import android.view.View; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class AboutActivity extends AppCompatActivity { | ||||
|  | ||||
|     private static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager"; | ||||
|     private static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"; | ||||
|     private static final String DONATION_URL = "http://topjohnwu.github.io/donate"; | ||||
|     @BindView(R.id.toolbar) Toolbar toolbar; | ||||
|     @BindView(R.id.app_version_info) AboutCardRow appVersionInfo; | ||||
|     @BindView(R.id.app_changelog) AboutCardRow appChangelog; | ||||
|     @BindView(R.id.app_developers) AboutCardRow appDevelopers; | ||||
|     @BindView(R.id.app_translators) AboutCardRow appTranslators; | ||||
|     @BindView(R.id.app_source_code) AboutCardRow appSourceCode; | ||||
|     @BindView(R.id.support_thread) AboutCardRow supportThread; | ||||
|     @BindView(R.id.donation) AboutCardRow donation; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(@Nullable Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", ""); | ||||
|         Logger.dev("AboutActivity: Theme is " + theme); | ||||
|         if (Utils.isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_dh); | ||||
|         } | ||||
|         setContentView(R.layout.activity_about); | ||||
|         ButterKnife.bind(this); | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|         toolbar.setNavigationOnClickListener(view -> finish()); | ||||
|  | ||||
|         ActionBar ab = getSupportActionBar(); | ||||
|         if (ab != null) { | ||||
|             ab.setTitle(R.string.about); | ||||
|             ab.setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|  | ||||
|         appVersionInfo.setSummary(BuildConfig.VERSION_NAME); | ||||
|  | ||||
|         String changes = null; | ||||
|         try { | ||||
|             InputStream is = getAssets().open("changelog.html"); | ||||
|             int size = is.available(); | ||||
|  | ||||
|             byte[] buffer = new byte[size]; | ||||
|             is.read(buffer); | ||||
|             is.close(); | ||||
|  | ||||
|             changes = new String(buffer); | ||||
|         } catch (IOException ignored) { | ||||
|         } | ||||
|  | ||||
|         appChangelog.removeSummary(); | ||||
|         if (changes == null) { | ||||
|             appChangelog.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             Spanned result; | ||||
|             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { | ||||
|                 result = Html.fromHtml(changes, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); | ||||
|             } else { | ||||
|                 result = Html.fromHtml(changes); | ||||
|             } | ||||
|             appChangelog.setOnClickListener(v -> { | ||||
|                 AlertDialog d = Utils.getAlertDialogBuilder(this) | ||||
|                         .setTitle(R.string.app_changelog) | ||||
|                         .setMessage(result) | ||||
|                         .setPositiveButton(android.R.string.ok, null) | ||||
|                         .show(); | ||||
|  | ||||
|                 //noinspection ConstantConditions | ||||
|                 ((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         appDevelopers.removeSummary(); | ||||
|         appDevelopers.setOnClickListener(view -> { | ||||
|             Spanned result; | ||||
|             if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { | ||||
|                 result = Html.fromHtml(getString(R.string.app_developers_), Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE); | ||||
|             } else { | ||||
|                 result = Html.fromHtml(getString(R.string.app_developers_)); | ||||
|             } | ||||
|             AlertDialog d = Utils.getAlertDialogBuilder(this) | ||||
|                     .setTitle(R.string.app_developers) | ||||
|                     .setMessage(result) | ||||
|                     .setPositiveButton(android.R.string.ok, null) | ||||
|                     .create(); | ||||
|  | ||||
|             d.show(); | ||||
|             //noinspection ConstantConditions | ||||
|             ((TextView) d.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|         }); | ||||
|  | ||||
|         String translators = getString(R.string.translators); | ||||
|         if (TextUtils.isEmpty(translators)) { | ||||
|             appTranslators.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             appTranslators.setSummary(translators); | ||||
|         } | ||||
|  | ||||
|         appSourceCode.removeSummary(); | ||||
|         appSourceCode.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(SOURCE_CODE_URL)))); | ||||
|  | ||||
|         supportThread.removeSummary(); | ||||
|         supportThread.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(XDA_THREAD)))); | ||||
|  | ||||
|         donation.removeSummary(); | ||||
|         donation.setOnClickListener(view -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)))); | ||||
|  | ||||
|         setFloating(); | ||||
|     } | ||||
|  | ||||
|     public void setFloating() { | ||||
|         boolean isTablet = getResources().getBoolean(R.bool.isTablet); | ||||
|         if (isTablet) { | ||||
|             WindowManager.LayoutParams params = getWindow().getAttributes(); | ||||
|             params.height = getResources().getDimensionPixelSize(R.dimen.floating_height); | ||||
|             params.width = getResources().getDimensionPixelSize(R.dimen.floating_width); | ||||
|             params.alpha = 1.0f; | ||||
|             params.dimAmount = 0.6f; | ||||
|             params.flags |= 2; | ||||
|             getWindow().setAttributes(params); | ||||
|             setFinishOnTouchOutside(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,113 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.support.design.widget.CoordinatorLayout; | ||||
| import android.support.design.widget.Snackbar; | ||||
| import android.support.v4.view.ViewCompat; | ||||
| import android.support.v4.view.ViewPropertyAnimatorCompat; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.View; | ||||
|  | ||||
| import com.github.clans.fab.FloatingActionMenu; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * Created by Matteo on 08/08/2015. | ||||
|  * | ||||
|  * Floating Action Menu Behavior for Clans.FloatingActionButton | ||||
|  * https://github.com/Clans/FloatingActionButton/ | ||||
|  * | ||||
|  * Use this behavior as your app:layout_behavior attribute in your Floating Action Menu to use the | ||||
|  * FabMenu in a Coordinator Layout. | ||||
|  * | ||||
|  * Remember to use the correct namespace for the fab: | ||||
|  * xmlns:fab="http://schemas.android.com/apk/res-auto" | ||||
|  */ | ||||
| public class FABBehavior extends CoordinatorLayout.Behavior { | ||||
|     private float mTranslationY; | ||||
|  | ||||
|     public FABBehavior(Context context, AttributeSet attrs) { | ||||
|         super(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { | ||||
|         return dependency instanceof Snackbar.SnackbarLayout; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { | ||||
|         if (dependency instanceof Snackbar.SnackbarLayout) { | ||||
|             updateTranslation(parent, child); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) { | ||||
|         if (dependency instanceof Snackbar.SnackbarLayout) { | ||||
|             revertTranslation(child); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateTranslation(CoordinatorLayout parent, View child) { | ||||
|         float translationY = getTranslationY(parent, child); | ||||
|         if (translationY != mTranslationY) { | ||||
|             ViewPropertyAnimatorCompat anim = ViewCompat.animate(child); | ||||
|             anim.cancel(); | ||||
|             anim.translationY(translationY).setDuration(100); | ||||
|             mTranslationY = translationY; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void revertTranslation(View child) { | ||||
|         if (mTranslationY != 0) { | ||||
|             ViewPropertyAnimatorCompat anim = ViewCompat.animate(child); | ||||
|             anim.cancel(); | ||||
|             anim.translationY(0).setDuration(100); | ||||
|             mTranslationY = 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private float getTranslationY(CoordinatorLayout parent, View child) { | ||||
|         float minOffset = 0.0F; | ||||
|         List dependencies = parent.getDependencies(child); | ||||
|         int i = 0; | ||||
|  | ||||
|         for (int z = dependencies.size(); i < z; ++i) { | ||||
|             View view = (View) dependencies.get(i); | ||||
|             if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) { | ||||
|                 minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return minOffset; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * onStartNestedScroll and onNestedScroll will hide/show the FabMenu when a scroll is detected. | ||||
|      */ | ||||
|     @Override | ||||
|     public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, | ||||
|                                        View directTargetChild, View target, int nestedScrollAxes) { | ||||
|         return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || | ||||
|                 super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, | ||||
|                         nestedScrollAxes); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, | ||||
|                                int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { | ||||
|         super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, | ||||
|                 dyUnconsumed); | ||||
|         FloatingActionMenu fabMenu = (FloatingActionMenu) child; | ||||
|         if (dyConsumed > 0 && !fabMenu.isMenuButtonHidden()) { | ||||
|             fabMenu.hideMenuButton(true); | ||||
|         } else if (dyConsumed < 0 && fabMenu.isMenuButtonHidden()) { | ||||
|             fabMenu.showMenuButton(true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,112 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.Fragment; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.widget.CardView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.Button; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.Spinner; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.receivers.MagiskDlReceiver; | ||||
| import com.topjohnwu.magisk.utils.CallbackHandler; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class InstallFragment extends Fragment implements CallbackHandler.EventListener { | ||||
|  | ||||
|     public static final CallbackHandler.Event blockDetectionDone = new CallbackHandler.Event(); | ||||
|  | ||||
|     public static List<String> blockList; | ||||
|     public static String bootBlock = null; | ||||
|  | ||||
|     @BindView(R.id.current_version_title) TextView currentVersionTitle; | ||||
|     @BindView(R.id.install_title) TextView installTitle; | ||||
|     @BindView(R.id.block_spinner) Spinner spinner; | ||||
|     @BindView(R.id.detect_bootimage) Button detectButton; | ||||
|     @BindView(R.id.flash_button) CardView flashButton; | ||||
|     @BindView(R.id.keep_force_enc) CheckBox keepEncChkbox; | ||||
|     @BindView(R.id.keep_verity) CheckBox keepVerityChkbox; | ||||
|  | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | ||||
|         View v = inflater.inflate(R.layout.install_fragment, container, false); | ||||
|         ButterKnife.bind(this, v); | ||||
|         detectButton.setOnClickListener(v1 -> toAutoDetect()); | ||||
|         currentVersionTitle.setText(getString(R.string.current_magisk_title, StatusFragment.magiskVersionString)); | ||||
|         installTitle.setText(getString(R.string.install_magisk_title, StatusFragment.remoteMagiskVersion)); | ||||
|         flashButton.setOnClickListener(v1 -> { | ||||
|             String bootImage = bootBlock; | ||||
|             if (bootImage == null) { | ||||
|                 bootImage = blockList.get(spinner.getSelectedItemPosition() - 1); | ||||
|             } | ||||
|             String filename = "Magisk-v" + StatusFragment.remoteMagiskVersion + ".zip"; | ||||
|             String finalBootImage = bootImage; | ||||
|             Utils.getAlertDialogBuilder(getActivity()) | ||||
|                     .setTitle(getString(R.string.repo_install_title, getString(R.string.magisk))) | ||||
|                     .setMessage(getString(R.string.repo_install_msg, filename)) | ||||
|                     .setCancelable(true) | ||||
|                     .setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive( | ||||
|                             getActivity(), | ||||
|                             new MagiskDlReceiver(finalBootImage, keepEncChkbox.isChecked(), keepVerityChkbox.isChecked()), | ||||
|                             StatusFragment.magiskLink, | ||||
|                             Utils.getLegalFilename(filename))) | ||||
|                     .setNeutralButton(R.string.check_release_notes, (dialog, which) -> { | ||||
|                         getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(StatusFragment.releaseNoteLink))); | ||||
|                     }) | ||||
|                     .setNegativeButton(R.string.no_thanks, null) | ||||
|                     .show(); | ||||
|         }); | ||||
|         if (blockDetectionDone.isTriggered) { | ||||
|             updateUI(); | ||||
|         } | ||||
|         return v; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onTrigger(CallbackHandler.Event event) { | ||||
|         updateUI(); | ||||
|     } | ||||
|  | ||||
|     private void updateUI() { | ||||
|         List<String> items = new ArrayList<>(blockList); | ||||
|         items.add(0, getString(R.string.auto_detect, bootBlock)); | ||||
|         ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), | ||||
|                 android.R.layout.simple_spinner_item, items); | ||||
|         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); | ||||
|         spinner.setAdapter(adapter); | ||||
|         toAutoDetect(); | ||||
|     } | ||||
|  | ||||
|     private void toAutoDetect() { | ||||
|         if (bootBlock != null) { | ||||
|             spinner.setSelection(0); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         getActivity().setTitle(R.string.install); | ||||
|         CallbackHandler.register(blockDetectionDone, this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         CallbackHandler.unRegister(blockDetectionDone, this); | ||||
|     } | ||||
| } | ||||
| @@ -1,236 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.Manifest; | ||||
| import android.annotation.SuppressLint; | ||||
| import android.app.Fragment; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Environment; | ||||
| import android.os.Handler; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.design.widget.Snackbar; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.HorizontalScrollView; | ||||
| import android.widget.ProgressBar; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileWriter; | ||||
| import java.io.IOException; | ||||
| import java.util.Calendar; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class LogFragment extends Fragment { | ||||
|  | ||||
|     private static final String MAGISK_LOG = "/cache/magisk.log"; | ||||
|  | ||||
|     @BindView(R.id.txtLog) TextView txtLog; | ||||
|     @BindView(R.id.svLog) ScrollView svLog; | ||||
|     @BindView(R.id.hsvLog) HorizontalScrollView hsvLog; | ||||
|  | ||||
|     @BindView(R.id.progressBar) ProgressBar progressBar; | ||||
|  | ||||
|     private MenuItem mClickedMenuItem; | ||||
|  | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | ||||
|         View view = inflater.inflate(R.layout.log_fragment, container, false); | ||||
|         ButterKnife.bind(this, view); | ||||
|  | ||||
|         txtLog.setTextIsSelectable(true); | ||||
|  | ||||
|         new LogManager().read(); | ||||
|  | ||||
|         return view; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         setHasOptionsMenu(true); | ||||
|         new LogManager().read(); | ||||
|         getActivity().setTitle(R.string.log); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         inflater.inflate(R.menu.menu_log, menu); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         mClickedMenuItem = item; | ||||
|         switch (item.getItemId()) { | ||||
|             case R.id.menu_refresh: | ||||
|                 new LogManager().read(); | ||||
|                 return true; | ||||
|             case R.id.menu_send: | ||||
|                 new LogManager().send(); | ||||
|                 return true; | ||||
|             case R.id.menu_save: | ||||
|                 new LogManager().save(); | ||||
|                 return true; | ||||
|             case R.id.menu_clear: | ||||
|                 new LogManager().clear(); | ||||
|                 return true; | ||||
|             default: | ||||
|                 return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
|         if (requestCode == 0) { | ||||
|             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||
|                 if (mClickedMenuItem != null) { | ||||
|                     new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500); | ||||
|                 } | ||||
|             } else { | ||||
|                 Snackbar.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class LogManager extends Async.RootTask<Object, Void, Object> { | ||||
|  | ||||
|         int mode; | ||||
|         File targetFile; | ||||
|  | ||||
|         @SuppressLint("DefaultLocale") | ||||
|         @Override | ||||
|         protected Object doInBackground(Object... params) { | ||||
|             mode = (int) params[0]; | ||||
|             switch (mode) { | ||||
|                 case 0: | ||||
|                     List<String> logList = Utils.readFile(MAGISK_LOG); | ||||
|  | ||||
|                     StringBuilder llog = new StringBuilder(15 * 10 * 1024); | ||||
|                     for (String s : logList) { | ||||
|                         llog.append(s).append("\n"); | ||||
|                     } | ||||
|  | ||||
|                     return llog.toString(); | ||||
|  | ||||
|                 case 1: | ||||
|                     Shell.su("echo > " + MAGISK_LOG); | ||||
|                     Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show(); | ||||
|                     return ""; | ||||
|  | ||||
|                 case 2: | ||||
|                 case 3: | ||||
|                     if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { | ||||
|                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|                             requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); | ||||
|                         } | ||||
|                         return false; | ||||
|                     } | ||||
|  | ||||
|                     if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) | ||||
|                         return false; | ||||
|  | ||||
|                     Calendar now = Calendar.getInstance(); | ||||
|                     String filename = String.format( | ||||
|                             "magisk_%s_%04d%02d%02d_%02d%02d%02d.log", "error", | ||||
|                             now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1, | ||||
|                             now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), | ||||
|                             now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); | ||||
|  | ||||
|                     targetFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/MagiskManager/" + filename); | ||||
|  | ||||
|                     if ((!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) | ||||
|                             || (targetFile.exists() && !targetFile.delete())) | ||||
|                         return false; | ||||
|  | ||||
|                     List<String> in = Utils.readFile(MAGISK_LOG); | ||||
|  | ||||
|                     try { | ||||
|                         FileWriter out = new FileWriter(targetFile); | ||||
|                         for (String line : in) { | ||||
|                             out.write(line + "\n"); | ||||
|                         } | ||||
|                         out.close(); | ||||
|  | ||||
|  | ||||
|                         return true; | ||||
|                     } catch (IOException e) { | ||||
|                         e.printStackTrace(); | ||||
|                         return false; | ||||
|                     } | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         protected void onPostExecute(Object o) { | ||||
|             boolean bool; | ||||
|             String llog; | ||||
|             switch (mode) { | ||||
|                 case 0: | ||||
|                 case 1: | ||||
|                     llog = (String) o; | ||||
|                     progressBar.setVisibility(View.GONE); | ||||
|                     if (llog.length() == 0) | ||||
|                         txtLog.setText(R.string.log_is_empty); | ||||
|                     else | ||||
|                         txtLog.setText(llog); | ||||
|                     svLog.post(() -> svLog.scrollTo(0, txtLog.getHeight())); | ||||
|                     hsvLog.post(() -> hsvLog.scrollTo(0, 0)); | ||||
|                     break; | ||||
|                 case 2: | ||||
|                     bool = (boolean) o; | ||||
|                     if (bool) | ||||
|                         Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show(); | ||||
|                     else | ||||
|                         Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show(); | ||||
|                     break; | ||||
|                 case 3: | ||||
|                     bool = (boolean) o; | ||||
|                     if (bool) { | ||||
|                         Intent sendIntent = new Intent(); | ||||
|                         sendIntent.setAction(Intent.ACTION_SEND); | ||||
|                         sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(targetFile)); | ||||
|                         sendIntent.setType("application/html"); | ||||
|                         startActivity(Intent.createChooser(sendIntent, getResources().getString(R.string.menuSend))); | ||||
|                     } else { | ||||
|                         Toast.makeText(getActivity(), getString(R.string.logs_save_failed), Toast.LENGTH_LONG).show(); | ||||
|                     } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public void read() { | ||||
|             exec(0); | ||||
|         } | ||||
|  | ||||
|         public void clear() { | ||||
|             exec(1); | ||||
|         } | ||||
|  | ||||
|         public void save() { | ||||
|             exec(2); | ||||
|         } | ||||
|  | ||||
|         public void send() { | ||||
|             exec(3); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,211 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.Manifest; | ||||
| import android.app.Fragment; | ||||
| import android.app.FragmentTransaction; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.Handler; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.IdRes; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.design.widget.NavigationView; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.view.GravityCompat; | ||||
| import android.support.v4.widget.DrawerLayout; | ||||
| import android.support.v7.app.ActionBarDrawerToggle; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.CallbackHandler; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class MainActivity extends AppCompatActivity | ||||
|         implements NavigationView.OnNavigationItemSelectedListener, CallbackHandler.EventListener { | ||||
|  | ||||
|     private static final String SELECTED_ITEM_ID = "SELECTED_ITEM_ID"; | ||||
|  | ||||
|     public static CallbackHandler.Event recreate = new CallbackHandler.Event(); | ||||
|  | ||||
|     private final Handler mDrawerHandler = new Handler(); | ||||
|     private SharedPreferences prefs; | ||||
|  | ||||
|     @BindView(R.id.toolbar) Toolbar toolbar; | ||||
|     @BindView(R.id.drawer_layout) DrawerLayout drawer; | ||||
|     @BindView(R.id.nav_view) public NavigationView navigationView; | ||||
|  | ||||
|     @IdRes | ||||
|     private int mSelectedId = R.id.status; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(final Bundle savedInstanceState) { | ||||
|  | ||||
|         prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); | ||||
|  | ||||
|         if (Utils.isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_dh); | ||||
|         } | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|         ButterKnife.bind(this); | ||||
|  | ||||
|         if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED | ||||
|                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); | ||||
|         } | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|  | ||||
|         ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) { | ||||
|             @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 | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         drawer.addDrawerListener(toggle); | ||||
|         toggle.syncState(); | ||||
|  | ||||
|         //noinspection ResourceType | ||||
|         mSelectedId = savedInstanceState == null ? mSelectedId : savedInstanceState.getInt(SELECTED_ITEM_ID); | ||||
|         navigationView.setCheckedItem(mSelectedId); | ||||
|  | ||||
|         if (savedInstanceState == null) { | ||||
|             mDrawerHandler.removeCallbacksAndMessages(null); | ||||
|             mDrawerHandler.postDelayed(() -> navigate(mSelectedId), 250); | ||||
|         } | ||||
|  | ||||
|         navigationView.setNavigationItemSelectedListener(this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         CallbackHandler.register(StatusFragment.updateCheckDone, this); | ||||
|         CallbackHandler.register(recreate, this); | ||||
|         if (StatusFragment.updateCheckDone.isTriggered) { | ||||
|             onTrigger(StatusFragment.updateCheckDone); | ||||
|         } | ||||
|         checkHideSection(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onPause() { | ||||
|         super.onPause(); | ||||
|         CallbackHandler.unRegister(StatusFragment.updateCheckDone, this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         CallbackHandler.unRegister(recreate, this); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void onSaveInstanceState(Bundle outState) { | ||||
|         super.onSaveInstanceState(outState); | ||||
|         outState.putInt(SELECTED_ITEM_ID, mSelectedId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBackPressed() { | ||||
|         if (drawer.isDrawerOpen(GravityCompat.START)) { | ||||
|             drawer.closeDrawer(GravityCompat.START); | ||||
|         } else { | ||||
|             finish(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) { | ||||
|         mSelectedId = menuItem.getItemId(); | ||||
|         mDrawerHandler.removeCallbacksAndMessages(null); | ||||
|         mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250); | ||||
|         drawer.closeDrawer(GravityCompat.START); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onTrigger(CallbackHandler.Event event) { | ||||
|         if (event == StatusFragment.updateCheckDone) { | ||||
|             Menu menu = navigationView.getMenu(); | ||||
|             menu.findItem(R.id.install).setVisible(StatusFragment.remoteMagiskVersion > 0 && | ||||
|                     Shell.rootAccess()); | ||||
|         } else if (event == recreate) { | ||||
|             recreate(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void checkHideSection() { | ||||
|         Menu menu = navigationView.getMenu(); | ||||
|         menu.findItem(R.id.magiskhide).setVisible(StatusFragment.magiskVersion >= 8 && | ||||
|                 prefs.getBoolean("magiskhide", false) && Shell.rootAccess()); | ||||
|         menu.findItem(R.id.modules).setVisible(StatusFragment.magiskVersion >= 4 && | ||||
|                 Shell.rootAccess()); | ||||
|         menu.findItem(R.id.downloads).setVisible(StatusFragment.magiskVersion >= 4 && | ||||
|                 Shell.rootAccess()); | ||||
|         menu.findItem(R.id.log).setVisible(Shell.rootAccess()); | ||||
|         menu.findItem(R.id.install).setVisible(Shell.rootAccess()); | ||||
|     } | ||||
|  | ||||
|     public void navigate(final int itemId) { | ||||
|         Fragment navFragment = null; | ||||
|         String tag = ""; | ||||
|         switch (itemId) { | ||||
|             case R.id.status: | ||||
|                 tag = "status"; | ||||
|                 navFragment = new StatusFragment(); | ||||
|                 break; | ||||
|             case R.id.install: | ||||
|                 tag = "install"; | ||||
|                 navFragment = new InstallFragment(); | ||||
|                 break; | ||||
|             case R.id.modules: | ||||
|                 tag = "modules"; | ||||
|                 navFragment = new ModulesFragment(); | ||||
|                 break; | ||||
|             case R.id.downloads: | ||||
|                 tag = "downloads"; | ||||
|                 navFragment = new ReposFragment(); | ||||
|                 break; | ||||
|             case R.id.magiskhide: | ||||
|                 tag = "magiskhide"; | ||||
|                 navFragment = new MagiskHideFragment(); | ||||
|                 break; | ||||
|             case R.id.log: | ||||
|                 tag = "log"; | ||||
|                 navFragment = new LogFragment(); | ||||
|                 break; | ||||
|             case R.id.settings: | ||||
|                 startActivity(new Intent(this, SettingsActivity.class)); | ||||
|                 break; | ||||
|             case R.id.app_about: | ||||
|                 startActivity(new Intent(this, AboutActivity.class)); | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
|         if (navFragment != null) { | ||||
|             FragmentTransaction transaction = getFragmentManager().beginTransaction(); | ||||
|             transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out); | ||||
|             try { | ||||
|                 transaction.replace(R.id.content_frame, navFragment, tag).commit(); | ||||
|             } catch (IllegalStateException ignored) {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,120 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.app.Fragment; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v4.widget.SwipeRefreshLayout; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.github.clans.fab.FloatingActionButton; | ||||
| import com.topjohnwu.magisk.adapters.ModulesAdapter; | ||||
| import com.topjohnwu.magisk.module.Module; | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.CallbackHandler; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.ModuleHelper; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class ModulesFragment extends Fragment implements CallbackHandler.EventListener { | ||||
|  | ||||
|     public static final CallbackHandler.Event moduleLoadDone = new CallbackHandler.Event(); | ||||
|  | ||||
|     private static final int FETCH_ZIP_CODE = 2; | ||||
|  | ||||
|     @BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout; | ||||
|     @BindView(R.id.recyclerView) RecyclerView recyclerView; | ||||
|     @BindView(R.id.empty_rv) TextView emptyTv; | ||||
|     @BindView(R.id.fab) FloatingActionButton fabio; | ||||
|  | ||||
|     private List<Module> listModules = new ArrayList<>(); | ||||
|  | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         View view = inflater.inflate(R.layout.modules_fragment, container, false); | ||||
|         ButterKnife.bind(this, view); | ||||
|  | ||||
|         fabio.setOnClickListener(v -> { | ||||
|             Intent intent = new Intent(Intent.ACTION_GET_CONTENT); | ||||
|             intent.setType("application/zip"); | ||||
|             startActivityForResult(intent, FETCH_ZIP_CODE); | ||||
|         }); | ||||
|  | ||||
|         mSwipeRefreshLayout.setOnRefreshListener(() -> { | ||||
|             recyclerView.setVisibility(View.GONE); | ||||
|             new Async.LoadModules().exec(); | ||||
|         }); | ||||
|  | ||||
|         recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { | ||||
|             @Override | ||||
|             public void onScrolled(RecyclerView recyclerView, int dx, int dy) { | ||||
|                 mSwipeRefreshLayout.setEnabled(recyclerView.getChildAt(0).getTop() >= 0); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onScrollStateChanged(RecyclerView recyclerView, int newState) { | ||||
|                 super.onScrollStateChanged(recyclerView, newState); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         if (moduleLoadDone.isTriggered) { | ||||
|             updateUI(); | ||||
|         } | ||||
|  | ||||
|         return view; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onTrigger(CallbackHandler.Event event) { | ||||
|         Logger.dev("ModulesFragment: UI refresh triggered"); | ||||
|         updateUI(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||
|         if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) { | ||||
|             // Get the URI of the selected file | ||||
|             final Uri uri = data.getData(); | ||||
|             new Async.FlashZIP(getActivity(), uri).exec(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         CallbackHandler.register(moduleLoadDone, this); | ||||
|         getActivity().setTitle(R.string.modules); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         CallbackHandler.unRegister(moduleLoadDone, this); | ||||
|     } | ||||
|  | ||||
|     private void updateUI() { | ||||
|         ModuleHelper.getModuleList(listModules); | ||||
|         if (listModules.size() == 0) { | ||||
|             emptyTv.setVisibility(View.VISIBLE); | ||||
|             recyclerView.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             emptyTv.setVisibility(View.GONE); | ||||
|             recyclerView.setVisibility(View.VISIBLE); | ||||
|             recyclerView.setAdapter(new ModulesAdapter(listModules)); | ||||
|         } | ||||
|         mSwipeRefreshLayout.setRefreshing(false); | ||||
|     } | ||||
| } | ||||
| @@ -1,193 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.app.Fragment; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v4.view.MenuItemCompat; | ||||
| import android.support.v4.widget.SwipeRefreshLayout; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.SearchView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.adapters.ReposAdapter; | ||||
| import com.topjohnwu.magisk.adapters.SimpleSectionedRecyclerViewAdapter; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.CallbackHandler; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.ModuleHelper; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class ReposFragment extends Fragment implements CallbackHandler.EventListener { | ||||
|  | ||||
|     public static final CallbackHandler.Event repoLoadDone = new CallbackHandler.Event(); | ||||
|  | ||||
|     @BindView(R.id.recyclerView) RecyclerView recyclerView; | ||||
|     @BindView(R.id.empty_rv) TextView emptyTv; | ||||
|     @BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout; | ||||
|  | ||||
|     private List<Repo> mUpdateRepos = new ArrayList<>(); | ||||
|     private List<Repo> mInstalledRepos = new ArrayList<>(); | ||||
|     private List<Repo> mOthersRepos = new ArrayList<>(); | ||||
|     private List<Repo> fUpdateRepos = new ArrayList<>(); | ||||
|     private List<Repo> fInstalledRepos = new ArrayList<>(); | ||||
|     private List<Repo> fOthersRepos = new ArrayList<>(); | ||||
|  | ||||
|     private SimpleSectionedRecyclerViewAdapter mSectionedAdapter; | ||||
|  | ||||
|     private SearchView.OnQueryTextListener searchListener; | ||||
|  | ||||
|     @Nullable | ||||
|  | ||||
|     @Override | ||||
|     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         View view = inflater.inflate(R.layout.repos_fragment, container, false); | ||||
|  | ||||
|         ButterKnife.bind(this, view); | ||||
|  | ||||
|         mSectionedAdapter = new | ||||
|                 SimpleSectionedRecyclerViewAdapter(getActivity(), R.layout.section, | ||||
|                 R.id.section_text, new ReposAdapter(fUpdateRepos, fInstalledRepos, fOthersRepos)); | ||||
|  | ||||
|         recyclerView.setAdapter(mSectionedAdapter); | ||||
|  | ||||
|         mSwipeRefreshLayout.setRefreshing(true); | ||||
|  | ||||
|         mSwipeRefreshLayout.setOnRefreshListener(() -> { | ||||
|             recyclerView.setVisibility(View.GONE); | ||||
|             new Async.LoadRepos(getActivity()).exec(); | ||||
|         }); | ||||
|  | ||||
|         if (repoLoadDone.isTriggered) { | ||||
|             reloadRepos(); | ||||
|             updateUI(); | ||||
|         } | ||||
|  | ||||
|         searchListener = new SearchView.OnQueryTextListener() { | ||||
|             @Override | ||||
|             public boolean onQueryTextSubmit(String query) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public boolean onQueryTextChange(String newText) { | ||||
|                 new FilterApps().exec(newText); | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return view; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onTrigger(CallbackHandler.Event event) { | ||||
|         Logger.dev("ReposFragment: UI refresh triggered"); | ||||
|         reloadRepos(); | ||||
|         updateUI(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | ||||
|         inflater.inflate(R.menu.menu_repo, menu); | ||||
|         SearchView search = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.repo_search)); | ||||
|         search.setOnQueryTextListener(searchListener); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         setHasOptionsMenu(true); | ||||
|         CallbackHandler.register(repoLoadDone, this); | ||||
|         getActivity().setTitle(R.string.downloads); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         CallbackHandler.unRegister(repoLoadDone, this); | ||||
|     } | ||||
|  | ||||
|     private void reloadRepos() { | ||||
|         ModuleHelper.getRepoLists(mUpdateRepos, mInstalledRepos, mOthersRepos); | ||||
|         fUpdateRepos.clear(); | ||||
|         fInstalledRepos.clear(); | ||||
|         fOthersRepos.clear(); | ||||
|         fUpdateRepos.addAll(mUpdateRepos); | ||||
|         fInstalledRepos.addAll(mInstalledRepos); | ||||
|         fOthersRepos.addAll(mOthersRepos); | ||||
|     } | ||||
|  | ||||
|     private void updateUI() { | ||||
|         if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) { | ||||
|             emptyTv.setVisibility(View.VISIBLE); | ||||
|             recyclerView.setVisibility(View.GONE); | ||||
|         } else { | ||||
|             List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>(); | ||||
|             if (!fUpdateRepos.isEmpty()) { | ||||
|                 sections.add(new SimpleSectionedRecyclerViewAdapter.Section(0, getString(R.string.update_available))); | ||||
|             } | ||||
|             if (!fInstalledRepos.isEmpty()) { | ||||
|                 sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size(), getString(R.string.installed))); | ||||
|             } | ||||
|             if (!fOthersRepos.isEmpty()) { | ||||
|                 sections.add(new SimpleSectionedRecyclerViewAdapter.Section(fUpdateRepos.size() + fInstalledRepos.size(), getString(R.string.not_installed))); | ||||
|             } | ||||
|             SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]); | ||||
|             mSectionedAdapter.setSections(array); | ||||
|             emptyTv.setVisibility(View.GONE); | ||||
|             recyclerView.setVisibility(View.VISIBLE); | ||||
|         } | ||||
|         mSwipeRefreshLayout.setRefreshing(false); | ||||
|     } | ||||
|  | ||||
|     private class FilterApps extends Async.NormalTask<String, Void, Void> { | ||||
|         @Override | ||||
|         protected Void doInBackground(String... strings) { | ||||
|             String newText = strings[0]; | ||||
|             fUpdateRepos.clear(); | ||||
|             fInstalledRepos.clear(); | ||||
|             fOthersRepos.clear(); | ||||
|             for (Repo repo: mUpdateRepos) { | ||||
|                 if (repo.getName().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getAuthor().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getDescription().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         ) { | ||||
|                     fUpdateRepos.add(repo); | ||||
|                 } | ||||
|             } | ||||
|             for (Repo repo: mInstalledRepos) { | ||||
|                 if (repo.getName().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getAuthor().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getDescription().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         ) { | ||||
|                     fInstalledRepos.add(repo); | ||||
|                 } | ||||
|             } | ||||
|             for (Repo repo: mOthersRepos) { | ||||
|                 if (repo.getName().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getAuthor().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         || repo.getDescription().toLowerCase().contains(newText.toLowerCase()) | ||||
|                         ) { | ||||
|                     fOthersRepos.add(repo); | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         protected void onPostExecute(Void v) { | ||||
|             updateUI(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,213 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.CheckBoxPreference; | ||||
| import android.preference.ListPreference; | ||||
| import android.preference.Preference; | ||||
| import android.preference.PreferenceFragment; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.Toast; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.ModuleHelper; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class SettingsActivity extends AppCompatActivity { | ||||
|  | ||||
|     @BindView(R.id.toolbar) Toolbar toolbar; | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         String theme = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("theme", ""); | ||||
|         Logger.dev("AboutActivity: Theme is " + theme); | ||||
|         if (Utils.isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_dh); | ||||
|         } | ||||
|  | ||||
|         setContentView(R.layout.activity_container); | ||||
|         ButterKnife.bind(this); | ||||
|  | ||||
|         setSupportActionBar(toolbar); | ||||
|  | ||||
|         toolbar.setNavigationOnClickListener(view -> finish()); | ||||
|  | ||||
|         ActionBar ab = getSupportActionBar(); | ||||
|         if (ab != null) { | ||||
|             ab.setTitle(R.string.settings); | ||||
|             ab.setDisplayHomeAsUpEnabled(true); | ||||
|         } | ||||
|  | ||||
|         setFloating(); | ||||
|  | ||||
|         if (savedInstanceState == null) { | ||||
|             getFragmentManager().beginTransaction().add(R.id.container, new SettingsFragment()).commit(); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void setFloating() { | ||||
|         boolean isTablet = getResources().getBoolean(R.bool.isTablet); | ||||
|         if (isTablet) { | ||||
|             WindowManager.LayoutParams params = getWindow().getAttributes(); | ||||
|             params.height = getResources().getDimensionPixelSize(R.dimen.floating_height); | ||||
|             params.width = getResources().getDimensionPixelSize(R.dimen.floating_width); | ||||
|             params.alpha = 1.0f; | ||||
|             params.dimAmount = 0.6f; | ||||
|             params.flags |= 2; | ||||
|             getWindow().setAttributes(params); | ||||
|             setFinishOnTouchOutside(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static class SettingsFragment extends PreferenceFragment | ||||
|             implements SharedPreferences.OnSharedPreferenceChangeListener { | ||||
|  | ||||
|         private ListPreference themePreference; | ||||
|         private SharedPreferences prefs; | ||||
|  | ||||
|         @Override | ||||
|         public void onCreate(Bundle savedInstanceState) { | ||||
|             super.onCreate(savedInstanceState); | ||||
|             addPreferencesFromResource(R.xml.app_settings); | ||||
|             PreferenceManager.setDefaultValues(getActivity(), R.xml.app_settings, false); | ||||
|             prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|  | ||||
|             themePreference = (ListPreference) findPreference("theme"); | ||||
|             CheckBoxPreference busyboxPreference = (CheckBoxPreference) findPreference("busybox"); | ||||
|             CheckBoxPreference magiskhidePreference = (CheckBoxPreference) findPreference("magiskhide"); | ||||
|             CheckBoxPreference hostsPreference = (CheckBoxPreference) findPreference("hosts"); | ||||
|             Preference clear = findPreference("clear"); | ||||
|  | ||||
|             clear.setOnPreferenceClickListener((pref) -> { | ||||
|                 SharedPreferences repoMap = getActivity().getSharedPreferences(ModuleHelper.FILE_KEY, Context.MODE_PRIVATE); | ||||
|                 repoMap.edit() | ||||
|                         .putString(ModuleHelper.ETAG_KEY, "") | ||||
|                         .putInt(ModuleHelper.VERSION_KEY, 0) | ||||
|                         .apply(); | ||||
|                 new Async.LoadRepos(getActivity()).exec(); | ||||
|                 Toast.makeText(getActivity(), R.string.repo_cache_cleared, Toast.LENGTH_LONG).show(); | ||||
|                 return true; | ||||
|             }); | ||||
|  | ||||
|             if (Utils.isDarkTheme) { | ||||
|                 themePreference.setSummary(R.string.theme_dark); | ||||
|             } else { | ||||
|                 themePreference.setSummary(R.string.theme_default); | ||||
|             } | ||||
|  | ||||
|             if (StatusFragment.magiskVersion < 9) { | ||||
|                 hostsPreference.setEnabled(false); | ||||
|                 busyboxPreference.setEnabled(false); | ||||
|             } else if (StatusFragment.magiskVersion < 8) { | ||||
|                 magiskhidePreference.setEnabled(false); | ||||
|             } else if (! Shell.rootAccess()) { | ||||
|                 busyboxPreference.setEnabled(false); | ||||
|                 magiskhidePreference.setEnabled(false); | ||||
|                 hostsPreference.setEnabled(false); | ||||
|             } else { | ||||
|                 busyboxPreference.setEnabled(true); | ||||
|                 magiskhidePreference.setEnabled(true); | ||||
|                 hostsPreference.setEnabled(true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onResume() { | ||||
|             super.onResume(); | ||||
|             prefs.registerOnSharedPreferenceChangeListener(this); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onDestroy() { | ||||
|             super.onDestroy(); | ||||
|             prefs.unregisterOnSharedPreferenceChangeListener(this); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { | ||||
|             Logger.dev("Settings: Prefs change " + key); | ||||
|             boolean checked; | ||||
|  | ||||
|             switch (key) { | ||||
|                 case "theme": | ||||
|                     String theme = prefs.getString("theme", getString(R.string.theme_default_value)); | ||||
|                     if (Utils.isDarkTheme != theme.equalsIgnoreCase(getString(R.string.theme_dark_value))) { | ||||
|                         Utils.isDarkTheme = !Utils.isDarkTheme; | ||||
|                         getActivity().recreate(); | ||||
|                         MainActivity.recreate.trigger(); | ||||
|                     } | ||||
|                     break; | ||||
|                 case "magiskhide": | ||||
|                     checked = prefs.getBoolean("magiskhide", false); | ||||
|                     new Async.RootTask<Void, Void, Void>() { | ||||
|                         private boolean enable = checked; | ||||
|                         @Override | ||||
|                         protected Void doInBackground(Void... params) { | ||||
|                             if (enable) { | ||||
|                                 Utils.createFile("/magisk/.core/magiskhide/enable"); | ||||
|                             } else { | ||||
|                                 Utils.removeItem("/magisk/.core/magiskhide/enable"); | ||||
|                             } | ||||
|  | ||||
|                             return null; | ||||
|                         } | ||||
|                     }.exec(); | ||||
|                     Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show(); | ||||
|                     break; | ||||
|                 case "busybox": | ||||
|                     checked = prefs.getBoolean("busybox", false); | ||||
|                     new Async.RootTask<Void, Void, Void>() { | ||||
|                         private boolean enable = checked; | ||||
|                         @Override | ||||
|                         protected Void doInBackground(Void... params) { | ||||
|                             if (enable) { | ||||
|                                 Utils.createFile("/magisk/.core/busybox/enable"); | ||||
|                             } else { | ||||
|                                 Utils.removeItem("/magisk/.core/busybox/enable"); | ||||
|                             } | ||||
|                             return null; | ||||
|                         } | ||||
|                     }.exec(); | ||||
|                     Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show(); | ||||
|                     break; | ||||
|                 case "hosts": | ||||
|                     checked = prefs.getBoolean("hosts", false); | ||||
|                     new Async.RootTask<Void, Void, Void>() { | ||||
|                         private boolean enable = checked; | ||||
|                         @Override | ||||
|                         protected Void doInBackground(Void... voids) { | ||||
|                             if (enable) { | ||||
|                                 Shell.su("cp -af /system/etc/hosts /magisk/.core/hosts", | ||||
|                                         "mount -o bind /magisk/.core/hosts /system/etc/hosts"); | ||||
|                             } else { | ||||
|                                 Shell.su("umount -l /system/etc/hosts", | ||||
|                                         "rm -f /magisk/.core/hosts"); | ||||
|                             } | ||||
|                             return null; | ||||
|                         } | ||||
|                     }.exec(); | ||||
|                     break; | ||||
|                 case "developer_logging": | ||||
|                     Logger.devLog = prefs.getBoolean("developer_logging", false); | ||||
|                     break; | ||||
|                 case "shell_logging": | ||||
|                     Logger.logShell = prefs.getBoolean("shell_logging", false); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,55 +0,0 @@ | ||||
| package com.topjohnwu.magisk; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| public class SplashActivity extends AppCompatActivity { | ||||
|  | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|  | ||||
|         super.onCreate(savedInstanceState); | ||||
|         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplication()); | ||||
|  | ||||
|         String theme = prefs.getString("theme", getString(R.string.theme_default_value)); | ||||
|         Utils.isDarkTheme = theme.equalsIgnoreCase(getString(R.string.theme_dark_value)); | ||||
|  | ||||
|         if (Utils.isDarkTheme) { | ||||
|             setTheme(R.style.AppTheme_dh); | ||||
|         } | ||||
|  | ||||
|         Logger.devLog = prefs.getBoolean("developer_logging", false); | ||||
|         Logger.logShell = prefs.getBoolean("shell_logging", false); | ||||
|  | ||||
|         // Initialize prefs | ||||
|         prefs.edit() | ||||
|                 .putBoolean("magiskhide", Utils.itemExist(false, "/magisk/.core/magiskhide/enable")) | ||||
|                 .putBoolean("busybox", Utils.commandExists("busybox")) | ||||
|                 .putBoolean("hosts", Utils.itemExist(false, "/magisk/.core/hosts")) | ||||
|                 .apply(); | ||||
|  | ||||
|         // Start all async tasks | ||||
|         new Async.GetBootBlocks().exec(); | ||||
|         new Async.CheckUpdates().exec(); | ||||
|         new Async.LoadModules() { | ||||
|             @Override | ||||
|             protected void onPostExecute(Void v) { | ||||
|                 super.onPostExecute(v); | ||||
|                 new Async.LoadRepos(getApplicationContext()).exec(); | ||||
|             } | ||||
|         }.exec(); | ||||
|         new Async.LoadApps(getPackageManager()).exec(); | ||||
|  | ||||
|         // Start main activity | ||||
|         Intent intent = new Intent(getApplicationContext(), MainActivity.class); | ||||
|         startActivity(intent); | ||||
|         finish(); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,128 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.pm.ApplicationInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.CheckBox; | ||||
| import android.widget.Filter; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> { | ||||
|  | ||||
|     private List<ApplicationInfo> mOriginalList, mList; | ||||
|     private List<String> mHideList; | ||||
|     private PackageManager packageManager; | ||||
|     private ApplicationFilter filter; | ||||
|  | ||||
|     public ApplicationAdapter(PackageManager packageManager) { | ||||
|         mOriginalList = mList = Collections.emptyList(); | ||||
|         mHideList = Collections.emptyList(); | ||||
|         this.packageManager = packageManager; | ||||
|     } | ||||
|  | ||||
|     public void setLists(List<ApplicationInfo> listApps, List<String> hideList) { | ||||
|         mOriginalList = mList = Collections.unmodifiableList(listApps); | ||||
|         mHideList = new ArrayList<>(hideList); | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false); | ||||
|         ButterKnife.bind(this, mView); | ||||
|         return new ViewHolder(mView); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(final ViewHolder holder, int position) { | ||||
|         ApplicationInfo info = mList.get(position); | ||||
|  | ||||
|         holder.appIcon.setImageDrawable(info.loadIcon(packageManager)); | ||||
|         holder.appName.setText(info.loadLabel(packageManager)); | ||||
|         holder.appPackage.setText(info.packageName); | ||||
|  | ||||
|         holder.checkBox.setOnCheckedChangeListener(null); | ||||
|         holder.checkBox.setChecked(mHideList.contains(info.packageName)); | ||||
|         holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> { | ||||
|             if (isChecked) { | ||||
|                 new Async.MagiskHide().add(info.packageName); | ||||
|                 mHideList.add(info.packageName); | ||||
|             } else { | ||||
|                 new Async.MagiskHide().rm(info.packageName); | ||||
|                 mHideList.remove(info.packageName); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return mList.size(); | ||||
|     } | ||||
|  | ||||
|     public void filter(String constraint) { | ||||
|         if (filter == null) { | ||||
|             filter = new ApplicationFilter(); | ||||
|         } | ||||
|         filter.filter(constraint); | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.app_icon) ImageView appIcon; | ||||
|         @BindView(R.id.app_name) TextView appName; | ||||
|         @BindView(R.id.app_package) TextView appPackage; | ||||
|         @BindView(R.id.checkbox) CheckBox checkBox; | ||||
|  | ||||
|         ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             ButterKnife.bind(this, itemView); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private class ApplicationFilter extends Filter { | ||||
|  | ||||
|         @Override | ||||
|         protected FilterResults performFiltering(CharSequence constraint) { | ||||
|             List<ApplicationInfo> filteredApps; | ||||
|             if (constraint == null || constraint.length() == 0) { | ||||
|                 filteredApps = mOriginalList; | ||||
|             } else { | ||||
|                 filteredApps = new ArrayList<>(); | ||||
|                 String filter = constraint.toString().toLowerCase(); | ||||
|                 for (ApplicationInfo info : mOriginalList) { | ||||
|                     if (Utils.lowercaseContains(info.loadLabel(packageManager), filter) | ||||
|                             || Utils.lowercaseContains(info.packageName, filter)) { | ||||
|                         filteredApps.add(info); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             FilterResults results = new FilterResults(); | ||||
|             results.values = filteredApps; | ||||
|             results.count = filteredApps.size(); | ||||
|             return results; | ||||
|         } | ||||
|  | ||||
|         @SuppressWarnings("unchecked") | ||||
|         @Override | ||||
|         protected void publishResults(CharSequence constraint, FilterResults results) { | ||||
|             mList = (List<ApplicationInfo>) results.values; | ||||
|             notifyDataSetChanged(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,221 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.animation.Animator; | ||||
| import android.animation.ObjectAnimator; | ||||
| import android.animation.ValueAnimator; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.text.TextUtils; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.ViewTreeObserver; | ||||
| import android.widget.ImageView; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
| import com.topjohnwu.magisk.receivers.RepoDlReceiver; | ||||
| import com.topjohnwu.magisk.utils.Utils; | ||||
| import com.topjohnwu.magisk.utils.WebWindow; | ||||
|  | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
|  | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
|  | ||||
| public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.ViewHolder> { | ||||
|  | ||||
|     private List<Repo> mUpdateRepos, mInstalledRepos, mOthersRepos; | ||||
|     private HashSet<Repo> expandList = new HashSet<>(); | ||||
|  | ||||
|     public ReposAdapter(List<Repo> update, List<Repo> installed, List<Repo> others) { | ||||
|         mUpdateRepos = update; | ||||
|         mInstalledRepos = installed; | ||||
|         mOthersRepos = others; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||
|         View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false); | ||||
|         ButterKnife.bind(this, v); | ||||
|         return new ViewHolder(v); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(final ViewHolder holder, int position) { | ||||
|         Context context = holder.itemView.getContext(); | ||||
|         Repo repo = getItem(position); | ||||
|  | ||||
|         holder.title.setText(repo.getName()); | ||||
|         holder.versionName.setText(repo.getVersion()); | ||||
|         String author = repo.getAuthor(); | ||||
|         holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author)); | ||||
|         holder.description.setText(repo.getDescription()); | ||||
|  | ||||
|         holder.setExpanded(expandList.contains(repo)); | ||||
|  | ||||
|         holder.itemView.setOnClickListener(view -> { | ||||
|             if (holder.mExpanded) { | ||||
|                 holder.collapse(); | ||||
|                 expandList.remove(repo); | ||||
|             } else { | ||||
|                 holder.expand(); | ||||
|                 expandList.add(repo); | ||||
|             } | ||||
|         }); | ||||
|         holder.changeLog.setOnClickListener(view -> { | ||||
|             if (!TextUtils.isEmpty(repo.getLogUrl())) { | ||||
|                 new WebWindow(context.getString(R.string.changelog), repo.getLogUrl(), context); | ||||
|             } | ||||
|         }); | ||||
|         holder.updateImage.setOnClickListener(view -> { | ||||
|             String filename = repo.getName() + "-" + repo.getVersion() + ".zip"; | ||||
|             Utils.getAlertDialogBuilder(context) | ||||
|                     .setTitle(context.getString(R.string.repo_install_title, repo.getName())) | ||||
|                     .setMessage(context.getString(R.string.repo_install_msg, filename)) | ||||
|                     .setCancelable(true) | ||||
|                     .setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive( | ||||
|                             context, | ||||
|                             new RepoDlReceiver(), | ||||
|                             repo.getZipUrl(), | ||||
|                             Utils.getLegalFilename(filename))) | ||||
|                     .setNegativeButton(R.string.no_thanks, null) | ||||
|                     .show(); | ||||
|         }); | ||||
|         holder.authorLink.setOnClickListener(view -> { | ||||
|             if (!TextUtils.isEmpty(repo.getDonateUrl())) { | ||||
|                 context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getDonateUrl()))); | ||||
|             } | ||||
|         }); | ||||
|         holder.supportLink.setOnClickListener(view -> { | ||||
|             if (!TextUtils.isEmpty(repo.getSupportUrl())) { | ||||
|                 context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(repo.getSupportUrl()))); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return mUpdateRepos.size() + mInstalledRepos.size() + mOthersRepos.size(); | ||||
|     } | ||||
|  | ||||
|     private Repo getItem(int position) { | ||||
|         if (position >= mUpdateRepos.size()) { | ||||
|             position -= mUpdateRepos.size(); | ||||
|             if (position >= mInstalledRepos.size()) { | ||||
|                 position -= mInstalledRepos.size(); | ||||
|                 return mOthersRepos.get(position); | ||||
|             } else { | ||||
|                 return mInstalledRepos.get(position); | ||||
|             } | ||||
|         } else { | ||||
|             return mUpdateRepos.get(position); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         @BindView(R.id.title) TextView title; | ||||
|         @BindView(R.id.version_name) TextView versionName; | ||||
|         @BindView(R.id.description) TextView description; | ||||
|         @BindView(R.id.author) TextView author; | ||||
|         @BindView(R.id.expand_layout) LinearLayout expandLayout; | ||||
|         @BindView(R.id.update) ImageView updateImage; | ||||
|         @BindView(R.id.changeLog) ImageView changeLog; | ||||
|         @BindView(R.id.authorLink) ImageView authorLink; | ||||
|         @BindView(R.id.supportLink) ImageView supportLink; | ||||
|  | ||||
|         private ValueAnimator mAnimator; | ||||
|         private ObjectAnimator animY2; | ||||
|         private boolean mExpanded = false; | ||||
|         private static int expandHeight = 0; | ||||
|  | ||||
|         ViewHolder(View itemView) { | ||||
|             super(itemView); | ||||
|             ButterKnife.bind(this, itemView); | ||||
|             expandLayout.getViewTreeObserver().addOnPreDrawListener( | ||||
|                     new ViewTreeObserver.OnPreDrawListener() { | ||||
|  | ||||
|                         @Override | ||||
|                         public boolean onPreDraw() { | ||||
|                             if (expandHeight == 0) { | ||||
|                                 final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | ||||
|                                 final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | ||||
|                                 expandLayout.measure(widthSpec, heightSpec); | ||||
|                                 expandHeight = expandLayout.getMeasuredHeight(); | ||||
|                             } | ||||
|  | ||||
|                             expandLayout.getViewTreeObserver().removeOnPreDrawListener(this); | ||||
|                             expandLayout.setVisibility(View.GONE); | ||||
|                             mAnimator = slideAnimator(0, expandHeight); | ||||
|                             animY2 = ObjectAnimator.ofFloat(updateImage, "translationY", expandHeight / 2); | ||||
|                             return true; | ||||
|                         } | ||||
|  | ||||
|                     }); | ||||
|         } | ||||
|  | ||||
|         private void setExpanded(boolean expanded) { | ||||
|             mExpanded = expanded; | ||||
|             ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams(); | ||||
|             layoutParams.height = expanded ? expandHeight : 0; | ||||
|             expandLayout.setLayoutParams(layoutParams); | ||||
|             expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE); | ||||
|             if (expanded) { | ||||
|                 updateImage.setTranslationY(expandHeight / 2); | ||||
|             } else { | ||||
|                 updateImage.setTranslationY(0); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void expand() { | ||||
|             expandLayout.setVisibility(View.VISIBLE); | ||||
|             mAnimator.start(); | ||||
|             animY2.start(); | ||||
|             mExpanded = true; | ||||
|         } | ||||
|  | ||||
|         private void collapse() { | ||||
|             if (!mExpanded) return; | ||||
|             int finalHeight = expandLayout.getHeight(); | ||||
|             ValueAnimator mAnimator = slideAnimator(finalHeight, 0); | ||||
|             mAnimator.addListener(new Animator.AnimatorListener() { | ||||
|                 @Override | ||||
|                 public void onAnimationEnd(Animator animator) { | ||||
|                     expandLayout.setVisibility(View.GONE); | ||||
|                 } | ||||
|  | ||||
|                 @Override | ||||
|                 public void onAnimationStart(Animator animator) {} | ||||
|  | ||||
|                 @Override | ||||
|                 public void onAnimationCancel(Animator animator) {} | ||||
|  | ||||
|                 @Override | ||||
|                 public void onAnimationRepeat(Animator animator) {} | ||||
|             }); | ||||
|             mAnimator.start(); | ||||
|             animY2.reverse(); | ||||
|             mExpanded = false; | ||||
|         } | ||||
|  | ||||
|         private ValueAnimator slideAnimator(int start, int end) { | ||||
|  | ||||
|             ValueAnimator animator = ValueAnimator.ofInt(start, end); | ||||
|  | ||||
|             animator.addUpdateListener(valueAnimator -> { | ||||
|                 int value = (Integer) valueAnimator.getAnimatedValue(); | ||||
|                 ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams(); | ||||
|                 layoutParams.height = value; | ||||
|                 expandLayout.setLayoutParams(layoutParams); | ||||
|             }); | ||||
|             return animator; | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,183 +0,0 @@ | ||||
| package com.topjohnwu.magisk.adapters; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.util.SparseArray; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Comparator; | ||||
|  | ||||
| public class SimpleSectionedRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { | ||||
|  | ||||
|     private final Context mContext; | ||||
|     private static final int SECTION_TYPE = 0; | ||||
|  | ||||
|     private boolean mValid = true; | ||||
|     private int mSectionResourceId; | ||||
|     private int mTextResourceId; | ||||
|     private LayoutInflater mLayoutInflater; | ||||
|     private RecyclerView.Adapter mBaseAdapter; | ||||
|     private SparseArray<Section> mSections = new SparseArray<Section>(); | ||||
|  | ||||
|  | ||||
|     public SimpleSectionedRecyclerViewAdapter(Context context, int sectionResourceId, int textResourceId, | ||||
|                                               RecyclerView.Adapter baseAdapter) { | ||||
|  | ||||
|         mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | ||||
|         mSectionResourceId = sectionResourceId; | ||||
|         mTextResourceId = textResourceId; | ||||
|         mBaseAdapter = baseAdapter; | ||||
|         mContext = context; | ||||
|  | ||||
|         mBaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { | ||||
|             @Override | ||||
|             public void onChanged() { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyDataSetChanged(); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeChanged(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeChanged(positionStart, itemCount); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeInserted(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeInserted(positionStart, itemCount); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onItemRangeRemoved(int positionStart, int itemCount) { | ||||
|                 mValid = mBaseAdapter.getItemCount()>0; | ||||
|                 notifyItemRangeRemoved(positionStart, itemCount); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static class SectionViewHolder extends RecyclerView.ViewHolder { | ||||
|  | ||||
|         public TextView title; | ||||
|  | ||||
|         public SectionViewHolder(View view, int mTextResourceid) { | ||||
|             super(view); | ||||
|             title = (TextView) view.findViewById(mTextResourceid); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int typeView) { | ||||
|         if (typeView == SECTION_TYPE) { | ||||
|             final View view = LayoutInflater.from(mContext).inflate(mSectionResourceId, parent, false); | ||||
|             return new SectionViewHolder(view,mTextResourceId); | ||||
|         }else{ | ||||
|             return mBaseAdapter.onCreateViewHolder(parent, typeView -1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBindViewHolder(RecyclerView.ViewHolder sectionViewHolder, int position) { | ||||
|         if (isSectionHeaderPosition(position)) { | ||||
|             ((SectionViewHolder)sectionViewHolder).title.setText(mSections.get(position).title); | ||||
|         }else{ | ||||
|             mBaseAdapter.onBindViewHolder(sectionViewHolder,sectionedPositionToPosition(position)); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemViewType(int position) { | ||||
|         return isSectionHeaderPosition(position) | ||||
|                 ? SECTION_TYPE | ||||
|                 : mBaseAdapter.getItemViewType(sectionedPositionToPosition(position)) +1 ; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public static class Section { | ||||
|         int firstPosition; | ||||
|         int sectionedPosition; | ||||
|         CharSequence title; | ||||
|  | ||||
|         public Section(int firstPosition, CharSequence title) { | ||||
|             this.firstPosition = firstPosition; | ||||
|             this.title = title; | ||||
|         } | ||||
|  | ||||
|         public CharSequence getTitle() { | ||||
|             return title; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public void setSections(Section[] sections) { | ||||
|         mSections.clear(); | ||||
|  | ||||
|         Arrays.sort(sections, new Comparator<Section>() { | ||||
|             @Override | ||||
|             public int compare(Section o, Section o1) { | ||||
|                 return (o.firstPosition == o1.firstPosition) | ||||
|                         ? 0 | ||||
|                         : ((o.firstPosition < o1.firstPosition) ? -1 : 1); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         int offset = 0; // offset positions for the headers we're adding | ||||
|         for (Section section : sections) { | ||||
|             section.sectionedPosition = section.firstPosition + offset; | ||||
|             mSections.append(section.sectionedPosition, section); | ||||
|             ++offset; | ||||
|         } | ||||
|  | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
|  | ||||
|     public int positionToSectionedPosition(int position) { | ||||
|         int offset = 0; | ||||
|         for (int i = 0; i < mSections.size(); i++) { | ||||
|             if (mSections.valueAt(i).firstPosition > position) { | ||||
|                 break; | ||||
|             } | ||||
|             ++offset; | ||||
|         } | ||||
|         return position + offset; | ||||
|     } | ||||
|  | ||||
|     public int sectionedPositionToPosition(int sectionedPosition) { | ||||
|         if (isSectionHeaderPosition(sectionedPosition)) { | ||||
|             return RecyclerView.NO_POSITION; | ||||
|         } | ||||
|  | ||||
|         int offset = 0; | ||||
|         for (int i = 0; i < mSections.size(); i++) { | ||||
|             if (mSections.valueAt(i).sectionedPosition > sectionedPosition) { | ||||
|                 break; | ||||
|             } | ||||
|             --offset; | ||||
|         } | ||||
|         return sectionedPosition + offset; | ||||
|     } | ||||
|  | ||||
|     public boolean isSectionHeaderPosition(int position) { | ||||
|         return mSections.get(position) != null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public long getItemId(int position) { | ||||
|         return isSectionHeaderPosition(position) | ||||
|                 ? Integer.MAX_VALUE - mSections.indexOfKey(position) | ||||
|                 : mBaseAdapter.getItemId(sectionedPositionToPosition(position)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return (mValid ? mBaseAdapter.getItemCount() + mSections.size() : 0); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,108 +0,0 @@ | ||||
| package com.topjohnwu.magisk.module; | ||||
|  | ||||
|  | ||||
| import android.support.annotation.NonNull; | ||||
|  | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| public abstract class BaseModule implements Comparable<BaseModule> { | ||||
|  | ||||
|     protected String mId, mName, mVersion, mAuthor, mDescription, mSupportUrl, mDonateUrl; | ||||
|     protected boolean mIsCacheModule = false; | ||||
|     protected int mVersionCode = 0; | ||||
|  | ||||
|     protected void parseProps(List<String> props) throws CacheModException { parseProps(props.toArray(new String[props.size()])); } | ||||
|  | ||||
|     protected void parseProps(String[] props) throws CacheModException { | ||||
|         for (String line : props) { | ||||
|             String[] prop = line.split("=", 2); | ||||
|             if (prop.length != 2) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             String key = prop[0].trim(); | ||||
|             if (key.charAt(0) == '#') { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             switch (key) { | ||||
|                 case "id": | ||||
|                     this.mId = prop[1]; | ||||
|                     break; | ||||
|                 case "name": | ||||
|                     this.mName = prop[1]; | ||||
|                     break; | ||||
|                 case "version": | ||||
|                     this.mVersion = prop[1]; | ||||
|                     break; | ||||
|                 case "versionCode": | ||||
|                     try { | ||||
|                         this.mVersionCode = Integer.parseInt(prop[1]); | ||||
|                     } catch (NumberFormatException ignored) {} | ||||
|                     break; | ||||
|                 case "author": | ||||
|                     this.mAuthor = prop[1]; | ||||
|                     break; | ||||
|                 case "description": | ||||
|                     this.mDescription = prop[1]; | ||||
|                     break; | ||||
|                 case "support": | ||||
|                     this.mSupportUrl = prop[1]; | ||||
|                     break; | ||||
|                 case "donate": | ||||
|                     this.mDonateUrl = prop[1]; | ||||
|                     break; | ||||
|                 case "cacheModule": | ||||
|                     this.mIsCacheModule = Boolean.parseBoolean(prop[1]); | ||||
|                     break; | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|         if (mIsCacheModule) | ||||
|             throw new CacheModException(mId); | ||||
|     } | ||||
|  | ||||
|     public String getName() { | ||||
|         return mName; | ||||
|     } | ||||
|  | ||||
|     public String getVersion() { | ||||
|         return mVersion; | ||||
|     } | ||||
|  | ||||
|     public String getAuthor() { | ||||
|         return mAuthor; | ||||
|     } | ||||
|  | ||||
|     public String getId() {return mId; } | ||||
|  | ||||
|     public String getDescription() { | ||||
|         return mDescription; | ||||
|     } | ||||
|  | ||||
|     public int getVersionCode() { | ||||
|         return mVersionCode; | ||||
|     } | ||||
|  | ||||
|     public String getDonateUrl() { | ||||
|         return mDonateUrl; | ||||
|     } | ||||
|  | ||||
|     public String getSupportUrl() { | ||||
|         return mSupportUrl; | ||||
|     } | ||||
|  | ||||
|     public static class CacheModException extends Exception { | ||||
|         public CacheModException(String id) { | ||||
|             Logger.dev("Cache mods are no longer supported! id: " + id); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int compareTo(@NonNull BaseModule o) { | ||||
|         return this.getName().toLowerCase().compareTo(o.getName().toLowerCase()); | ||||
|     } | ||||
| } | ||||
| @@ -1,56 +0,0 @@ | ||||
| package com.topjohnwu.magisk.module; | ||||
|  | ||||
| import android.content.Context; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Logger; | ||||
| import com.topjohnwu.magisk.utils.WebService; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| public class Repo extends BaseModule { | ||||
|     private String mLogUrl, mManifestUrl, mZipUrl; | ||||
|     private Date mLastUpdate; | ||||
|  | ||||
|     public Repo(Context context, String name, Date lastUpdate) throws CacheModException { | ||||
|         mLastUpdate = lastUpdate; | ||||
|         mLogUrl = context.getString(R.string.file_url, name, "changelog.txt"); | ||||
|         mManifestUrl = context.getString(R.string.file_url, name, "module.prop"); | ||||
|         mZipUrl = context.getString(R.string.zip_url, name); | ||||
|         update(); | ||||
|     } | ||||
|  | ||||
|     public void update() throws CacheModException { | ||||
|         Logger.dev("Repo: Re-fetch prop"); | ||||
|         String props = WebService.request(mManifestUrl, WebService.GET, true); | ||||
|         String lines[] = props.split("\\n"); | ||||
|         parseProps(lines); | ||||
|     } | ||||
|  | ||||
|     public void update(Date lastUpdate) throws CacheModException { | ||||
|         Logger.dev("Repo: Old: " + mLastUpdate); | ||||
|         Logger.dev("Repo: New: " + lastUpdate); | ||||
|         if (mIsCacheModule) | ||||
|             throw new CacheModException(mId); | ||||
|         if (lastUpdate.after(mLastUpdate)) { | ||||
|             mLastUpdate = lastUpdate; | ||||
|             update(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public String getZipUrl() { | ||||
|         return mZipUrl; | ||||
|     } | ||||
|  | ||||
|     public String getLogUrl() { | ||||
|         return mLogUrl; | ||||
|     } | ||||
|  | ||||
|     public String getManifestUrl() { | ||||
|         return mManifestUrl; | ||||
|     } | ||||
|  | ||||
|     public Date getLastUpdate() { | ||||
|         return mLastUpdate; | ||||
|     } | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| package com.topjohnwu.magisk.receivers; | ||||
|  | ||||
| import android.net.Uri; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.StatusFragment; | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Shell; | ||||
| import com.topjohnwu.magisk.utils.ZipUtils; | ||||
|  | ||||
| import java.io.File; | ||||
|  | ||||
| public class MagiskDlReceiver extends DownloadReceiver { | ||||
|  | ||||
|     String mBoot; | ||||
|     boolean mEnc, mVerity; | ||||
|  | ||||
|     public MagiskDlReceiver(String bootImage, boolean keepEnc, boolean keepVerity) { | ||||
|         mBoot = bootImage; | ||||
|         mEnc = keepEnc; | ||||
|         mVerity = keepVerity; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDownloadDone(Uri uri) { | ||||
|         new Async.FlashZIP(mContext, uri, mFilename) { | ||||
|  | ||||
|             @Override | ||||
|             protected void preProcessing() throws Throwable { | ||||
|                 Shell.su( | ||||
|                         "echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" > /dev/.magisk", | ||||
|                         "echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk", | ||||
|                         "echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk" | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected boolean unzipAndCheck() { | ||||
|                 publishProgress(mContext.getString(R.string.zip_install_unzip_zip_msg)); | ||||
|                 if (Shell.rootAccess()) { | ||||
|                     // We might not have busybox yet, unzip with Java | ||||
|                     // We will have complete busybox after Magisk installation | ||||
|                     ZipUtils.unzip(mCachedFile, new File(mCachedFile.getParent(), "magisk")); | ||||
|                     Shell.su( | ||||
|                             "mkdir -p " + Async.TMP_FOLDER_PATH + "/magisk", | ||||
|                             "cp -af " + mCachedFile.getParent() + "/magisk/. " + Async.TMP_FOLDER_PATH + "/magisk", | ||||
|                             "mv -f " + mCachedFile.getParent() + "/magisk/META-INF " + mCachedFile.getParent() + "/META-INF" | ||||
|                     ); | ||||
|                 } | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected void onSuccess() { | ||||
|                 new Async.RootTask<Void, Void, Void>() { | ||||
|                     @Override | ||||
|                     protected Void doInBackground(Void... params) { | ||||
|                         Shell.su("setprop magisk.version " | ||||
|                                 + String.valueOf(StatusFragment.remoteMagiskVersion)); | ||||
|                         return null; | ||||
|                     } | ||||
|                 }.exec(); | ||||
|                 super.onSuccess(); | ||||
|             } | ||||
|         }.exec(); | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| package com.topjohnwu.magisk.receivers; | ||||
|  | ||||
| import android.net.Uri; | ||||
|  | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.utils.Async; | ||||
| import com.topjohnwu.magisk.utils.Utils.ByteArrayInOutStream; | ||||
| import com.topjohnwu.magisk.utils.ZipUtils; | ||||
|  | ||||
| import java.io.OutputStream; | ||||
|  | ||||
| public class RepoDlReceiver extends DownloadReceiver { | ||||
|     @Override | ||||
|     public void onDownloadDone(Uri uri) { | ||||
|         // Flash the zip | ||||
|         new Async.FlashZIP(mContext, uri, mFilename){ | ||||
|             @Override | ||||
|             protected void preProcessing() throws Throwable { | ||||
|                 // Process and sign the zip | ||||
|                 publishProgress(mContext.getString(R.string.zip_install_process_zip_msg)); | ||||
|                 ByteArrayInOutStream buffer = new ByteArrayInOutStream(); | ||||
|  | ||||
|                 // First remove top folder (the folder with the repo name) in Github source zip | ||||
|                 ZipUtils.removeTopFolder(mContext.getContentResolver().openInputStream(mUri), buffer); | ||||
|  | ||||
|                 // Then sign the zip for the first time | ||||
|                 ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, false); | ||||
|  | ||||
|                 // Adjust the zip to prevent unzip issues | ||||
|                 ZipUtils.adjustZip(buffer); | ||||
|  | ||||
|                 // Finally, sign the whole zip file again | ||||
|                 ZipUtils.signZip(mContext, buffer.getInputStream(), buffer, true); | ||||
|  | ||||
|                 // Write it back to the downloaded zip | ||||
|                 OutputStream out = mContext.getContentResolver().openOutputStream(mUri); | ||||
|                 buffer.writeTo(out); | ||||
|                 out.close(); | ||||
|             } | ||||
|         }.exec(); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,58 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
|  | ||||
| public class CallbackHandler { | ||||
|  | ||||
|     private static HashMap<Event, HashSet<EventListener>> listeners = new HashMap<>(); | ||||
|  | ||||
|     public static void register(Event event, EventListener listener) { | ||||
|         HashSet<EventListener> list = listeners.get(event); | ||||
|         if (list == null) { | ||||
|             list = new HashSet<>(); | ||||
|             listeners.put(event, list); | ||||
|         } | ||||
|         list.add(listener); | ||||
|     } | ||||
|  | ||||
|     public static void unRegister(Event event, EventListener listener) { | ||||
|         HashSet<EventListener> list = listeners.get(event); | ||||
|         if (list != null) { | ||||
|             list.remove(listener); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void triggerCallback(Event event) { | ||||
|         HashSet<EventListener> list = listeners.get(event); | ||||
|         if (list != null) { | ||||
|             for (EventListener listener : list) { | ||||
|                 listener.onTrigger(event); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static class Event { | ||||
|  | ||||
|         public boolean isTriggered = false; | ||||
|         private Object result; | ||||
|  | ||||
|         public void trigger() { | ||||
|             trigger(null); | ||||
|         } | ||||
|  | ||||
|         public void trigger(Object result) { | ||||
|             this.result = result; | ||||
|             isTriggered = true; | ||||
|             triggerCallback(this); | ||||
|         } | ||||
|  | ||||
|         public Object getResult() { | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public interface EventListener { | ||||
|         void onTrigger(Event event); | ||||
|     } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.util.Log; | ||||
|  | ||||
| public class Logger { | ||||
|  | ||||
|     public static final String TAG = "Magisk"; | ||||
|     public static final String DEV_TAG = "Magisk: DEV"; | ||||
|     public static final String DEBUG_TAG = "Magisk: DEBUG"; | ||||
|  | ||||
|     public static boolean logShell, devLog; | ||||
|  | ||||
|     public static void debug(String msg) { | ||||
|         Log.d(DEBUG_TAG, msg); | ||||
|     } | ||||
|  | ||||
|     public static void dev(String msg, Object... args) { | ||||
|         if (devLog) { | ||||
|             if (args.length == 1 && args[0] instanceof Throwable) { | ||||
|                 Log.d(DEV_TAG, msg, (Throwable) args[0]); | ||||
|             } else { | ||||
|                 Log.d(DEV_TAG, String.format(msg, args)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void dev(String msg) { | ||||
|         if (devLog) { | ||||
|             Log.d(DEV_TAG, msg); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void shell(boolean root, String msg) { | ||||
|         if (logShell) { | ||||
|             Log.d(root ? "SU" : "SH", msg); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,203 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.support.annotation.NonNull; | ||||
|  | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.topjohnwu.magisk.R; | ||||
| import com.topjohnwu.magisk.module.BaseModule; | ||||
| import com.topjohnwu.magisk.module.Module; | ||||
| import com.topjohnwu.magisk.module.Repo; | ||||
|  | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
|  | ||||
| public class ModuleHelper { | ||||
|     private static final String MAGISK_PATH = "/magisk"; | ||||
|     public static final String FILE_KEY = "RepoMap"; | ||||
|     private static final String REPO_KEY = "repomap"; | ||||
|     public static final String VERSION_KEY = "version"; | ||||
|     public static final String ETAG_KEY = "ETag"; | ||||
|     private static final int DATABASE_VER = 1; | ||||
|  | ||||
|     private static ValueSortedMap<String, Repo> repoMap = new ValueSortedMap<>(); | ||||
|     private static ValueSortedMap<String, Module> moduleMap = new ValueSortedMap<>(); | ||||
|  | ||||
|  | ||||
|     public static void createModuleMap() { | ||||
|         Logger.dev("ModuleHelper: Loading modules"); | ||||
|  | ||||
|         moduleMap.clear(); | ||||
|  | ||||
|         for (String path : Utils.getModList(MAGISK_PATH)) { | ||||
|             Logger.dev("ModuleHelper: Adding modules from " + path); | ||||
|             Module module; | ||||
|             try { | ||||
|                 module = new Module(path); | ||||
|                 moduleMap.put(module.getId(), module); | ||||
|             } catch (BaseModule.CacheModException ignored) {} | ||||
|         } | ||||
|  | ||||
|         Logger.dev("ModuleHelper: Module load done"); | ||||
|     } | ||||
|  | ||||
|     public static void createRepoMap(Context context) { | ||||
|         Logger.dev("ModuleHelper: Loading repos"); | ||||
|  | ||||
|         SharedPreferences prefs = context.getSharedPreferences(FILE_KEY, Context.MODE_PRIVATE); | ||||
|  | ||||
|         repoMap.clear(); | ||||
|  | ||||
|         Gson gson = new Gson(); | ||||
|         String jsonString; | ||||
|  | ||||
|         int cachedVersion = prefs.getInt(VERSION_KEY, 0); | ||||
|         if (cachedVersion != DATABASE_VER) { | ||||
|             // Ignore incompatible cached database | ||||
|             jsonString = null; | ||||
|         } else { | ||||
|             jsonString = prefs.getString(REPO_KEY, null); | ||||
|         } | ||||
|  | ||||
|         ValueSortedMap<String, Repo> cached = null; | ||||
|  | ||||
|         if (jsonString != null) { | ||||
|             cached = gson.fromJson(jsonString, new TypeToken<ValueSortedMap<String, Repo>>(){}.getType()); | ||||
|         } | ||||
|  | ||||
|         if (cached == null) { | ||||
|             cached = new ValueSortedMap<>(); | ||||
|         } | ||||
|  | ||||
|         // Get cached ETag to add in the request header | ||||
|         String etag = prefs.getString(ETAG_KEY, ""); | ||||
|         HashMap<String, String> header = new HashMap<>(); | ||||
|         header.put("If-None-Match", etag); | ||||
|  | ||||
|         // Making a request to main URL for repo info | ||||
|         jsonString = WebService.request( | ||||
|                 context.getString(R.string.url_main), WebService.GET, null, header, false); | ||||
|  | ||||
|         if (!jsonString.isEmpty()) { | ||||
|             try { | ||||
|                 JSONArray jsonArray = new JSONArray(jsonString); | ||||
|                 // If it gets to this point, the response is valid, update ETag | ||||
|                 etag = WebService.getLastResponseHeader().get(ETAG_KEY).get(0); | ||||
|                 // Maybe bug in Android build tools, sometimes the ETag has crap in it... | ||||
|                 etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1); | ||||
|  | ||||
|                 // Update repo info | ||||
|                 for (int i = 0; i < jsonArray.length(); i++) { | ||||
|                     JSONObject jsonobject = jsonArray.getJSONObject(i); | ||||
|                     String id = jsonobject.getString("description"); | ||||
|                     String name = jsonobject.getString("name"); | ||||
|                     String lastUpdate = jsonobject.getString("pushed_at"); | ||||
|                     SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); | ||||
|                     Date updatedDate; | ||||
|                     try { | ||||
|                         updatedDate = format.parse(lastUpdate); | ||||
|                     } catch (ParseException e) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     Repo repo = cached.get(id); | ||||
|                     try { | ||||
|                         if (repo == null) { | ||||
|                             Logger.dev("ModuleHelper: Create new repo " + id); | ||||
|                             repo = new Repo(context, name, updatedDate); | ||||
|                         } else { | ||||
|                             Logger.dev("ModuleHelper: Update cached repo " + id); | ||||
|                             repo.update(updatedDate); | ||||
|                         } | ||||
|                         if (repo.getId() != null) { | ||||
|                             repoMap.put(id, repo); | ||||
|                         } | ||||
|                     } catch (BaseModule.CacheModException ignored) {} | ||||
|                 } | ||||
|             } catch (JSONException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } else { | ||||
|             // Use cached if no internet or no updates | ||||
|             Logger.dev("ModuleHelper: No updates, use cached"); | ||||
|             repoMap.putAll(cached); | ||||
|         } | ||||
|  | ||||
|         prefs.edit() | ||||
|                 .putInt(VERSION_KEY, DATABASE_VER) | ||||
|                 .putString(REPO_KEY, gson.toJson(repoMap)) | ||||
|                 .putString(ETAG_KEY, etag) | ||||
|                 .apply(); | ||||
|  | ||||
|         Logger.dev("ModuleHelper: Repo load done"); | ||||
|     } | ||||
|  | ||||
|     public static void getModuleList(List<Module> moduleList) { | ||||
|         moduleList.clear(); | ||||
|         moduleList.addAll(moduleMap.values()); | ||||
|     } | ||||
|  | ||||
|     public static void getRepoLists(List<Repo> update, List<Repo> installed, List<Repo> others) { | ||||
|         update.clear(); | ||||
|         installed.clear(); | ||||
|         others.clear(); | ||||
|         for (Repo repo : repoMap.values()) { | ||||
|             Module module = moduleMap.get(repo.getId()); | ||||
|             if (module != null) { | ||||
|                 if (repo.getVersionCode() > module.getVersionCode()) { | ||||
|                     update.add(repo); | ||||
|                 } else { | ||||
|                     installed.add(repo); | ||||
|                 } | ||||
|             } else { | ||||
|                 others.add(repo); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static class ValueSortedMap<K, V extends Comparable > extends HashMap<K, V> { | ||||
|  | ||||
|         private List<V> sorted = new ArrayList<>(); | ||||
|  | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public Collection<V> values() { | ||||
|             if (sorted.isEmpty()) { | ||||
|                 sorted.addAll(super.values()); | ||||
|                 Collections.sort(sorted); | ||||
|             } | ||||
|             return sorted; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public V put(K key, V value) { | ||||
|             sorted.clear(); | ||||
|             return super.put(key, value); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void putAll(Map<? extends K, ? extends V> m) { | ||||
|             sorted.clear(); | ||||
|             super.putAll(m); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public V remove(Object key) { | ||||
|             sorted.clear(); | ||||
|             return super.remove(key); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,83 +0,0 @@ | ||||
| package com.topjohnwu.magisk.utils; | ||||
|  | ||||
| import android.content.Context; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.util.Base64; | ||||
|  | ||||
| import com.google.android.gms.common.ConnectionResult; | ||||
| import com.google.android.gms.common.api.GoogleApiClient; | ||||
| import com.google.android.gms.common.api.Status; | ||||
| import com.google.android.gms.safetynet.SafetyNet; | ||||
|  | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | ||||
| import java.security.SecureRandom; | ||||
|  | ||||
| public abstract class SafetyNetHelper | ||||
|         implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks { | ||||
|  | ||||
|     private GoogleApiClient mGoogleApiClient; | ||||
|  | ||||
|     public SafetyNetHelper(Context context) { | ||||
|         mGoogleApiClient = new GoogleApiClient.Builder(context) | ||||
|                 .addApi(SafetyNet.API) | ||||
|                 .addConnectionCallbacks(this) | ||||
|                 .addOnConnectionFailedListener(this) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onConnectionFailed(@NonNull ConnectionResult result) { | ||||
|         Logger.dev("SN: Google API fail"); | ||||
|         handleResults(-2); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onConnected(@Nullable Bundle bundle) { | ||||
|         Logger.dev("SN: Google API Connected"); | ||||
|         safetyNetCheck(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onConnectionSuspended(int i) { | ||||
|         Logger.dev("SN: Google API Suspended"); | ||||
|         handleResults(-3); | ||||
|     } | ||||
|  | ||||
|     public void requestTest() { | ||||
|         // Connect Google Service | ||||
|         mGoogleApiClient.connect(); | ||||
|     } | ||||
|  | ||||
|     private void safetyNetCheck() { | ||||
|         // Create nonce | ||||
|         byte[] nonce = new byte[24]; | ||||
|         new SecureRandom().nextBytes(nonce); | ||||
|  | ||||
|         Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT)); | ||||
|  | ||||
|         // Call SafetyNet | ||||
|         SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) | ||||
|                 .setResultCallback(result -> { | ||||
|                     Status status = result.getStatus(); | ||||
|                     if (status.isSuccess()) { | ||||
|                         String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); | ||||
|                         Logger.dev("SN: Response: " + json); | ||||
|                         try { | ||||
|                             JSONObject decoded = new JSONObject(json); | ||||
|                             handleResults(decoded.getBoolean("ctsProfileMatch") ? 1 : 0); | ||||
|                         } catch (JSONException ignored) {} | ||||
|                     } else { | ||||
|                         Logger.dev("SN: No response"); | ||||
|                         handleResults(-1); | ||||
|                     } | ||||
|                     // Disconnect | ||||
|                     mGoogleApiClient.disconnect(); | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     public abstract void handleResults(int i); | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user