mirror of https://github.com/topjohnwu/Magisk
Compare commits
569 Commits
Author | SHA1 | Date |
---|---|---|
LoveSy | c6f0762510 | |
LoveSy | 941a363c5a | |
Arbri çoçka | 2afcdc64a0 | |
VD $ VD171 @ Priv8 | 3c66c4bbc5 | |
VD $ VD171 @ Priv8 | 9f5cd5e1cc | |
kubalav | a35f2bb73b | |
topjohnwu | 6cf00130f4 | |
topjohnwu | 6c27ba6b88 | |
vvb2060 | dd3b9980e7 | |
vvb2060 | 02e189a029 | |
topjohnwu | 72b8d12ee4 | |
topjohnwu | eed03080c1 | |
LoveSy | 090cb4b0f9 | |
topjohnwu | 6f2c76b898 | |
topjohnwu | f61827cbec | |
topjohnwu | 3f2264f2c7 | |
topjohnwu | c1cadf4bdc | |
Rodrigo Martínez | 0e56991369 | |
LoveSy | 4dc1c59040 | |
topjohnwu | 33b7b8b297 | |
topjohnwu | e6af5ed460 | |
topjohnwu | b678afa4b6 | |
WINZORT | 4bac2df4e7 | |
igor | 50416eee09 | |
igor | 73cf501d33 | |
Hen_Ry | d2b7907bed | |
topjohnwu | 99d5dd5ea8 | |
cloudchamb3r | 5fdb841fa8 | |
topjohnwu | 7c88484d64 | |
topjohnwu | b22b6a4204 | |
topjohnwu | 2a3d34c812 | |
topjohnwu | c50ee722a1 | |
topjohnwu | ffc1e38e48 | |
topjohnwu | 6219d5fcbf | |
topjohnwu | 2e4440b702 | |
topjohnwu | 0d9ec0931b | |
vvb2060 | 60e8415369 | |
LoveSy | 652a26d5d9 | |
topjohnwu | f57839379a | |
LoveSy | 36bd00a046 | |
topjohnwu | fb5ee86615 | |
topjohnwu | 30bf5c8448 | |
topjohnwu | 2051836a73 | |
topjohnwu | 2cb0af1ff3 | |
topjohnwu | a1b6568226 | |
topjohnwu | 1eddbfd72c | |
topjohnwu | 21ed095601 | |
Js0n | 000a2e4d59 | |
Js0n | 7abe635de9 | |
topjohnwu | 9a008c17ba | |
topjohnwu | 08dbf728a4 | |
topjohnwu | 4670f762d3 | |
topjohnwu | efa49567fa | |
topjohnwu | 0ffc4527a7 | |
topjohnwu | dd9d43be96 | |
topjohnwu | 865fca71a5 | |
topjohnwu | 6b4baa3bcd | |
topjohnwu | a9ee2d7d18 | |
topjohnwu | d654b9cb97 | |
LoveSy | 4d2921e742 | |
vvb2060 | ecc74d45d1 | |
vvb2060 | 5de597f079 | |
LoveSy | 156b0e67ca | |
vvb2060 | 10069215f4 | |
LoveSy | 92b305a389 | |
topjohnwu | d20b30c771 | |
topjohnwu | 83209b21ff | |
topjohnwu | 81658d45f7 | |
topjohnwu | c951b208a1 | |
topjohnwu | 050a073771 | |
topjohnwu | 21d374214f | |
LoveSy | 19ea25a9d0 | |
topjohnwu | dbf6e40dfe | |
topjohnwu | d56f4fbc90 | |
topjohnwu | 73c3d741a7 | |
pndwal | 2b5fc75127 | |
osm0sis | 991802ab82 | |
WindowsFan9600 | 7f6b5305ba | |
canyie | 825c6c4316 | |
canyie | f00408c793 | |
topjohnwu | a6ff3672af | |
LoveSy | 2290ddeb89 | |
topjohnwu | 74af79ad03 | |
LoveSy | b6c24a3a8a | |
LoveSy | a8c2ae223a | |
topjohnwu | 953d44302c | |
topjohnwu | 24e46a5971 | |
topjohnwu | b1297c4192 | |
topjohnwu | 9ae328fd84 | |
topjohnwu | 625a1d6f44 | |
topjohnwu | 987e5f5413 | |
topjohnwu | 715284b70d | |
LoveSy | 62fc7868ac | |
topjohnwu | 1a70796339 | |
topjohnwu | af6965eefa | |
topjohnwu | 8f7d2e38f7 | |
topjohnwu | be433fa667 | |
topjohnwu | 0ccd6e7381 | |
topjohnwu | 907bbbda41 | |
topjohnwu | 4393bc077d | |
topjohnwu | 365b373480 | |
topjohnwu | 47e6dd286d | |
topjohnwu | 0dbaf52566 | |
topjohnwu | 66f49dfab5 | |
topjohnwu | f8967e9274 | |
topjohnwu | a4f008fde5 | |
topjohnwu | e9980c778b | |
topjohnwu | 06b6fb0c33 | |
topjohnwu | 38cb3d4105 | |
topjohnwu | db99caf258 | |
topjohnwu | 39dbffadfe | |
topjohnwu | b7505c3c9c | |
topjohnwu | 3185e5a7ca | |
topjohnwu | e0cbe28711 | |
topjohnwu | 66cee19cea | |
topjohnwu | 2ec29ade79 | |
topjohnwu | c865d4e187 | |
topjohnwu | a42a0a53ce | |
topjohnwu | 6d79de7d71 | |
topjohnwu | 7e9abe6e90 | |
残页 | 4d5510be4f | |
topjohnwu | b04e1394c0 | |
topjohnwu | 2aa923191e | |
topjohnwu | 4bf1c74164 | |
topjohnwu | 472c7878b2 | |
topjohnwu | 38ad871e33 | |
topjohnwu | c5d34670c4 | |
topjohnwu | 154121f3dd | |
topjohnwu | 3d91a561fe | |
topjohnwu | 2c6adbc69b | |
topjohnwu | 5280982363 | |
topjohnwu | 18c45ae289 | |
LoveSy | 41fbd2a7be | |
LoveSy | 5e45884af4 | |
topjohnwu | d78ee171bc | |
LoveSy | 356ee1febd | |
LoveSy | cc044ccc4c | |
LoveSy | 9c638cc463 | |
topjohnwu | df786eb2b6 | |
topjohnwu | 8e7186eebb | |
topjohnwu | 74b7b84561 | |
topjohnwu | 308c9999fa | |
topjohnwu | 930bb8687f | |
topjohnwu | f2c4288d2d | |
topjohnwu | b44141ae39 | |
kam821 | 86e0020964 | |
残页 | 94d3daeadf | |
LoveSy | 79334b7702 | |
LoveSy | df66458db6 | |
LoveSy | 97705704e2 | |
topjohnwu | 1206179580 | |
topjohnwu | a0b8aa4da6 | |
topjohnwu | 65207f96c8 | |
Abhishek Girish | 062e498bdd | |
topjohnwu | 1057cb3e3c | |
topjohnwu | 2dd23b2518 | |
RafaeloxMC | 8cab12998c | |
topjohnwu | 48b1c26dc8 | |
topjohnwu | f1e0bc3e4a | |
topjohnwu | 38527cd58f | |
LoveSy | e94d65b4b2 | |
LoveSy | 27ece3c7df | |
LoveSy | 06687abffc | |
vvb2060 | deedb462a0 | |
igor | c48962bdf7 | |
Wang Han | 1ef3f6e13b | |
topjohnwu | 83a34a9004 | |
topjohnwu | e30bda6c8d | |
vvb2060 | 00e9d76a5a | |
LoveSy | 6cda6c2fae | |
VD $ VD171 @ Priv8 | 6dfda6dc39 | |
LoveSy | f41994cb52 | |
topjohnwu | a003336497 | |
LoveSy | 401090d6fe | |
LoveSy | 90dcc1cd30 | |
LoveSy | 2ac464b186 | |
LoveSy | 8b7fae278b | |
topjohnwu | d73c2daf6d | |
topjohnwu | ca25935de3 | |
LoveSy | d7750b7220 | |
LoveSy | 98861f0b5a | |
topjohnwu | e35925d520 | |
Kieron Quinn | 685a2d2101 | |
LoveSy | f7e471616d | |
残页 | c013a349af | |
topjohnwu | 61ea59a27b | |
VD $ VD171 @ Priv8 | e55f338367 | |
VD $ VD171 @ Priv8 | 1425cf4105 | |
topjohnwu | b493a985b0 | |
canyie | 1fe9ede940 | |
LoveSy | 1fd49e4987 | |
LoveSy | d49b02b274 | |
LoveSy | d47e70cfaa | |
topjohnwu | 40cb031af5 | |
topjohnwu | 1dcf325547 | |
LoveSy | 4e99997013 | |
LoveSy | 334554697d | |
LoveSy | e77cbd0c15 | |
topjohnwu | 46ba008b9d | |
LoveSy | 58aded31c2 | |
LoveSy | 6f6b0ade06 | |
topjohnwu | d9b67a207b | |
topjohnwu | c7083659aa | |
topjohnwu | a6d1803105 | |
Re*Index. (ot_inc) | 66eef75673 | |
Alessandro Sangiorgi | 367f5f7b44 | |
topjohnwu | 0edcb03c45 | |
canyie | 63eef153de | |
canyie | 68442f38ac | |
topjohnwu | 8d5b9e5329 | |
topjohnwu | 6c0966b795 | |
topjohnwu | 7c2e93d266 | |
topjohnwu | 1ff7b9055f | |
topjohnwu | 49f241b77c | |
topjohnwu | cfb20b0f86 | |
topjohnwu | 6d6f14fcb3 | |
topjohnwu | 977c981265 | |
topjohnwu | ef48abf19d | |
topjohnwu | 65c18f9c09 | |
残页 | ecb31eed40 | |
topjohnwu | a80cadf587 | |
LoveSy | fce1bf2365 | |
LoveSy | cbc6d40b2c | |
LoveSy | 9fbd079560 | |
LoveSy | 42eb928054 | |
topjohnwu | 577483fde1 | |
topjohnwu | aa6c7c15cc | |
topjohnwu | 6c807d35b2 | |
topjohnwu | 8ca8cdae97 | |
topjohnwu | 75e37be6f3 | |
WindowsFan9600 | 4985314ca6 | |
topjohnwu | ac5ceb18c8 | |
topjohnwu | 72b39594d3 | |
topjohnwu | 16ae4aedf1 | |
topjohnwu | 3ba00858e6 | |
topjohnwu | 489100c755 | |
topjohnwu | da766f2a4e | |
topjohnwu | c81d7ff76c | |
topjohnwu | a6e50d3648 | |
topjohnwu | a177846044 | |
topjohnwu | 19a4e11645 | |
topjohnwu | 67cc36268e | |
topjohnwu | 28770b9a32 | |
WindowsFan9600 | 9f92e1bf15 | |
topjohnwu | 23fe5d5a19 | |
LoveSy | 9088b584f6 | |
vvb2060 | beaf636415 | |
vvb2060 | 09bb2fe8dc | |
tzagim | 1d6747d90e | |
南宫雪珊 | efadd94de3 | |
vvb2060 | 8c0b4e444a | |
Rom | 32c7106e40 | |
topjohnwu | d2f2a9e4c8 | |
topjohnwu | 985454afd4 | |
topjohnwu | 9e1322de25 | |
topjohnwu | 4e4ec73d94 | |
topjohnwu | bb39a524d0 | |
topjohnwu | 196d9af099 | |
topjohnwu | 1eeb2a34a1 | |
Arbri çoçka | cf43c56218 | |
kubalav | e6c1aec443 | |
topjohnwu | 43fd1c4c1b | |
topjohnwu | 022caca979 | |
topjohnwu | 0352ea2cca | |
topjohnwu | e483d6befe | |
vvb2060 | 678c07fff5 | |
topjohnwu | 91c92051f1 | |
topjohnwu | 4b8a0388e7 | |
topjohnwu | 66788dc58c | |
topjohnwu | dd8c28b1cb | |
残页 | 32c5153e8e | |
topjohnwu | 36de62873a | |
topjohnwu | 51e37880c6 | |
topjohnwu | 4b83c1e76c | |
topjohnwu | b0b04690d5 | |
topjohnwu | 6d1e8d86cb | |
topjohnwu | eda8c70a80 | |
topjohnwu | 587b6cfd41 | |
topjohnwu | e774408782 | |
canyie | 187f583c95 | |
topjohnwu | f5d3a71478 | |
残页 | d868ff3080 | |
nkh0472 | f80198a669 | |
topjohnwu | 6076b52c48 | |
topjohnwu | 79a1c39b30 | |
topjohnwu | 5c92d39498 | |
topjohnwu | 6e7a995716 | |
topjohnwu | a55d570213 | |
topjohnwu | 5d07d0b964 | |
Wang Han | ec115cd7e3 | |
osm0sis | 9b3896fd3d | |
topjohnwu | a3f5918d25 | |
topjohnwu | b28326198c | |
topjohnwu | 46275b90c2 | |
topjohnwu | 15e13a8d8b | |
topjohnwu | b750c89c87 | |
LoveSy | 8d7c7c3dfb | |
topjohnwu | 8e1a91509c | |
LoveSy | 927cd571f8 | |
LoveSy | 5fbd3e5c65 | |
LoveSy | 877aeb66cb | |
topjohnwu | 8a88d8465a | |
topjohnwu | dda8cc85c9 | |
topjohnwu | 6a59939d9a | |
topjohnwu | 4745e86c1b | |
topjohnwu | 9aa466c773 | |
LoveSy | 0243610c09 | |
topjohnwu | 0a2a590ab7 | |
topjohnwu | 89aee6ffa7 | |
topjohnwu | 4eaf701cb7 | |
topjohnwu | 4fff2aa7d8 | |
topjohnwu | 35b3c8ba5c | |
topjohnwu | 1d7cff7703 | |
LoveSy | 8d81bd0e33 | |
topjohnwu | 7826d7527f | |
topjohnwu | d4e552d08b | |
topjohnwu | 5a16418543 | |
topjohnwu | 7297aba15a | |
Ylarod | bc5d5f9502 | |
vvb2060 | 1761986c1b | |
topjohnwu | 1e034e3e0e | |
topjohnwu | bbf9756bfa | |
topjohnwu | 96e559fb0e | |
topjohnwu | 4c45775131 | |
LoveSy | c072b4254d | |
topjohnwu | 2dbb812126 | |
topjohnwu | be50f17f55 | |
残页 | 6f77f190f2 | |
topjohnwu | 6bdc57cbe4 | |
残页 | de00f1d5a9 | |
残页 | e9b9bf987b | |
topjohnwu | f4b6385f9f | |
topjohnwu | 75d905a56d | |
topjohnwu | b1363ee479 | |
topjohnwu | 51afe43a30 | |
残页 | 189c03c047 | |
topjohnwu | ae9d270a32 | |
topjohnwu | e47e869f6b | |
topjohnwu | c39038a439 | |
topjohnwu | 69174e2c13 | |
Chris Renshaw | 474268a0af | |
topjohnwu | eadb0307fa | |
topjohnwu | 5a5d0d5d72 | |
topjohnwu | a1273bc467 | |
topjohnwu | 0c28a916be | |
BlackMesa123 | 0ba573b789 | |
BlackMesa123 | ec42ee152c | |
I3elphegor | abcb487361 | |
vvb2060 | d12d9e82f1 | |
topjohnwu | 275208e81b | |
topjohnwu | 41226c12b8 | |
topjohnwu | f86c66c99d | |
topjohnwu | 93876b5fd3 | |
topjohnwu | b5b14ce343 | |
topjohnwu | 350d0d600c | |
topjohnwu | f924ffcbf3 | |
VD $ VD171 @ Priv8 | 0f5963f231 | |
Arbri çoçka | 1961ff2c40 | |
vvb2060 | 40003691d6 | |
topjohnwu | 8290358241 | |
kubalav | ee34f775c3 | |
vvb2060 | feb47cd88c | |
vvb2060 | c6efb51f61 | |
Hen_Ry | a5acf33ccd | |
vvb2060 | ab9ee449e4 | |
vvb2060 | 9571b6f9be | |
vvb2060 | 207d7fd3f6 | |
南宫雪珊 | bcdcfa1104 | |
vvb2060 | e0a4230dac | |
topjohnwu | 17ba5cba3e | |
topjohnwu | f2e109ad7d | |
topjohnwu | c83e141a1c | |
topjohnwu | 6089cc36de | |
topjohnwu | 9638dc0a66 | |
Andrew Gunnerson | b191a14a23 | |
topjohnwu | cf1bc82537 | |
残页 | 6141bb5bb3 | |
topjohnwu | 4d2b62da0d | |
topjohnwu | 39383229d1 | |
topjohnwu | 08bfbb154a | |
残页 | d390ca2fdf | |
topjohnwu | 7ad77a14ae | |
topjohnwu | f33343b4e6 | |
topjohnwu | af65d07456 | |
topjohnwu | 16d728f379 | |
topjohnwu | c97ab690b6 | |
topjohnwu | 4caed73fe0 | |
BlackMesa123 | 4856da1584 | |
LoveSy | 0a07018fec | |
LoveSy | 64c82e1f2c | |
topjohnwu | e8e8afa6c2 | |
LoveSy | af2207433d | |
LoveSy | 75ba62d588 | |
LoveSy | 606d97ae4d | |
topjohnwu | d778b0b0a7 | |
topjohnwu | 5ee6daf126 | |
Fs00 | 43b9a09c9b | |
Fs00 | 8475a2bb94 | |
Rom | d8692de2f4 | |
LoveSy | 33a9abc946 | |
topjohnwu | ee943afbc9 | |
LoveSy | 1f7c3e9f14 | |
topjohnwu | 46770db18b | |
vvb2060 | 92f980601c | |
vvb2060 | d0b8c16651 | |
LoveSy | a470ee6f93 | |
vvb2060 | ff1c56683d | |
topjohnwu | 4ee4cbada6 | |
topjohnwu | dbc2236dd2 | |
topjohnwu | a8c4a33e91 | |
topjohnwu | 279f955a84 | |
topjohnwu | fbd1dbb20c | |
topjohnwu | 6c09fc2e64 | |
LoveSy | f3304b482c | |
LoveSy | 0a85ef61c3 | |
topjohnwu | dc26ad7125 | |
LoveSy | 24b1c607f3 | |
topjohnwu | 732a161b67 | |
topjohnwu | 9c7cf340a1 | |
topjohnwu | 399b9e5eba | |
topjohnwu | 5805573625 | |
topjohnwu | a6b1149b9f | |
LoveSy | 51e985ae7f | |
vvb2060 | 9929b25339 | |
topjohnwu | 2359cfc480 | |
topjohnwu | 81493475f9 | |
Arbri çoçka | 0493829231 | |
VD $ VD171 @ Priv8 | e2d1952ad9 | |
LoveSy | 7450965458 | |
Vladimír Kubala | f45384685b | |
topjohnwu | 8abcccc262 | |
LoveSy | a9c89cbbbb | |
topjohnwu | d2eaa6e6c1 | |
LoveSy | 53257b6ea1 | |
LoveSy | c874391be4 | |
LoveSy | 7e8e013832 | |
topjohnwu | 037f46f7f0 | |
topjohnwu | d3e1c496ca | |
topjohnwu | d7d0a44693 | |
topjohnwu | 9d6f6764cb | |
topjohnwu | cb3ab63815 | |
topjohnwu | caae932117 | |
LoveSy | e9cf27eb5a | |
LoveSy | 6ee6685f4c | |
LoveSy | d15017b777 | |
LoveSy | a9387e63e1 | |
topjohnwu | 23c1f0111b | |
LoveSy | 866386e21f | |
LoveSy | bf10496fa9 | |
LoveSy | 607e6547a7 | |
topjohnwu | 6b21091fe2 | |
topjohnwu | e58f98e844 | |
LoveSy | b8cb9cd84d | |
LoveSy | c1038ac6f9 | |
LoveSy | c556dd0aac | |
LoveSy | d2fbcd07b7 | |
LoveSy | bf6359abaa | |
topjohnwu | d1621845b8 | |
topjohnwu | f33f1d25d0 | |
topjohnwu | 40f25f4d56 | |
topjohnwu | e13775ec2c | |
topjohnwu | ee4dad7a13 | |
topjohnwu | 5e2ef1b7f4 | |
topjohnwu | f8c38eab74 | |
topjohnwu | 305e8b3d14 | |
topjohnwu | 2a654e5d7f | |
topjohnwu | 57afae3425 | |
topjohnwu | feb44f875e | |
topjohnwu | 7eebe62bb6 | |
topjohnwu | 9ea9f01933 | |
topjohnwu | 665c6bdc4b | |
topjohnwu | c79bc83275 | |
topjohnwu | c30fbdf145 | |
topjohnwu | f12951bd1d | |
nikk gitanes | 52f2e8c4a0 | |
nikk gitanes | 1b2af1ed6d | |
nikk gitanes | 0f9b2a7df8 | |
topjohnwu | f2846694e1 | |
topjohnwu | e668dbf6f7 | |
topjohnwu | d77a368176 | |
topjohnwu | ad0da08610 | |
topjohnwu | 0c52385ad4 | |
topjohnwu | 5b8b48ccc1 | |
topjohnwu | 659b9c6fee | |
LoveSy | ec31cab5a7 | |
vvb2060 | dd93556ad8 | |
topjohnwu | 533aeadd38 | |
topjohnwu | 18d0cedbe2 | |
topjohnwu | 5a94ef9106 | |
topjohnwu | 8e8f01f8b5 | |
topjohnwu | 7087badf87 | |
topjohnwu | 47d2d4e3a5 | |
topjohnwu | 6c47d8f556 | |
topjohnwu | 8c9d0314fb | |
topjohnwu | 69144942e3 | |
topjohnwu | 5627053b74 | |
topjohnwu | 0f666de5e6 | |
LoveSy | eddc862fa3 | |
LoveSy | 4327682120 | |
LoveSy | af5bdee78f | |
LoveSy | 0e36e86dbf | |
LoveSy | f95478f1f1 | |
topjohnwu | 9fe8741a02 | |
topjohnwu | a5768e02ea | |
topjohnwu | f5aaff2b1e | |
topjohnwu | 655f778171 | |
topjohnwu | 2e77a426b2 | |
topjohnwu | 2bcf2e76f1 | |
topjohnwu | 57bd450798 | |
topjohnwu | 582cad1b8d | |
topjohnwu | 6ca2a3d841 | |
topjohnwu | 91773c3311 | |
topjohnwu | dc61033b2c | |
topjohnwu | f8d62a4b6c | |
topjohnwu | 1d2145b1b7 | |
topjohnwu | 1f7f84b74a | |
topjohnwu | cd7a335d0f | |
topjohnwu | 17569005a4 | |
topjohnwu | f36b21bae5 | |
topjohnwu | fe1ca52f6d | |
topjohnwu | 1be647a279 | |
topjohnwu | 2ea1a47bec | |
Ernest | 2d799dae0d | |
Ernest | c6408babac | |
topjohnwu | a8c1ed8795 | |
topjohnwu | e3cb5f8ddd | |
topjohnwu | e160e211dd | |
topjohnwu | 22d05ca399 | |
vvb2060 | bd2651057d | |
topjohnwu | 1610092ec4 | |
LoveSy | b9e6937996 | |
topjohnwu | a207f03952 | |
topjohnwu | 851153dd7c | |
topjohnwu | 583ffc8177 | |
topjohnwu | 7518092ad2 | |
topjohnwu | 8c2ad3883a | |
topjohnwu | d364554425 | |
vvb2060 | 726ffdcd98 | |
vvb2060 | f9d22cf8ee | |
vvb2060 | ee50da566f | |
vvb2060 | 9f7d410959 | |
vvb2060 | bc94ea4334 | |
topjohnwu | c0c9204848 | |
topjohnwu | c0d1bf63bc | |
StoyanDimitrov | bbda0cdffe | |
topjohnwu | 7b5ff99cd1 | |
topjohnwu | 21ddb26db8 | |
LoveSy | 7bf2e3875f | |
topjohnwu | b136aba1e2 | |
topjohnwu | 0d84f80b3c | |
topjohnwu | af45aeb771 | |
topjohnwu | 1c5a435e1f | |
Soo-Hwan Na | 0ea1257dcd | |
Mohamadreza Nakhleh | 4c92677b5a | |
fadlyas07 | 979260bd62 | |
topjohnwu | f7de649a36 | |
topjohnwu | 0cf0d2b821 | |
vvb2060 | 3733c9a091 | |
vvb2060 | e9f32e4f68 | |
vvb2060 | 68c2817d40 | |
naxitoo | 83d837d868 | |
I3elphegor | 093eb15ee1 | |
VD $ VD171 @ Priv8 | c6412c1b1b | |
serkanege | 1151393d74 | |
topjohnwu | 468f3efb13 | |
LoveSy | d6b19b9d4c | |
Ilya Kushnir | 709f25f600 | |
topjohnwu | 4b16e4b026 | |
topjohnwu | cdfbc02922 |
|
@ -0,0 +1,44 @@
|
|||
name: Magisk Setup
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
variant: sccache
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
max-size: 10000M
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache build cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-build-cache-
|
||||
|
||||
- name: Set up NDK
|
||||
run: python build.py -v ndk
|
||||
shell: bash
|
|
@ -2,107 +2,153 @@ name: Magisk Build
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- 'native/**'
|
||||
- 'stub/**'
|
||||
- 'buildSrc/**'
|
||||
- 'build.py'
|
||||
- 'gradle.properties'
|
||||
- '.github/workflows/build.yml'
|
||||
- "app/**"
|
||||
- "native/**"
|
||||
- "stub/**"
|
||||
- "buildSrc/**"
|
||||
- "build.py"
|
||||
- "gradle.properties"
|
||||
- ".github/workflows/build.yml"
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Build Magisk artifacts
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
env:
|
||||
NDK_CCACHE: ccache
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
RUSTC_WRAPPER: sccache
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set up ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
variant: sccache
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache build cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-build-cache-
|
||||
|
||||
- name: Set up NDK
|
||||
run: python build.py -v ndk
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build release
|
||||
run: |
|
||||
python build.py -vr all
|
||||
run: ./build.py -vr all
|
||||
|
||||
- name: Build debug
|
||||
run: |
|
||||
python build.py -v all
|
||||
run: ./build.py -v all
|
||||
|
||||
- name: Stop gradle daemon
|
||||
run: ./gradlew --stop
|
||||
|
||||
# Only upload artifacts built on Linux
|
||||
- name: Upload build artifact
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: out
|
||||
compression-level: 9
|
||||
|
||||
- name: Upload mapping and native debug symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}-symbols
|
||||
path: app/build/outputs
|
||||
compression-level: 9
|
||||
|
||||
test-build:
|
||||
name: Test building on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-14]
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build debug
|
||||
run: python build.py -v all
|
||||
|
||||
- name: Stop gradle daemon
|
||||
run: ./gradlew --stop
|
||||
|
||||
test:
|
||||
name: Test x86_64 on API ${{ matrix.api }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
api: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: out
|
||||
|
||||
- name: Upload mapping and native debug symbols
|
||||
if: runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: AVD test
|
||||
run: scripts/avd_test.sh ${{ matrix.api }}
|
||||
|
||||
test-32:
|
||||
name: Test x86 on API ${{ matrix.api }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
api: [23, 24, 25, 26, 27, 28, 29, 30]
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
name: ${{ github.sha }}-symbols
|
||||
path: app/build/outputs
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: out
|
||||
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: AVD test
|
||||
env:
|
||||
FORCE_32_BIT: 1
|
||||
run: scripts/avd_test.sh ${{ matrix.api }}
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
[submodule "busybox"]
|
||||
path = native/src/external/busybox
|
||||
url = https://github.com/topjohnwu/ndk-busybox.git
|
||||
[submodule "dtc"]
|
||||
path = native/src/external/dtc
|
||||
url = https://github.com/dgibson/dtc.git
|
||||
[submodule "lz4"]
|
||||
path = native/src/external/lz4
|
||||
url = https://github.com/lz4/lz4.git
|
||||
|
@ -16,12 +13,6 @@
|
|||
[submodule "xz"]
|
||||
path = native/src/external/xz
|
||||
url = https://github.com/xz-mirror/xz.git
|
||||
[submodule "nanopb"]
|
||||
path = native/src/external/nanopb
|
||||
url = https://github.com/nanopb/nanopb.git
|
||||
[submodule "mincrypt"]
|
||||
path = native/src/external/mincrypt
|
||||
url = https://github.com/topjohnwu/mincrypt.git
|
||||
[submodule "pcre"]
|
||||
path = native/src/external/pcre
|
||||
url = https://android.googlesource.com/platform/external/pcre
|
||||
|
@ -31,9 +22,6 @@
|
|||
[submodule "zlib"]
|
||||
path = native/src/external/zlib
|
||||
url = https://android.googlesource.com/platform/external/zlib
|
||||
[submodule "parallel-hashmap"]
|
||||
path = native/src/external/parallel-hashmap
|
||||
url = https://github.com/greg7mdp/parallel-hashmap.git
|
||||
[submodule "zopfli"]
|
||||
path = native/src/external/zopfli
|
||||
url = https://github.com/google/zopfli.git
|
||||
|
@ -43,6 +31,12 @@
|
|||
[submodule "lsplt"]
|
||||
path = native/src/external/lsplt
|
||||
url = https://github.com/LSPosed/LSPlt.git
|
||||
[submodule "system_properties"]
|
||||
path = native/src/external/system_properties
|
||||
url = https://github.com/topjohnwu/system_properties.git
|
||||
[submodule "crt0"]
|
||||
path = native/src/external/crt0
|
||||
url = https://github.com/topjohnwu/crt0.git
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
|
|
29
README.MD
29
README.MD
|
@ -18,14 +18,15 @@ Some highlight features:
|
|||
|
||||
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
||||
|
||||
[![](https://img.shields.io/badge/Magisk-v25.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.2)
|
||||
[![](https://img.shields.io/badge/Magisk%20Beta-v26.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.0)
|
||||
[![](https://img.shields.io/badge/Magisk-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[![](https://img.shields.io/badge/Magisk%20Beta-v27.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
|
||||
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
|
||||
- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)
|
||||
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
||||
|
||||
## Bug Reports
|
||||
|
@ -36,30 +37,6 @@ For installation issues, upload both boot image and install logs.<br>
|
|||
For Magisk issues, upload boot logcat or dmesg.<br>
|
||||
For Magisk app crashes, record and upload the logcat when the crash occurs.
|
||||
|
||||
## Building and Development
|
||||
|
||||
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
|
||||
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
- Install Python 3.8+ \
|
||||
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
|
||||
- Configure to use the JDK bundled in Android Studio:
|
||||
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"`
|
||||
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
|
||||
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
|
||||
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
|
||||
- Run `./build.py ndk` to let the script download and install NDK for you
|
||||
- To start building, run `build.py` to see your options. \
|
||||
For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
||||
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native sources.
|
||||
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
|
||||
|
||||
## Signing and Distribution
|
||||
|
||||
- The certificate of the key used to sign the final Magisk APK product is also directly embedded into some executables. In release builds, Magisk's root daemon will enforce this certificate check and reject and forcefully uninstall any non-matching Magisk apps to protect users from malicious and unverified Magisk APKs.
|
||||
- To do any development on Magisk itself, switch to an **official debug build and reinstall Magisk** to bypass the signature check.
|
||||
- To distribute your own Magisk builds signed with your own keys, set your signing configs in `config.prop`.
|
||||
- Check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key) for more details on generating your own key.
|
||||
|
||||
## Translation Contributions
|
||||
|
||||
Default string resources for the Magisk app and its stub APK are located here:
|
||||
|
|
|
@ -70,17 +70,17 @@ configurations.all {
|
|||
dependencies {
|
||||
implementation(project(":app:shared"))
|
||||
|
||||
implementation("com.github.topjohnwu:jtar:1.0.0")
|
||||
implementation("com.github.topjohnwu:jtar:1.1.0")
|
||||
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
|
||||
implementation("com.github.topjohnwu:lz4-java:1.7.1")
|
||||
implementation("com.jakewharton.timber:timber:5.0.1")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.77")
|
||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
|
||||
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
|
||||
implementation("io.noties.markwon:core:4.6.2")
|
||||
|
||||
val vLibsu = "5.0.5"
|
||||
val vLibsu = "5.2.2"
|
||||
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:service:${vLibsu}")
|
||||
implementation("com.github.topjohnwu.libsu:nio:${vLibsu}")
|
||||
|
@ -90,32 +90,32 @@ dependencies {
|
|||
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
|
||||
|
||||
val vOkHttp = "4.10.0"
|
||||
val vOkHttp = "4.12.0"
|
||||
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
|
||||
|
||||
val vMoshi = "1.14.0"
|
||||
val vMoshi = "1.15.0"
|
||||
implementation("com.squareup.moshi:moshi:${vMoshi}")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
|
||||
|
||||
val vRoom = "2.5.1"
|
||||
val vRoom = "2.6.1"
|
||||
implementation("androidx.room:room-runtime:${vRoom}")
|
||||
implementation("androidx.room:room-ktx:${vRoom}")
|
||||
kapt("androidx.room:room-compiler:${vRoom}")
|
||||
|
||||
val vNav = "2.5.3"
|
||||
val vNav = "2.7.7"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
|
||||
|
||||
implementation("androidx.biometric:biometric:1.1.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.6")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||
implementation("androidx.transition:transition:1.4.1")
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.0")
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
}
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE"
|
||||
android:maxSdkVersion="33" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
|
|
|
@ -67,8 +67,6 @@ public final class APKInstall {
|
|||
public interface Session {
|
||||
// @WorkerThread
|
||||
OutputStream openStream(Context context) throws IOException;
|
||||
// @WorkerThread
|
||||
void install(Context context, File apk) throws IOException;
|
||||
// @WorkerThread @Nullable
|
||||
Intent waitIntent();
|
||||
}
|
||||
|
@ -167,13 +165,5 @@ public final class APKInstall {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(Context context, File apk) throws IOException {
|
||||
try (var src = new FileInputStream(apk);
|
||||
var out = openStream(context)) {
|
||||
transfer(src, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,8 +61,9 @@
|
|||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".core.download.DownloadService"
|
||||
android:name=".core.Service"
|
||||
android:exported="false"
|
||||
android:enabled="@bool/enable_fg_service"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<service
|
||||
|
@ -82,11 +83,15 @@
|
|||
android:name="androidx.room.MultiInstanceInvalidationService"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- We don't need emoji compat -->
|
||||
<!-- We handle initialization ourselves -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- We handle profile installation ourselves -->
|
||||
<receiver
|
||||
android:name="androidx.profileinstaller.ProfileInstallReceiver"
|
||||
tools:node="remove" />
|
||||
|
||||
</application>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
package androidx.lifecycle;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ProcessLifecycleAccessor {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init(context);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import androidx.databinding.DataBindingUtil
|
|||
import androidx.databinding.OnRebindCallback
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.BR
|
||||
|
||||
|
@ -37,6 +38,9 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||
it.setVariable(BR.viewModel, viewModel)
|
||||
it.lifecycleOwner = viewLifecycleOwner
|
||||
}
|
||||
if (this is MenuProvider) {
|
||||
activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
|
||||
}
|
||||
savedInstanceState?.let { viewModel.onRestoreState(it) }
|
||||
return binding.root
|
||||
}
|
||||
|
@ -66,8 +70,6 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (this is MenuProvider)
|
||||
activity?.addMenuProvider(this, viewLifecycleOwner)
|
||||
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
|
||||
override fun onPreBind(binding: Binding): Boolean {
|
||||
this@BaseFragment.onPreBind(binding)
|
||||
|
@ -91,5 +93,4 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||
fun NavDirections.navigate() {
|
||||
navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.Manifest.permission.*
|
||||
import android.Manifest.permission.POST_NOTIFICATIONS
|
||||
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.databinding.PropertyChangeRegistry
|
||||
|
|
|
@ -5,10 +5,14 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ProcessLifecycleAccessor
|
||||
import android.system.Os
|
||||
import androidx.profileinstaller.ProfileInstaller
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.utils.DispatcherExecutor
|
||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
|
@ -19,6 +23,8 @@ import com.topjohnwu.superuser.Shell
|
|||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.system.exitProcess
|
||||
|
@ -41,6 +47,8 @@ open class App() : Application() {
|
|||
Timber.e(e)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
Os.setenv("PATH", "${Os.getenv("PATH")}:/debug_ramdisk:/sbin", true)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(context: Context) {
|
||||
|
@ -81,7 +89,13 @@ open class App() : Application() {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ProcessLifecycleAccessor.init(this)
|
||||
ProcessLifecycle.init(this)
|
||||
NetworkObserver.init(this)
|
||||
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ProfileInstaller.writeProfile(this@App)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Xml
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite
|
||||
import com.topjohnwu.magisk.core.repository.DBConfig
|
||||
import com.topjohnwu.magisk.core.repository.PreferenceConfig
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.IOException
|
||||
|
||||
object Config : PreferenceConfig, DBConfig {
|
||||
|
||||
|
@ -25,13 +25,12 @@ object Config : PreferenceConfig, DBConfig {
|
|||
override val context get() = ServiceLocator.deContext
|
||||
override val coroutineScope get() = GlobalScope
|
||||
|
||||
@get:SuppressLint("ApplySharedPref")
|
||||
val prefsFile: File get() {
|
||||
// Flush prefs to disk
|
||||
prefs.edit().apply {
|
||||
remove(Key.ASKED_HOME)
|
||||
}.commit()
|
||||
return File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
|
||||
private val prefsFile = File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
fun getPrefsFile(): File {
|
||||
prefs.edit().remove(Key.ASKED_HOME).commit()
|
||||
return prefsFile
|
||||
}
|
||||
|
||||
object Key {
|
||||
|
@ -41,6 +40,7 @@ object Config : PreferenceConfig, DBConfig {
|
|||
const val SU_MNT_NS = "mnt_ns"
|
||||
const val SU_BIOMETRIC = "su_biometric"
|
||||
const val ZYGISK = "zygisk"
|
||||
const val BOOTLOOP = "bootloop"
|
||||
const val DENYLIST = "denylist"
|
||||
const val SU_MANAGER = "requester"
|
||||
const val KEYSTORE = "keystore"
|
||||
|
@ -118,7 +118,6 @@ object Config : PreferenceConfig, DBConfig {
|
|||
|
||||
@JvmField var keepVerity = false
|
||||
@JvmField var keepEnc = false
|
||||
@JvmField var patchVbmeta = false
|
||||
@JvmField var recovery = false
|
||||
|
||||
var bootId by preference(Key.BOOT_ID, "")
|
||||
|
@ -161,8 +160,14 @@ object Config : PreferenceConfig, DBConfig {
|
|||
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
|
||||
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
|
||||
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
|
||||
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
||||
private var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
||||
var userAuth
|
||||
get() = Info.isDeviceSecure && suBiometric
|
||||
set(value) {
|
||||
suBiometric = value
|
||||
}
|
||||
var zygisk by dbSettings(Key.ZYGISK, false)
|
||||
var bootloop by dbSettings(Key.BOOTLOOP, 0)
|
||||
var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false)
|
||||
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
||||
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
|
||||
|
@ -171,10 +176,15 @@ object Config : PreferenceConfig, DBConfig {
|
|||
|
||||
fun load(pkg: String?) {
|
||||
// Only try to load prefs when fresh install and a previous package name is set
|
||||
if (pkg != null && prefs.all.isEmpty()) runCatching {
|
||||
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.use {
|
||||
prefs.edit { parsePrefs(it) }
|
||||
if (pkg != null && prefs.all.isEmpty()) {
|
||||
runBlocking {
|
||||
try {
|
||||
context.contentResolver
|
||||
.openInputStream(Provider.preferencesUri(pkg))
|
||||
?.writeTo(prefsFile, dispatcher = Dispatchers.Unconfined)
|
||||
} catch (ignored: IOException) {}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
prefs.edit {
|
||||
|
@ -191,52 +201,4 @@ object Config : PreferenceConfig, DBConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedPreferences.Editor.parsePrefs(input: InputStream) {
|
||||
runCatching {
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(input, "UTF-8")
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.START_TAG, null, "map")
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG)
|
||||
continue
|
||||
val key: String = parser.getAttributeValue(null, "name")
|
||||
fun value() = parser.getAttributeValue(null, "value")!!
|
||||
when (parser.name) {
|
||||
"string" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "string")
|
||||
putString(key, parser.nextText())
|
||||
parser.require(XmlPullParser.END_TAG, null, "string")
|
||||
}
|
||||
"boolean" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "boolean")
|
||||
putBoolean(key, value().toBoolean())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "boolean")
|
||||
}
|
||||
"int" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||
putInt(key, value().toInt())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||
}
|
||||
"long" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "long")
|
||||
putLong(key, value().toLong())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "long")
|
||||
}
|
||||
"float" -> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "int")
|
||||
putFloat(key, value().toFloat())
|
||||
parser.nextTag()
|
||||
parser.require(XmlPullParser.END_TAG, null, "int")
|
||||
}
|
||||
else -> parser.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ object Const {
|
|||
}
|
||||
|
||||
object ID {
|
||||
const val JOB_SERVICE_ID = 7
|
||||
const val DOWNLOAD_JOB_ID = 6
|
||||
const val CHECK_UPDATE_JOB_ID = 7
|
||||
}
|
||||
|
||||
object Url {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.LiveData
|
||||
import android.app.KeyguardManager
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.ktx.getProperty
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import com.topjohnwu.magisk.core.repository.NetworkService
|
||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
||||
import com.topjohnwu.superuser.ShellUtils.fastCmd
|
||||
|
||||
val isRunningAsStub get() = Info.stub != null
|
||||
|
@ -28,35 +26,31 @@ object Info {
|
|||
// Device state
|
||||
@JvmStatic val env by lazy { loadState() }
|
||||
@JvmField var isSAR = false
|
||||
var legacySAR = false
|
||||
var isAB = false
|
||||
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
|
||||
@JvmStatic val isFDE get() = crypto == "block"
|
||||
@JvmField var ramdisk = false
|
||||
@JvmField var vbmeta = false
|
||||
var patchBootVbmeta = false
|
||||
var crypto = ""
|
||||
var noDataExec = false
|
||||
var isRooted = false
|
||||
|
||||
@JvmField var hasGMS = true
|
||||
val isSamsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
@JvmField val isEmulator =
|
||||
getProperty("ro.kernel.qemu", "0") == "1" ||
|
||||
getProperty("ro.boot.qemu", "0") == "1"
|
||||
|
||||
val isConnected: LiveData<Boolean> by lazy {
|
||||
MutableLiveData(false).also { field ->
|
||||
NetworkObserver.observe(AppContext) {
|
||||
remote = EMPTY_REMOTE
|
||||
field.postValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
val isConnected = MutableLiveData(false)
|
||||
|
||||
val showSuperUser: Boolean get() {
|
||||
return env.isActive && (Const.USER_ID == 0
|
||||
|| Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER)
|
||||
}
|
||||
|
||||
val isDeviceSecure get() =
|
||||
AppContext.getSystemService(KeyguardManager::class.java).isDeviceSecure
|
||||
|
||||
private fun loadState(): Env {
|
||||
val v = fastCmd("magisk -v").split(":".toRegex())
|
||||
return Env(
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
|
@ -8,38 +11,78 @@ import androidx.core.content.getSystemService
|
|||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class JobService : BaseJobService() {
|
||||
|
||||
private val job = Job()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
private var mSession: Session? = null
|
||||
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
||||
coroutineScope.launch {
|
||||
doWork()
|
||||
@TargetApi(value = 34)
|
||||
inner class Session(
|
||||
private var params: JobParameters
|
||||
) : DownloadEngine.Session {
|
||||
|
||||
override val context get() = this@JobService
|
||||
val engine = DownloadEngine(this)
|
||||
|
||||
fun updateParams(params: JobParameters) {
|
||||
this.params = params
|
||||
engine.reattach()
|
||||
}
|
||||
|
||||
override fun attachNotification(id: Int, builder: Notification.Builder) {
|
||||
setNotification(params, id, builder.build(), JOB_END_NOTIFICATION_POLICY_REMOVE)
|
||||
}
|
||||
|
||||
override fun onDownloadComplete() {
|
||||
jobFinished(params, false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private suspend fun doWork() {
|
||||
svc.fetchUpdate()?.let {
|
||||
Info.remote = it
|
||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
|
||||
Notifications.updateAvailable()
|
||||
@SuppressLint("NewApi")
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
return when (params.jobId) {
|
||||
Const.ID.CHECK_UPDATE_JOB_ID -> checkUpdate(params)
|
||||
Const.ID.DOWNLOAD_JOB_ID -> downloadFile(params)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
job.cancel()
|
||||
return false
|
||||
override fun onStopJob(params: JobParameters?) = false
|
||||
|
||||
@TargetApi(value = 34)
|
||||
private fun downloadFile(params: JobParameters): Boolean {
|
||||
params.transientExtras.classLoader = Subject::class.java.classLoader
|
||||
val subject = params.transientExtras
|
||||
.getParcelable(DownloadEngine.SUBJECT_KEY, Subject::class.java) ?:
|
||||
return false
|
||||
|
||||
val session = mSession?.also {
|
||||
it.updateParams(params)
|
||||
} ?: run {
|
||||
Session(params).also { mSession = it }
|
||||
}
|
||||
|
||||
session.engine.download(subject)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkUpdate(params: JobParameters): Boolean {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ServiceLocator.networkService.fetchUpdate()?.let {
|
||||
Info.remote = it
|
||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
|
||||
Notifications.updateAvailable()
|
||||
jobFinished(params, false)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -47,14 +90,14 @@ class JobService : BaseJobService() {
|
|||
val scheduler = context.getSystemService<JobScheduler>() ?: return
|
||||
if (Config.checkUpdate) {
|
||||
val cmp = JobService::class.java.cmp(context.packageName)
|
||||
val info = JobInfo.Builder(Const.ID.JOB_SERVICE_ID, cmp)
|
||||
val info = JobInfo.Builder(Const.ID.CHECK_UPDATE_JOB_ID, cmp)
|
||||
.setPeriodic(TimeUnit.HOURS.toMillis(12))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else {
|
||||
scheduler.cancel(Const.ID.JOB_SERVICE_ID)
|
||||
scheduler.cancel(Const.ID.CHECK_UPDATE_JOB_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,23 @@ import android.os.ParcelFileDescriptor
|
|||
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
|
||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.TestHandler
|
||||
|
||||
class Provider : BaseProvider() {
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
return Bundle.EMPTY
|
||||
return when (method) {
|
||||
SuCallbackHandler.LOG, SuCallbackHandler.NOTIFY -> {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
Bundle.EMPTY
|
||||
}
|
||||
else -> TestHandler.run(method)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
return when (uri.encodedPath ?: return null) {
|
||||
"/prefs_file" -> ParcelFileDescriptor.open(Config.prefsFile, MODE_READ_ONLY)
|
||||
"/prefs_file" -> ParcelFileDescriptor.open(Config.getPrefsFile(), MODE_READ_ONLY)
|
||||
else -> super.openFile(uri, mode)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,11 @@ package com.topjohnwu.magisk.core
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.superuser.Shell
|
||||
|
@ -35,6 +38,12 @@ open class Receiver : BaseReceiver() {
|
|||
}
|
||||
|
||||
when (intent.action ?: return) {
|
||||
DownloadEngine.ACTION -> {
|
||||
IntentCompat.getParcelableExtra(
|
||||
intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)?.let {
|
||||
DownloadEngine.start(context, it)
|
||||
}
|
||||
}
|
||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||
// This will only work pre-O
|
||||
if (Config.suReAuth)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
|
||||
class Service : BaseService(), DownloadEngine.Session {
|
||||
|
||||
private var mEngine: DownloadEngine? = null
|
||||
override val context get() = this
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
if (intent.action == DownloadEngine.ACTION) {
|
||||
IntentCompat
|
||||
.getParcelableExtra(intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)
|
||||
?.let { subject ->
|
||||
val engine = mEngine ?: DownloadEngine(this).also { mEngine = it }
|
||||
engine.download(subject)
|
||||
}
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun attachNotification(id: Int, builder: Notification.Builder) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
startForeground(id, builder.build())
|
||||
}
|
||||
|
||||
override fun onDownloadComplete() {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import com.topjohnwu.magisk.R
|
|||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.reflectField
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.utils.RequestAuthentication
|
||||
import com.topjohnwu.magisk.core.utils.RequestInstall
|
||||
import com.topjohnwu.magisk.core.wrap
|
||||
|
||||
|
@ -43,6 +44,12 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||
installCallback = null
|
||||
}
|
||||
|
||||
var authenticateCallback: ((Boolean) -> Unit)? = null
|
||||
val requestAuthenticate = registerForActivityResult(RequestAuthentication()) {
|
||||
authenticateCallback?.invoke(it)
|
||||
authenticateCallback = null
|
||||
}
|
||||
|
||||
private var contentCallback: ContentResultCallback? = null
|
||||
private val getContent = registerForActivityResult(GetContent()) {
|
||||
if (it != null) contentCallback?.onActivityResult(it)
|
||||
|
|
|
@ -4,7 +4,11 @@ import com.topjohnwu.magisk.core.model.BranchInfo
|
|||
import com.topjohnwu.magisk.core.model.ModuleJson
|
||||
import com.topjohnwu.magisk.core.model.UpdateInfo
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.*
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Streaming
|
||||
import retrofit2.http.Url
|
||||
|
||||
private const val BRANCH = "branch"
|
||||
private const val REPO = "repo"
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
package com.topjohnwu.magisk.core.data
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Database
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.topjohnwu.magisk.core.model.su.SuLog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
|
||||
@Database(version = 1, entities = [SuLog::class], exportSchema = false)
|
||||
@Database(version = 2, entities = [SuLog::class], exportSchema = false)
|
||||
abstract class SuLogDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun suLogDao(): SuLogDao
|
||||
|
||||
companion object {
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
|
||||
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
|
||||
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
|
|
|
@ -45,6 +45,7 @@ object ServiceLocator {
|
|||
|
||||
private fun createSuLogDatabase(context: Context) =
|
||||
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
|
||||
.addMigrations(SuLogDatabase.MIGRATION_1_2)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import androidx.collection.isNotEmpty
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.ActivityTracker
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.JobService
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.cmp
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.forEach
|
||||
import com.topjohnwu.magisk.core.ktx.set
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class drives the execution of file downloads and notification management.
|
||||
*
|
||||
* Each download engine instance has to be paired with a "session" that is managed by the operating
|
||||
* system. A session is an Android component that allows executing long lasting operations and
|
||||
* have its state tied to a notification to show progress.
|
||||
*
|
||||
* A session can only have one single notification representing its state, and the operating system
|
||||
* also uses the notification to manage the lifecycle of a session. One goal of this class is
|
||||
* to support concurrent download tasks using only one single session, so internally it manages
|
||||
* all active tasks and notifications and properly re-assign notifications to be attached to
|
||||
* the session to make sure all download operations can be completed without the operating system
|
||||
* killing the session.
|
||||
*
|
||||
* For API 23 - 33, we use a foreground service as a session.
|
||||
* For API 34 and higher, we use user-initiated job services as a session.
|
||||
*/
|
||||
class DownloadEngine(
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
interface Session {
|
||||
val context: Context
|
||||
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
|
||||
const val SUBJECT_KEY = "subject"
|
||||
private const val REQUEST_CODE = 1
|
||||
|
||||
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
private fun broadcast(progress: Float, subject: Subject) {
|
||||
progressBroadcast.postValue(progress to subject)
|
||||
}
|
||||
|
||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||
progressBroadcast.value = null
|
||||
progressBroadcast.observe(owner) {
|
||||
val (progress, subject) = it ?: return@observe
|
||||
callback(progress, subject)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(context: Context, subject: Subject) =
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
} else {
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
val intent = createIntent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
// On API 34+, download tasks are handled with a user-initiated job.
|
||||
// However, there is no way to schedule a new job directly with a pending intent.
|
||||
// As a workaround, we send the subject to a broadcast receiver and have it
|
||||
// schedule the job for us.
|
||||
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun startWithActivity(activity: BaseActivity, subject: Subject) {
|
||||
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||
// Always download regardless of notification permission status
|
||||
start(activity.applicationContext, subject)
|
||||
}
|
||||
}
|
||||
|
||||
fun start(context: Context, subject: Subject) {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
val scheduler = context.getSystemService<JobScheduler>()!!
|
||||
val cmp = JobService::class.java.cmp(context.packageName)
|
||||
val extras = Bundle()
|
||||
extras.putParcelable(SUBJECT_KEY, subject)
|
||||
val info = JobInfo.Builder(Const.ID.DOWNLOAD_JOB_ID, cmp)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setUserInitiated(true)
|
||||
.setTransientExtras(extras)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(createIntent(context, subject))
|
||||
} else {
|
||||
context.startService(createIntent(context, subject))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = network.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
val activity = ActivityTracker.foreground
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
notifyRemove(subject.notifyId)
|
||||
subject.pendingIntent(activity)?.send()
|
||||
} else {
|
||||
notifyFinish(subject)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
notifyFail(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun reattach() {
|
||||
val builder = notifications[attachedId] ?: return
|
||||
session.attachNotification(attachedId, builder)
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val context get() = session.context
|
||||
private val network get() = ServiceLocator.networkService
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
val newId = Notifications.nextId()
|
||||
Notifications.mgr.notify(newId, notification.build())
|
||||
return newId
|
||||
}
|
||||
|
||||
private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(-2f, subject)
|
||||
it.setContentText(context.getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false)
|
||||
}
|
||||
|
||||
private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(1f, subject)
|
||||
it.setContentTitle(subject.title)
|
||||
.setContentText(context.getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
session.attachNotification(id, notification)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
val notification = (notifications[id] ?: Notifications.startProgress("").also {
|
||||
notifications[id] = it
|
||||
}).apply(editor)
|
||||
|
||||
if (attachedId < 0)
|
||||
attachNotification(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification.build())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyRemove(id: Int): Notification.Builder? {
|
||||
val idx = notifications.indexOfKey(id)
|
||||
var n: Notification.Builder? = null
|
||||
|
||||
if (idx >= 0) {
|
||||
n = notifications.valueAt(idx)
|
||||
notifications.removeAt(idx)
|
||||
|
||||
// The cancelled notification is the one attached to the session, need special handling
|
||||
if (attachedId == id) {
|
||||
if (notifications.isNotEmpty()) {
|
||||
// There are still remaining notifications, pick one and attach to the session
|
||||
val anotherId = notifications.keyAt(0)
|
||||
val notification = notifications.valueAt(0)
|
||||
attachNotification(anotherId, notification)
|
||||
} else {
|
||||
// No more notifications left, terminate the session
|
||||
attachedId = -1
|
||||
session.onDownloadComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.mgr.cancel(id)
|
||||
return n
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val zf = ZipFile(updateApk)
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = HideAPK.upgrade(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src)
|
||||
val output = ZipOutputStream(file.outputStream())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyAll(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
val id = subject.notifyId
|
||||
|
||||
notifyUpdate(id) { it.setContentTitle(subject.title) }
|
||||
|
||||
return ProgressInputStream(byteStream()) {
|
||||
val progress = it.toFloat() / 1048576
|
||||
notifyUpdate(id) { notification ->
|
||||
if (max > 0) {
|
||||
broadcast(progress / total, subject)
|
||||
notification
|
||||
.setProgress(max.toInt(), it.toInt(), false)
|
||||
.setContentText("%.2f / %.2f MB".format(progress, total))
|
||||
} else {
|
||||
broadcast(-1f, subject)
|
||||
notification.setContentText("%.2f MB / ??".format(progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.net.toFile
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.ActivityTracker
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.*
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.Properties
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class DownloadService : NotificationService() {
|
||||
|
||||
private val job = Job()
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
intent.getParcelableExtra<Subject>(SUBJECT_KEY)?.let { download(it) }
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = service.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
}
|
||||
val activity = ActivityTracker.foreground
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
notifyRemove(subject.notifyId)
|
||||
subject.pendingIntent(activity)?.send()
|
||||
} else {
|
||||
notifyFinish(subject)
|
||||
}
|
||||
subject.postDownload?.invoke()
|
||||
if (!hasNotifications)
|
||||
stopSelf()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
notifyFail(subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
fun writeTee(output: OutputStream) {
|
||||
val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri
|
||||
val external = uri.outputStream()
|
||||
stream.copyAndClose(TeeOutputStream(external, output))
|
||||
}
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(this)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
writeTee(updateApk.outputStream())
|
||||
|
||||
val zf = ZipFile(updateApk)
|
||||
val prop = Properties()
|
||||
prop.load(ByteArrayInputStream(zf.comment.toByteArray()))
|
||||
val stubVersion = prop.getProperty("stubVersion").toIntOrNull() ?: -1
|
||||
if (Info.stub!!.version < stubVersion) {
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val apk = subject.file.toFile()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = HideAPK.upgrade(this, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} else {
|
||||
ActivityTracker.foreground?.let {
|
||||
// Relaunch the process if we are foreground
|
||||
StubApk.restartProcess(it)
|
||||
} ?: run {
|
||||
// Or else kill the current process after posting notification
|
||||
subject.intent = selfLaunchIntent()
|
||||
subject.postDownload = { Runtime.getRuntime().exit(0) }
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(this)
|
||||
writeTee(session.openStream(this))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src.buffered())
|
||||
val output = ZipOutputStream(file.outputStream().buffered())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
assets.open("module_installer.sh").copyTo(zout)
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyTo(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUBJECT_KEY = "subject"
|
||||
private const val REQUEST_CODE = 1
|
||||
|
||||
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
|
||||
progressBroadcast.value = null
|
||||
progressBroadcast.observe(owner) {
|
||||
val (progress, subject) = it ?: return@observe
|
||||
callback(progress, subject)
|
||||
}
|
||||
}
|
||||
|
||||
private fun intent(context: Context, subject: Subject) =
|
||||
context.intent<DownloadService>().putExtra(SUBJECT_KEY, subject)
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT or FLAG_ONE_SHOT
|
||||
val intent = intent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun start(activity: BaseActivity, subject: Subject) {
|
||||
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||
// Always download regardless of notification permission status
|
||||
val app = activity.applicationContext
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
app.startForegroundService(intent(app, subject))
|
||||
} else {
|
||||
app.startService(intent(app, subject))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.InputStream
|
||||
|
||||
open class NotificationService : BaseService() {
|
||||
|
||||
private val notifications = HashMap<Int, Notification.Builder>().synchronized()
|
||||
protected val hasNotifications get() = notifications.isNotEmpty()
|
||||
|
||||
protected val service get() = ServiceLocator.networkService
|
||||
|
||||
private var attachedNotificationId = 0
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
super.onTaskRemoved(rootIntent)
|
||||
notifications.forEach { Notifications.mgr.cancel(it.key) }
|
||||
notifications.clear()
|
||||
}
|
||||
|
||||
protected fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
val id = subject.notifyId
|
||||
|
||||
notifyUpdate(id) { it.setContentTitle(subject.title) }
|
||||
|
||||
return ProgressInputStream(byteStream()) {
|
||||
val progress = it.toFloat() / 1048576
|
||||
notifyUpdate(id) { notification ->
|
||||
if (max > 0) {
|
||||
broadcast(progress / total, subject)
|
||||
notification
|
||||
.setProgress(max.toInt(), it.toInt(), false)
|
||||
.setContentText("%.2f / %.2f MB".format(progress, total))
|
||||
} else {
|
||||
broadcast(-1f, subject)
|
||||
notification.setContentText("%.2f MB / ??".format(progress))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
val newId = Notifications.nextId()
|
||||
Notifications.mgr.notify(newId, notification.build())
|
||||
return newId
|
||||
}
|
||||
|
||||
protected fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(-2f, subject)
|
||||
it.setContentText(getString(R.string.download_file_error))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setOngoing(false)
|
||||
}
|
||||
|
||||
protected fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {
|
||||
broadcast(1f, subject)
|
||||
it.setContentTitle(subject.title)
|
||||
.setContentText(getString(R.string.download_complete))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification) {
|
||||
attachedNotificationId = id
|
||||
startForeground(id, notification)
|
||||
}
|
||||
|
||||
private fun maybeDetachNotification(id: Int) : Boolean {
|
||||
if (attachedNotificationId != id) return false
|
||||
if (hasNotifications) {
|
||||
val (anotherId, notification) = notifications.entries.first()
|
||||
// Attaching a new notification will remove the current showing one
|
||||
attachNotification(anotherId, notification.build())
|
||||
return true
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
stopForeground(true)
|
||||
}
|
||||
attachedNotificationId = 0
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
fun create() = Notifications.startProgress("")
|
||||
|
||||
val wasEmpty = !hasNotifications
|
||||
val notification = notifications.getOrPut(id, ::create).also(editor).build()
|
||||
if (wasEmpty)
|
||||
attachNotification(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification)
|
||||
}
|
||||
|
||||
protected fun notifyRemove(id: Int): Notification.Builder? {
|
||||
val n = notifications.remove(id)
|
||||
if (n == null || !maybeDetachNotification(id))
|
||||
Notifications.mgr.cancel(id)
|
||||
return n
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
protected val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
|
||||
|
||||
private fun broadcast(progress: Float, subject: Subject) {
|
||||
progressBroadcast.postValue(progress to subject)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,13 +17,8 @@ import com.topjohnwu.magisk.ui.flash.FlashFragment
|
|||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
|
||||
|
||||
enum class Action {
|
||||
Flash,
|
||||
Download
|
||||
}
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
sealed class Subject : Parcelable {
|
||||
|
||||
|
@ -32,19 +27,17 @@ sealed class Subject : Parcelable {
|
|||
abstract val title: String
|
||||
abstract val notifyId: Int
|
||||
open val autoLaunch: Boolean get() = true
|
||||
open val postDownload: (() -> Unit)? get() = null
|
||||
|
||||
abstract fun pendingIntent(context: Context): PendingIntent?
|
||||
open fun pendingIntent(context: Context): PendingIntent? = null
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
val module: OnlineModule,
|
||||
val action: Action,
|
||||
private val module: OnlineModule,
|
||||
override val autoLaunch: Boolean,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject() {
|
||||
override val url: String get() = module.zipUrl
|
||||
override val title: String get() = module.downloadFilename
|
||||
override val autoLaunch: Boolean get() = action == Action.Flash
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
|
@ -65,17 +58,24 @@ sealed class Subject : Parcelable {
|
|||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
cachedFile("manager.apk")
|
||||
MediaStoreUtils.getFile("${title}.apk").uri
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
override var postDownload: (() -> Unit)? = null
|
||||
|
||||
@IgnoredOnParcel
|
||||
var intent: Intent? = null
|
||||
override fun pendingIntent(context: Context) = intent?.toPending(context)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Test(
|
||||
override val notifyId: Int = Notifications.nextId(),
|
||||
override val title: String = UUID.randomUUID().toString().substring(0, 6)
|
||||
) : Subject() {
|
||||
override val url get() = "https://link.testfile.org/250MB"
|
||||
override val file get() = File("/dev/null").toUri()
|
||||
override val autoLaunch get() = false
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
protected fun Intent.toPending(context: Context): PendingIntent {
|
||||
return PendingIntent.getActivity(context, notifyId, this,
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.graphics.Canvas
|
|||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Process
|
||||
|
@ -21,16 +20,12 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import kotlin.Array
|
||||
import kotlin.String
|
||||
import java.lang.reflect.Array as JArray
|
||||
|
||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||
|
||||
|
@ -56,110 +51,6 @@ val Context.deviceProtectedContext: Context get() =
|
|||
createDeviceProtectedStorageContext()
|
||||
} else { this }
|
||||
|
||||
fun Intent.startActivityWithRoot() {
|
||||
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||
val cmd = toCommand(args).joinToString(" ")
|
||||
Shell.cmd(cmd).submit()
|
||||
}
|
||||
|
||||
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
|
||||
action?.also {
|
||||
args.add("-a")
|
||||
args.add(it)
|
||||
}
|
||||
component?.also {
|
||||
args.add("-n")
|
||||
args.add(it.flattenToString())
|
||||
}
|
||||
data?.also {
|
||||
args.add("-d")
|
||||
args.add(it.toString())
|
||||
}
|
||||
categories?.also {
|
||||
for (cat in it) {
|
||||
args.add("-c")
|
||||
args.add(cat)
|
||||
}
|
||||
}
|
||||
type?.also {
|
||||
args.add("-t")
|
||||
args.add(it)
|
||||
}
|
||||
extras?.also {
|
||||
loop@ for (key in it.keySet()) {
|
||||
val v = it[key] ?: continue
|
||||
var value: Any = v
|
||||
val arg: String
|
||||
when {
|
||||
v is String -> arg = "--es"
|
||||
v is Boolean -> arg = "--ez"
|
||||
v is Int -> arg = "--ei"
|
||||
v is Long -> arg = "--el"
|
||||
v is Float -> arg = "--ef"
|
||||
v is Uri -> arg = "--eu"
|
||||
v is ComponentName -> {
|
||||
arg = "--ecn"
|
||||
value = v.flattenToString()
|
||||
}
|
||||
v is List<*> -> {
|
||||
if (v.isEmpty())
|
||||
continue@loop
|
||||
|
||||
arg = if (v[0] is Int)
|
||||
"--eial"
|
||||
else if (v[0] is Long)
|
||||
"--elal"
|
||||
else if (v[0] is Float)
|
||||
"--efal"
|
||||
else if (v[0] is String)
|
||||
"--esal"
|
||||
else
|
||||
continue@loop /* Unsupported */
|
||||
|
||||
val sb = StringBuilder()
|
||||
for (o in v) {
|
||||
sb.append(o.toString().replace(",", "\\,"))
|
||||
sb.append(',')
|
||||
}
|
||||
// Remove trailing comma
|
||||
sb.deleteCharAt(sb.length - 1)
|
||||
value = sb
|
||||
}
|
||||
v.javaClass.isArray -> {
|
||||
arg = if (v is IntArray)
|
||||
"--eia"
|
||||
else if (v is LongArray)
|
||||
"--ela"
|
||||
else if (v is FloatArray)
|
||||
"--efa"
|
||||
else if (v is Array<*> && v.isArrayOf<String>())
|
||||
"--esa"
|
||||
else
|
||||
continue@loop /* Unsupported */
|
||||
|
||||
val sb = StringBuilder()
|
||||
val len = JArray.getLength(v)
|
||||
for (i in 0 until len) {
|
||||
sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
|
||||
sb.append(',')
|
||||
}
|
||||
// Remove trailing comma
|
||||
sb.deleteCharAt(sb.length - 1)
|
||||
value = sb
|
||||
}
|
||||
else -> continue@loop
|
||||
} /* Unsupported */
|
||||
|
||||
args.add(arg)
|
||||
args.add(key)
|
||||
args.add(value.toString())
|
||||
}
|
||||
}
|
||||
args.add("-f")
|
||||
args.add(flags.toString())
|
||||
return args
|
||||
}
|
||||
|
||||
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||
|
||||
fun ApplicationInfo.getLabel(pm: PackageManager): String {
|
||||
|
|
|
@ -2,10 +2,15 @@ package com.topjohnwu.magisk.core.ktx
|
|||
|
||||
import androidx.collection.SparseArrayCompat
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.Field
|
||||
|
@ -35,9 +40,38 @@ inline fun <In : InputStream, Out : OutputStream> withStreams(
|
|||
}
|
||||
}
|
||||
|
||||
fun InputStream.copyAndClose(out: OutputStream) = withStreams(this, out) { i, o -> i.copyTo(o) }
|
||||
@Throws(IOException::class)
|
||||
suspend fun InputStream.copyAll(
|
||||
out: OutputStream,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
): Long {
|
||||
return withContext(dispatcher) {
|
||||
var bytesCopied: Long = 0
|
||||
val buffer = ByteArray(bufferSize)
|
||||
var bytes = read(buffer)
|
||||
while (isActive && bytes >= 0) {
|
||||
out.write(buffer, 0, bytes)
|
||||
bytesCopied += bytes
|
||||
bytes = read(buffer)
|
||||
}
|
||||
bytesCopied
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.writeTo(file: File) = copyAndClose(file.outputStream())
|
||||
@Throws(IOException::class)
|
||||
suspend inline fun InputStream.copyAndClose(
|
||||
out: OutputStream,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend inline fun InputStream.writeTo(
|
||||
file: File,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = copyAndClose(file.outputStream(), bufferSize, dispatcher)
|
||||
|
||||
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
|
||||
put(key, value)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.topjohnwu.magisk.core.ktx
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -15,12 +13,4 @@ fun reboot(reason: String = if (Config.recovery) "recovery" else "") {
|
|||
Shell.cmd("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
|
||||
}
|
||||
|
||||
fun relaunchApp(context: Context) {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return
|
||||
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||
val cmd = intent.toCommand(args).joinToString(separator = " ")
|
||||
Shell.cmd("run_delay 1 \"$cmd\"").exec()
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
suspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() }
|
||||
|
|
|
@ -14,7 +14,10 @@ class SuLog(
|
|||
val packageName: String,
|
||||
val appName: String,
|
||||
val command: String,
|
||||
val action: Boolean,
|
||||
val action: Int,
|
||||
val target: Int,
|
||||
val context: String,
|
||||
val gids: String,
|
||||
val time: Long = System.currentTimeMillis()
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true) var id: Int = 0
|
||||
|
@ -25,7 +28,10 @@ fun PackageManager.createSuLog(
|
|||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
policy: Int
|
||||
policy: Int,
|
||||
target: Int,
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
val appInfo = info.applicationInfo
|
||||
return SuLog(
|
||||
|
@ -35,7 +41,10 @@ fun PackageManager.createSuLog(
|
|||
packageName = getNameForUid(appInfo.uid)!!,
|
||||
appName = appInfo.getLabel(this),
|
||||
command = command,
|
||||
action = policy == SuPolicy.ALLOW
|
||||
action = policy,
|
||||
target = target,
|
||||
context = context,
|
||||
gids = gids,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -44,7 +53,10 @@ fun createSuLog(
|
|||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
policy: Int
|
||||
policy: Int,
|
||||
target: Int,
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
return SuLog(
|
||||
fromUid = fromUid,
|
||||
|
@ -53,6 +65,9 @@ fun createSuLog(
|
|||
packageName = "[UID] $fromUid",
|
||||
appName = "[UID] $fromUid",
|
||||
command = command,
|
||||
action = policy == SuPolicy.ALLOW
|
||||
action = policy,
|
||||
target = target,
|
||||
context = context,
|
||||
gids = gids,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -57,17 +57,20 @@ object SuCallbackHandler {
|
|||
val toUid = data.getIntComp("to.uid", -1)
|
||||
val pid = data.getIntComp("pid", -1)
|
||||
val command = data.getString("command", "")
|
||||
val target = data.getIntComp("target", -1)
|
||||
val seContext = data.getString("context", "")
|
||||
val gids = data.getString("gids", "")
|
||||
|
||||
val pm = context.packageManager
|
||||
|
||||
val log = runCatching {
|
||||
pm.getPackageInfo(fromUid, pid)?.let {
|
||||
pm.createSuLog(it, toUid, pid, command, policy)
|
||||
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
|
||||
}
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy)
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||
|
||||
if (notify)
|
||||
notify(context, log.action, log.appName)
|
||||
notify(context, log.action == SuPolicy.ALLOW, log.appName)
|
||||
|
||||
runBlocking { ServiceLocator.logRepo.insert(log) }
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.DataOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -22,7 +23,7 @@ class SuRequestHandler(
|
|||
private val policyDB: PolicyDao
|
||||
) {
|
||||
|
||||
private lateinit var output: DataOutputStream
|
||||
private lateinit var output: File
|
||||
private lateinit var policy: SuPolicy
|
||||
lateinit var pkgInfo: PackageInfo
|
||||
private set
|
||||
|
@ -52,37 +53,32 @@ class SuRequestHandler(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun close() {
|
||||
if (::output.isInitialized)
|
||||
runCatching { output.close() }
|
||||
}
|
||||
|
||||
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val fifo = intent.getStringExtra("fifo") ?: throw IOException("fifo == null")
|
||||
output = DataOutputStream(FileOutputStream(fifo))
|
||||
val uid = intent.getIntExtra("uid", -1)
|
||||
if (uid <= 0) {
|
||||
throw IOException("uid == $uid")
|
||||
}
|
||||
policy = SuPolicy(uid)
|
||||
val pid = intent.getIntExtra("pid", -1)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
// We only fill in sharedUserId and leave other fields uninitialized
|
||||
sharedUserId = name.split(":")[0]
|
||||
}
|
||||
return@withContext true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
respond(SuPolicy.DENY, -1)
|
||||
return@withContext false
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
close()
|
||||
return@withContext false
|
||||
private suspend fun init(intent: Intent): Boolean {
|
||||
val uid = intent.getIntExtra("uid", -1)
|
||||
val pid = intent.getIntExtra("pid", -1)
|
||||
val fifo = intent.getStringExtra("fifo")
|
||||
if (uid <= 0 || pid <= 0 || fifo == null) {
|
||||
Timber.e("Unexpected extras: uid=[${uid}], pid=[${pid}], fifo=[${fifo}]")
|
||||
return false
|
||||
}
|
||||
output = File(fifo)
|
||||
policy = SuPolicy(uid)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
// We only fill in sharedUserId and leave other fields uninitialized
|
||||
sharedUserId = name.split(":")[0]
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Timber.e(e)
|
||||
respond(SuPolicy.DENY, -1)
|
||||
return false
|
||||
}
|
||||
if (!output.canWrite()) {
|
||||
Timber.e("Cannot write to $output")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun respond(action: Int, time: Int) {
|
||||
|
@ -97,14 +93,15 @@ class SuRequestHandler(
|
|||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
output.writeInt(policy.policy)
|
||||
output.flush()
|
||||
DataOutputStream(FileOutputStream(output)).use {
|
||||
it.writeInt(policy.policy)
|
||||
it.flush()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
close()
|
||||
if (until >= 0)
|
||||
policyDB.update(policy)
|
||||
}
|
||||
if (until >= 0) {
|
||||
policyDB.update(policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.NOPList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object TestHandler {
|
||||
|
||||
fun run(method: String): Bundle {
|
||||
val r = Bundle()
|
||||
|
||||
fun setup(): Boolean {
|
||||
val nop = NOPList.getInstance()
|
||||
return runBlocking {
|
||||
MagiskInstaller.Emulator(nop, nop).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun test(): Boolean {
|
||||
// Make sure Zygisk works correctly
|
||||
if (!Info.isZygiskEnabled) {
|
||||
r.putString("reason", "zygisk not enabled")
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the Magisk app can get root
|
||||
val shell = Shell.getShell()
|
||||
if (!shell.isRoot) {
|
||||
r.putString("reason", "shell not root")
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val b = runCatching {
|
||||
when (method) {
|
||||
"setup" -> setup()
|
||||
"test" -> test()
|
||||
else -> {
|
||||
r.putString("reason", "unknown method")
|
||||
false
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
r.putString("reason", it.stackTraceToString())
|
||||
false
|
||||
}
|
||||
|
||||
r.putBoolean("result", b)
|
||||
return r
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ open class FlashZip(
|
|||
private lateinit var zipFile: File
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun flash(): Boolean {
|
||||
private suspend fun flash(): Boolean {
|
||||
installDir.deleteRecursively()
|
||||
installDir.mkdirs()
|
||||
|
||||
|
@ -47,13 +47,13 @@ open class FlashZip(
|
|||
}
|
||||
}
|
||||
|
||||
val isValid = runCatching {
|
||||
val isValid = try {
|
||||
zipFile.unzip(installDir, "META-INF/com/google/android", true)
|
||||
val script = File(installDir, "updater-script")
|
||||
script.readText().contains("#MAGISK")
|
||||
}.getOrElse {
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unzip error")
|
||||
throw it
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.topjohnwu.magisk.core.Config
|
|||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Provider
|
||||
import com.topjohnwu.magisk.core.ktx.await
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.AXML
|
||||
|
@ -168,7 +169,7 @@ object HideAPK {
|
|||
activity.finish()
|
||||
}
|
||||
|
||||
private fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
|
||||
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
|
||||
val stub = File(activity.cacheDir, "stub.apk")
|
||||
try {
|
||||
activity.assets.open("stub.apk").writeTo(stub)
|
||||
|
@ -195,7 +196,7 @@ object HideAPK {
|
|||
if (Shell.cmd(cmd).exec().isSuccess) return true
|
||||
|
||||
try {
|
||||
session.install(activity, repack)
|
||||
repack.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
|
@ -244,7 +245,7 @@ object HideAPK {
|
|||
if (Shell.cmd(cmd).await().isSuccess) return
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
session.install(activity, apk)
|
||||
apk.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return@withContext false
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
package com.topjohnwu.magisk.core.tasks
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Process
|
||||
import android.system.ErrnoException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.system.OsConstants.O_WRONLY
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.AppApkPath
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.reboot
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.signing.SignBoot
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.internal.NOPList
|
||||
|
@ -40,6 +48,7 @@ import java.util.*
|
|||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class MagiskInstallImpl protected constructor(
|
||||
protected val console: MutableList<String> = NOPList.getInstance(),
|
||||
|
@ -86,7 +95,7 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun extractFiles(): Boolean {
|
||||
private suspend fun extractFiles(): Boolean {
|
||||
console.add("- Device platform: ${Const.CPU_ABI}")
|
||||
console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
|
||||
|
||||
|
@ -97,20 +106,23 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
try {
|
||||
// Extract binaries
|
||||
if (isRunningAsStub) {
|
||||
val zf = ZipFile(StubApk.current(context))
|
||||
ZipFile(StubApk.current(context)).use { zf ->
|
||||
zf.entries().asSequence().filter {
|
||||
!it.isDirectory && it.name.startsWith("/lib/${Const.CPU_ABI}/")
|
||||
}.forEach {
|
||||
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
|
||||
val name = n.substring(3, n.length - 3)
|
||||
val dest = File(installDir, name)
|
||||
zf.getInputStream(it).writeTo(dest)
|
||||
dest.setExecutable(true)
|
||||
}
|
||||
|
||||
// Also extract magisk32 on non 64-bit only 64-bit devices
|
||||
val is32lib = Const.CPU_ABI_32?.let {
|
||||
{ entry: ZipEntry -> entry.name == "lib/$it/libmagisk32.so" }
|
||||
} ?: { false }
|
||||
|
||||
zf.entries().asSequence().filter {
|
||||
!it.isDirectory && (it.name.startsWith("lib/${Const.CPU_ABI}/") || is32lib(it))
|
||||
}.forEach {
|
||||
val n = it.name.substring(it.name.lastIndexOf('/') + 1)
|
||||
val name = n.substring(3, n.length - 3)
|
||||
val dest = File(installDir, name)
|
||||
zf.getInputStream(it).writeTo(dest)
|
||||
val abi32 = Const.CPU_ABI_32
|
||||
if (Process.is64Bit() && abi32 != null) {
|
||||
val magisk32 = File(installDir, "magisk32")
|
||||
zf.getInputStream(ZipEntry("lib/$abi32/libmagisk.so")).writeTo(magisk32)
|
||||
magisk32.setExecutable(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val info = context.applicationInfo
|
||||
|
@ -118,16 +130,17 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
name.startsWith("lib") && name.endsWith(".so")
|
||||
} ?: emptyArray()
|
||||
|
||||
// Also symlink magisk32 on non 64-bit only 64-bit devices
|
||||
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
|
||||
if (lib32 != null) {
|
||||
libs += File(lib32, "libmagisk32.so")
|
||||
}
|
||||
|
||||
for (lib in libs) {
|
||||
val name = lib.name.substring(3, lib.name.length - 3)
|
||||
Os.symlink(lib.path, "$installDir/$name")
|
||||
}
|
||||
|
||||
// Also symlink magisk32 on 64-bit devices that supports 32-bit
|
||||
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
|
||||
.get(info) as String?
|
||||
if (lib32 != null) {
|
||||
Os.symlink("$lib32/libmagisk.so", "$installDir/magisk32");
|
||||
}
|
||||
}
|
||||
|
||||
// Extract scripts
|
||||
|
@ -164,106 +177,221 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun InputStream.cleanPump(out: OutputStream) = withStreams(this, out) { src, _ ->
|
||||
src.copyTo(out)
|
||||
}
|
||||
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
|
||||
|
||||
private fun newTarEntry(name: String, size: Long): TarEntry {
|
||||
console.add("-- Writing: $name")
|
||||
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
|
||||
}
|
||||
|
||||
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
|
||||
// Make sure available is never called on the actual stream and always return 0
|
||||
// 1. Workaround bug in LZ4FrameInputStream
|
||||
// 2. Reduce max buffer size to prevent OOM
|
||||
override fun available() = 0
|
||||
}
|
||||
|
||||
private class NoBootException : IOException()
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun processTar(input: InputStream, output: OutputStream): OutputStream {
|
||||
private suspend fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
|
||||
console.add("- Processing tar file")
|
||||
val tarOut = TarOutputStream(output)
|
||||
TarInputStream(input).use { tarIn ->
|
||||
lateinit var entry: TarEntry
|
||||
lateinit var entry: TarEntry
|
||||
|
||||
fun decompressedStream(): InputStream {
|
||||
val src = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
|
||||
return object : FilterInputStream(src) {
|
||||
override fun available() = 0 /* Workaround bug in LZ4FrameInputStream */
|
||||
override fun close() { /* Never close src stream */ }
|
||||
}
|
||||
}
|
||||
fun decompressedStream(): InputStream {
|
||||
val stream = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
|
||||
return NoAvailableStream(stream)
|
||||
}
|
||||
|
||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||
if (entry.name.startsWith("boot.img") ||
|
||||
entry.name.startsWith("init_boot.img") ||
|
||||
(Config.recovery && entry.name.contains("recovery.img"))) {
|
||||
val name = entry.name.replace(".lz4", "")
|
||||
console.add("-- Extracting: $name")
|
||||
while (tarIn.nextEntry?.let { entry = it } != null) {
|
||||
if (entry.name.startsWith("boot.img") ||
|
||||
entry.name.startsWith("init_boot.img") ||
|
||||
(Config.recovery && entry.name.contains("recovery.img"))) {
|
||||
val name = entry.name.replace(".lz4", "")
|
||||
console.add("-- Extracting: $name")
|
||||
|
||||
val extract = installDir.getChildFile(name)
|
||||
decompressedStream().cleanPump(extract.newOutputStream())
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
val extract = installDir.getChildFile(name)
|
||||
decompressedStream().copyAndCloseOut(extract.newOutputStream())
|
||||
} else if (entry.name.contains("vbmeta.img")) {
|
||||
val rawData = decompressedStream().readBytes()
|
||||
// Valid vbmeta.img should be at least 256 bytes
|
||||
if (rawData.size < 256)
|
||||
continue
|
||||
|
||||
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
|
||||
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
|
||||
console.add("-- Patching: vbmeta.img")
|
||||
ByteBuffer.wrap(rawData).putInt(120, 3)
|
||||
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
|
||||
tarOut.write(rawData)
|
||||
} else {
|
||||
console.add("-- Copying: ${entry.name}")
|
||||
tarOut.putNextEntry(entry)
|
||||
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
|
||||
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
|
||||
console.add("-- Patching: vbmeta.img")
|
||||
ByteBuffer.wrap(rawData).putInt(120, 3)
|
||||
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
|
||||
tarOut.write(rawData)
|
||||
// vbmeta partition exist, disable boot vbmeta patch
|
||||
Info.patchBootVbmeta = false
|
||||
} else if (entry.name.contains("userdata.img")) {
|
||||
continue
|
||||
} else {
|
||||
console.add("-- Copying: ${entry.name}")
|
||||
tarOut.putNextEntry(entry)
|
||||
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
|
||||
}
|
||||
}
|
||||
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
val recovery = installDir.getChildFile("recovery.img")
|
||||
if (Config.recovery && recovery.exists() && boot.exists()) {
|
||||
// Install to recovery
|
||||
srcBoot = recovery
|
||||
// Repack boot image to prevent auto restore
|
||||
arrayOf(
|
||||
"cd $installDir",
|
||||
"chmod -R 755 .",
|
||||
"./magiskboot unpack boot.img",
|
||||
"./magiskboot repack boot.img",
|
||||
"cat new-boot.img > boot.img",
|
||||
"./magiskboot cleanup",
|
||||
"rm -f new-boot.img",
|
||||
"cd /").sh()
|
||||
boot.newInputStream().use {
|
||||
tarOut.putNextEntry(newTarEntry("boot.img", boot.length()))
|
||||
it.copyTo(tarOut)
|
||||
|
||||
suspend fun ExtendedFile.copyToTar() {
|
||||
newInputStream().use {
|
||||
tarOut.putNextEntry(newTarEntry(name, length()))
|
||||
it.copyAll(tarOut)
|
||||
}
|
||||
boot.delete()
|
||||
} else {
|
||||
srcBoot = when {
|
||||
initBoot.exists() -> initBoot
|
||||
boot.exists() -> boot
|
||||
else -> {
|
||||
console.add("! No boot image found")
|
||||
throw IOException()
|
||||
delete()
|
||||
}
|
||||
|
||||
// Patch priority: recovery > init_boot > boot
|
||||
return when {
|
||||
recovery.exists() -> {
|
||||
if (boot.exists()) {
|
||||
// Repack boot image to prevent auto restore
|
||||
arrayOf(
|
||||
"cd $installDir",
|
||||
"chmod -R 755 .",
|
||||
"./magiskboot unpack boot.img",
|
||||
"./magiskboot repack boot.img",
|
||||
"cat new-boot.img > boot.img",
|
||||
"./magiskboot cleanup",
|
||||
"rm -f new-boot.img",
|
||||
"cd /").sh()
|
||||
boot.copyToTar()
|
||||
}
|
||||
recovery
|
||||
}
|
||||
initBoot.exists() -> {
|
||||
if (boot.exists())
|
||||
boot.copyToTar()
|
||||
initBoot
|
||||
}
|
||||
boot.exists() -> boot
|
||||
else -> throw NoBootException()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private suspend fun processZip(zipIn: ZipInputStream): ExtendedFile {
|
||||
console.add("- Processing zip file")
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
lateinit var entry: ZipEntry
|
||||
while (zipIn.nextEntry?.also { entry = it } != null) {
|
||||
if (entry.isDirectory) continue
|
||||
when (entry.name.substringAfterLast('/')) {
|
||||
"payload.bin" -> {
|
||||
try {
|
||||
return processPayload(zipIn)
|
||||
} catch (e: IOException) {
|
||||
// No boot image in payload.bin, continue to find boot images
|
||||
}
|
||||
}
|
||||
"init_boot.img" -> {
|
||||
console.add("- Extracting init_boot.img")
|
||||
zipIn.copyAndCloseOut(initBoot.newOutputStream())
|
||||
return initBoot
|
||||
}
|
||||
"boot.img" -> {
|
||||
console.add("- Extracting boot.img")
|
||||
zipIn.copyAndCloseOut(boot.newOutputStream())
|
||||
// Don't return here since there might be an init_boot.img
|
||||
}
|
||||
}
|
||||
}
|
||||
return tarOut
|
||||
if (boot.exists()) {
|
||||
return boot
|
||||
} else {
|
||||
throw NoBootException()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFile(uri: Uri): Boolean {
|
||||
@Throws(IOException::class)
|
||||
private fun processPayload(input: InputStream): ExtendedFile {
|
||||
var fifo: File? = null
|
||||
try {
|
||||
console.add("- Processing payload.bin")
|
||||
fifo = File.createTempFile("payload-fifo-", null, installDir)
|
||||
fifo.delete()
|
||||
Os.mkfifo(fifo.path, 420 /* 0644 */)
|
||||
|
||||
// Enqueue the shell command first, or the subsequent FIFO open will block
|
||||
val future = arrayOf(
|
||||
"cd $installDir",
|
||||
"./magiskboot extract $fifo",
|
||||
"cd /"
|
||||
).eq()
|
||||
|
||||
val fd = Os.open(fifo.path, O_WRONLY, 0)
|
||||
try {
|
||||
val bufSize = 1024 * 1024
|
||||
val buf = ByteBuffer.allocate(bufSize)
|
||||
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
|
||||
while (buf.hasRemaining()) {
|
||||
try {
|
||||
Os.write(fd, buf)
|
||||
} catch (e: ErrnoException) {
|
||||
if (e.errno != OsConstants.EPIPE)
|
||||
throw e
|
||||
// If SIGPIPE, then the other side is closed, we're done
|
||||
break
|
||||
}
|
||||
if (!buf.hasRemaining()) {
|
||||
buf.limit(bufSize)
|
||||
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Os.close(fd)
|
||||
}
|
||||
|
||||
val success = try { future.get().isSuccess } catch (e: Exception) { false }
|
||||
if (!success) {
|
||||
console.add("! Error while extracting payload.bin")
|
||||
throw IOException()
|
||||
}
|
||||
val boot = installDir.getChildFile("boot.img")
|
||||
val initBoot = installDir.getChildFile("init_boot.img")
|
||||
return when {
|
||||
initBoot.exists() -> {
|
||||
console.add("-- Extract init_boot.img")
|
||||
initBoot
|
||||
}
|
||||
boot.exists() -> {
|
||||
console.add("-- Extract boot.img")
|
||||
boot
|
||||
}
|
||||
else -> {
|
||||
throw NoBootException()
|
||||
}
|
||||
}
|
||||
} catch (e: ErrnoException) {
|
||||
throw IOException(e)
|
||||
} finally {
|
||||
fifo?.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFile(uri: Uri): Boolean {
|
||||
val outStream: OutputStream
|
||||
var outFile: MediaStoreUtils.UriFile? = null
|
||||
val outFile: MediaStoreUtils.UriFile
|
||||
|
||||
// Process input file
|
||||
try {
|
||||
uri.inputStream().buffered().use { src ->
|
||||
src.mark(500)
|
||||
val magic = ByteArray(5)
|
||||
if (src.skip(257) != 257L || src.read(magic) != magic.size) {
|
||||
PushbackInputStream(uri.inputStream(), 512).use { src ->
|
||||
val head = ByteArray(512)
|
||||
if (src.read(head) != head.size) {
|
||||
console.add("! Invalid input file")
|
||||
return false
|
||||
}
|
||||
src.reset()
|
||||
src.unread(head)
|
||||
|
||||
val magic = head.copyOf(4)
|
||||
val tarMagic = Arrays.copyOfRange(head, 257, 262)
|
||||
|
||||
val alpha = "abcdefghijklmnopqrstuvwxyz"
|
||||
val alphaNum = "$alpha${alpha.uppercase(Locale.ROOT)}0123456789"
|
||||
|
@ -275,29 +403,52 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
toString()
|
||||
}
|
||||
|
||||
outStream = if (magic.contentEquals("ustar".toByteArray())) {
|
||||
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
|
||||
// tar file
|
||||
outFile = MediaStoreUtils.getFile("$filename.tar", true)
|
||||
processTar(src, outFile!!.uri.outputStream())
|
||||
outStream = TarOutputStream(outFile.uri.outputStream())
|
||||
|
||||
try {
|
||||
processTar(TarInputStream(src), outStream)
|
||||
} catch (e: IOException) {
|
||||
outStream.close()
|
||||
outFile.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
// raw image
|
||||
srcBoot = installDir.getChildFile("boot.img")
|
||||
console.add("- Copying image to cache")
|
||||
src.cleanPump(srcBoot.newOutputStream())
|
||||
outFile = MediaStoreUtils.getFile("$filename.img", true)
|
||||
outFile!!.uri.outputStream()
|
||||
outStream = outFile.uri.outputStream()
|
||||
|
||||
try {
|
||||
if (magic.contentEquals("CrAU".toByteArray())) {
|
||||
processPayload(src)
|
||||
} else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) {
|
||||
processZip(ZipInputStream(src))
|
||||
} else {
|
||||
console.add("- Copying image to cache")
|
||||
installDir.getChildFile("boot.img").also {
|
||||
src.copyAndCloseOut(it.newOutputStream())
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
outStream.close()
|
||||
outFile.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
if (e is NoBootException)
|
||||
console.add("! No boot image found")
|
||||
console.add("! Process error")
|
||||
outFile?.delete()
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch file
|
||||
if (!patchBoot()) {
|
||||
outFile!!.delete()
|
||||
outFile.delete()
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -314,7 +465,7 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
}
|
||||
outStream.putNextEntry(newTarEntry(name, newBoot.length()))
|
||||
}
|
||||
newBoot.newInputStream().cleanPump(outStream)
|
||||
newBoot.newInputStream().copyAndClose(outStream)
|
||||
newBoot.delete()
|
||||
|
||||
console.add("")
|
||||
|
@ -324,7 +475,7 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
console.add("****************************")
|
||||
} catch (e: IOException) {
|
||||
console.add("! Failed to output to $outFile")
|
||||
outFile!!.delete()
|
||||
outFile.delete()
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
@ -337,22 +488,6 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
}
|
||||
|
||||
private fun patchBoot(): Boolean {
|
||||
var isSigned = false
|
||||
if (!srcBoot.isCharacter) {
|
||||
try {
|
||||
srcBoot.newInputStream().use {
|
||||
if (SignBoot.verifySignature(it, null)) {
|
||||
isSigned = true
|
||||
console.add("- Boot image is signed with AVB 1.0")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unable to check signature")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
val newBoot = installDir.getChildFile("new-boot.img")
|
||||
if (!useRootDir) {
|
||||
// Create output files before hand
|
||||
|
@ -364,39 +499,20 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
"cd $installDir",
|
||||
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
|
||||
"KEEPVERITY=${Config.keepVerity} " +
|
||||
"PATCHVBMETAFLAG=${Config.patchVbmeta} " +
|
||||
"PATCHVBMETAFLAG=${Info.patchBootVbmeta} " +
|
||||
"RECOVERYMODE=${Config.recovery} " +
|
||||
"SYSTEM_ROOT=${Info.isSAR} " +
|
||||
"LEGACYSAR=${Info.legacySAR} " +
|
||||
"sh boot_patch.sh $srcBoot")
|
||||
val isSuccess = cmds.sh().isSuccess
|
||||
|
||||
if (!cmds.sh().isSuccess)
|
||||
return false
|
||||
shell.newJob().add("./magiskboot cleanup", "cd /").exec()
|
||||
|
||||
val job = shell.newJob().add("./magiskboot cleanup", "cd /")
|
||||
|
||||
if (isSigned) {
|
||||
console.add("- Signing boot image with verity keys")
|
||||
val signed = File.createTempFile("signed", ".img", context.cacheDir)
|
||||
try {
|
||||
val src = newBoot.newInputStream().buffered()
|
||||
val out = signed.outputStream().buffered()
|
||||
withStreams(src, out) { _, _ ->
|
||||
SignBoot.doSignature(null, null, src, out, "/boot")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unable to sign image")
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
job.add("cat $signed > $newBoot", "rm -f $signed")
|
||||
}
|
||||
job.exec()
|
||||
return true
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
|
||||
|
||||
private fun postOTA(): Boolean {
|
||||
private suspend fun postOTA(): Boolean {
|
||||
try {
|
||||
val bootctl = File.createTempFile("bootctl", null, context.cacheDir)
|
||||
context.assets.open("bootctl").writeTo(bootctl)
|
||||
|
@ -407,25 +523,27 @@ abstract class MagiskInstallImpl protected constructor(
|
|||
return false
|
||||
}
|
||||
|
||||
console.add("***************************************")
|
||||
console.add("*************************************************************")
|
||||
console.add(" Next reboot will boot to second slot!")
|
||||
console.add("***************************************")
|
||||
console.add(" Go back to System Updates and press Restart to complete OTA")
|
||||
console.add("*************************************************************")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Array<String>.eq() = shell.newJob().add(*this).to(console, logs).enqueue()
|
||||
private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()
|
||||
private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()
|
||||
private fun String.fsh() = ShellUtils.fastCmd(shell, this)
|
||||
private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)
|
||||
|
||||
protected fun patchFile(file: Uri) = extractFiles() && handleFile(file)
|
||||
protected suspend fun patchFile(file: Uri) = extractFiles() && handleFile(file)
|
||||
|
||||
protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
|
||||
protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
|
||||
|
||||
protected fun secondSlot() =
|
||||
protected suspend fun secondSlot() =
|
||||
findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA()
|
||||
|
||||
protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
|
||||
protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
|
||||
object BiometricHelper {
|
||||
|
||||
private val mgr by lazy { BiometricManager.from(AppContext) }
|
||||
|
||||
val isSupported get() = when (mgr.canAuthenticate()) {
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val isEnabled: Boolean get() {
|
||||
val enabled = Config.suBiometric
|
||||
if (enabled && !isSupported) {
|
||||
Config.suBiometric = false
|
||||
return false
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
fun authenticate(
|
||||
activity: FragmentActivity,
|
||||
onError: () -> Unit = {},
|
||||
onSuccess: () -> Unit): BiometricPrompt {
|
||||
val prompt = BiometricPrompt(activity,
|
||||
ContextCompat.getMainExecutor(activity),
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
onError()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
onError()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
)
|
||||
val info = BiometricPrompt.PromptInfo.Builder()
|
||||
.setConfirmationRequired(true)
|
||||
.setDeviceCredentialAllowed(false)
|
||||
.setTitle(activity.getString(R.string.authenticate))
|
||||
.setNegativeButtonText(activity.getString(android.R.string.cancel))
|
||||
.build()
|
||||
prompt.authenticate(info)
|
||||
return prompt
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,7 @@ import com.topjohnwu.magisk.core.createNewResources
|
|||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
var currentLocale: Locale = Locale.getDefault()
|
||||
|
||||
|
|
|
@ -15,9 +15,6 @@ import com.topjohnwu.magisk.core.di.AppContext
|
|||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
import kotlin.experimental.and
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object MediaStoreUtils {
|
||||
|
@ -102,6 +99,8 @@ object MediaStoreUtils {
|
|||
|
||||
fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException()
|
||||
|
||||
fun Uri.fileDescriptor(mode: String) = cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException()
|
||||
|
||||
val Uri.displayName: String get() {
|
||||
if (scheme == "file") {
|
||||
// Simple uri wrapper over file, directly get file name
|
||||
|
@ -118,24 +117,6 @@ object MediaStoreUtils {
|
|||
return this.toString()
|
||||
}
|
||||
|
||||
fun Uri.checkSum(alg: String, reference: String) = runCatching {
|
||||
this.inputStream().use {
|
||||
val digest = MessageDigest.getInstance(alg)
|
||||
it.copyTo(object : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
digest.update(b.toByte())
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
digest.update(b, off, len)
|
||||
}
|
||||
})
|
||||
val sb = StringBuilder()
|
||||
digest.digest().forEach { b -> sb.append("%02x".format(b and 0xff.toByte())) }
|
||||
sb.toString() == reference
|
||||
}
|
||||
}.getOrElse { false }
|
||||
|
||||
interface UriFile {
|
||||
val uri: Uri
|
||||
fun delete(): Boolean
|
||||
|
|
|
@ -14,14 +14,10 @@ import androidx.core.content.getSystemService
|
|||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
|
||||
|
||||
typealias ConnectionCallback = (Boolean) -> Unit
|
||||
|
||||
class NetworkObserver(
|
||||
context: Context,
|
||||
private val callback: ConnectionCallback
|
||||
): DefaultLifecycleObserver {
|
||||
class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
private val manager = context.getSystemService<ConnectivityManager>()!!
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
|
@ -29,11 +25,11 @@ class NetworkObserver(
|
|||
|
||||
override fun onAvailable(network: Network) {
|
||||
activeList.add(network)
|
||||
callback(true)
|
||||
postValue(true)
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
activeList.remove(network)
|
||||
callback(!activeList.isEmpty())
|
||||
postValue(!activeList.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +41,7 @@ class NetworkObserver(
|
|||
}
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (context.isIdleMode()) {
|
||||
callback(false)
|
||||
postValue(false)
|
||||
} else {
|
||||
postCurrentState()
|
||||
}
|
||||
|
@ -67,13 +63,18 @@ class NetworkObserver(
|
|||
}
|
||||
|
||||
private fun postCurrentState() {
|
||||
callback(manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
|
||||
}
|
||||
|
||||
private fun postValue(b: Boolean) {
|
||||
Info.remote = Info.EMPTY_REMOTE
|
||||
Info.isConnected.postValue(b)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun observe(context: Context, callback: ConnectionCallback): NetworkObserver {
|
||||
return NetworkObserver(context, callback).apply { postCurrentState() }
|
||||
fun init(context: Context): NetworkObserver {
|
||||
return NetworkObserver(context).apply { postCurrentState() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LifecycleDispatcher;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
// Use Java to bypass Kotlin internal visibility modifier
|
||||
public class ProcessLifecycle {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init$lifecycle_process_release(context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
class RequestAuthentication: ActivityResultContract<Unit, Boolean>() {
|
||||
|
||||
override fun createIntent(context: Context, input: Unit) =
|
||||
context.getSystemService(KeyguardManager::class.java)
|
||||
.createConfirmDeviceCredentialIntent(null, null)
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?) =
|
||||
resultCode == Activity.RESULT_OK
|
||||
}
|
|
@ -13,6 +13,8 @@ import com.topjohnwu.magisk.core.ktx.rawResource
|
|||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.util.jar.JarFile
|
||||
|
||||
|
@ -34,7 +36,9 @@ class ShellInit : Shell.Initializer() {
|
|||
val bb = jar.getJarEntry("lib/${Const.CPU_ABI}/libbusybox.so")
|
||||
localBB = context.deviceProtectedContext.cachedFile("busybox")
|
||||
localBB.delete()
|
||||
jar.getInputStream(bb).writeTo(localBB)
|
||||
runBlocking {
|
||||
jar.getInputStream(bb).writeTo(localBB, dispatcher = Dispatchers.Unconfined)
|
||||
}
|
||||
localBB.setExecutable(true)
|
||||
} else {
|
||||
localBB = File(context.applicationInfo.nativeLibraryDir, "libbusybox.so")
|
||||
|
@ -73,17 +77,17 @@ class ShellInit : Shell.Initializer() {
|
|||
fun getVar(name: String) = fastCmd("echo \$$name")
|
||||
fun getBool(name: String) = getVar(name).toBoolean()
|
||||
|
||||
Info.isSAR = getBool("SYSTEM_ROOT")
|
||||
Info.isSAR = getBool("SYSTEM_AS_ROOT")
|
||||
Info.ramdisk = getBool("RAMDISKEXIST")
|
||||
Info.vbmeta = getBool("VBMETAEXIST")
|
||||
Info.isAB = getBool("ISAB")
|
||||
Info.crypto = getVar("CRYPTOTYPE")
|
||||
Info.patchBootVbmeta = getBool("PATCHVBMETAFLAG")
|
||||
Info.legacySAR = getBool("LEGACYSAR")
|
||||
|
||||
// Default presets
|
||||
Config.recovery = getBool("RECOVERYMODE")
|
||||
Config.keepVerity = getBool("KEEPVERITY")
|
||||
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
|
||||
Config.patchVbmeta = getBool("PATCHVBMETAFLAG")
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.topjohnwu.magisk.core.utils
|
||||
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
@ -7,14 +8,14 @@ import java.util.zip.ZipEntry
|
|||
import java.util.zip.ZipInputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
|
||||
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
|
||||
inputStream().buffered().use {
|
||||
it.unzip(folder, path, junkPath)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
|
||||
suspend fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
|
||||
try {
|
||||
val zin = ZipInputStream(this)
|
||||
var entry: ZipEntry
|
||||
|
@ -34,7 +35,7 @@ fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
|
|||
if (!it.exists())
|
||||
it.mkdirs()
|
||||
}
|
||||
dest.outputStream().use { out -> zin.copyTo(out) }
|
||||
dest.outputStream().use { out -> zin.copyAll(out) }
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw IOException(e)
|
||||
|
|
|
@ -8,7 +8,12 @@ import android.text.Spanned
|
|||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.cardview.widget.CardView
|
||||
|
@ -20,7 +25,11 @@ import androidx.databinding.BindingAdapter
|
|||
import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.topjohnwu.magisk.databinding
|
|||
import androidx.databinding.ListChangeRegistry
|
||||
import androidx.databinding.ObservableList
|
||||
import androidx.databinding.ObservableList.OnListChangedCallback
|
||||
import java.util.*
|
||||
import java.util.AbstractList
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.topjohnwu.magisk.R
|
|||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import java.io.File
|
||||
|
@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
|
|||
setCancelable(true)
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.install
|
||||
onClick { DownloadService.start(activity, Subject.App()) }
|
||||
onClick { DownloadEngine.startWithActivity(activity, Subject.App()) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
text = android.R.string.cancel
|
||||
|
|
|
@ -2,8 +2,7 @@ package com.topjohnwu.magisk.dialog
|
|||
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.Action
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
|
@ -22,9 +21,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
|
|||
dialog.apply {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
val action = if (install) Action.Flash else Action.Download
|
||||
val subject = Subject.Module(item, action)
|
||||
DownloadService.start(activity, subject)
|
||||
DownloadEngine.startWithActivity(activity, Subject.Module(item, install))
|
||||
}
|
||||
|
||||
val title = context.getString(R.string.repo_install_title,
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package com.topjohnwu.magisk.events
|
||||
|
||||
import com.topjohnwu.magisk.arch.ActivityExecutor
|
||||
import com.topjohnwu.magisk.arch.UIActivity
|
||||
import com.topjohnwu.magisk.arch.ViewEvent
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
|
||||
class BiometricEvent(
|
||||
builder: Builder.() -> Unit
|
||||
) : ViewEvent(), ActivityExecutor {
|
||||
|
||||
private var listenerOnFailure: () -> Unit = {}
|
||||
private var listenerOnSuccess: () -> Unit = {}
|
||||
|
||||
init {
|
||||
builder(Builder())
|
||||
}
|
||||
|
||||
override fun invoke(activity: UIActivity<*>) {
|
||||
BiometricHelper.authenticate(
|
||||
activity,
|
||||
onError = listenerOnFailure,
|
||||
onSuccess = listenerOnSuccess
|
||||
)
|
||||
}
|
||||
|
||||
inner class Builder internal constructor() {
|
||||
|
||||
fun onFailure(listener: () -> Unit) {
|
||||
listenerOnFailure = listener
|
||||
}
|
||||
|
||||
fun onSuccess(listener: () -> Unit) {
|
||||
listenerOnSuccess = listener
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -51,6 +51,16 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
class AuthEvent(
|
||||
private val callback: () -> Unit
|
||||
) : ViewEvent(), ActivityExecutor {
|
||||
|
||||
override fun invoke(activity: UIActivity<*>) {
|
||||
activity.authenticateCallback = { if (it) callback() }
|
||||
activity.requestAuthenticate.launch(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
class GetContentEvent(
|
||||
private val type: String,
|
||||
private val callback: ContentResultCallback
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
package com.topjohnwu.magisk.signing;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.ECPrivateKeySpec;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CryptoUtils {
|
||||
|
||||
static final Map<String, String> ID_TO_ALG;
|
||||
static final Map<String, String> ALG_TO_ID;
|
||||
|
||||
static {
|
||||
ID_TO_ALG = new HashMap<>();
|
||||
ALG_TO_ID = new HashMap<>();
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
|
||||
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
|
||||
ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
|
||||
ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
|
||||
ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
|
||||
ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
|
||||
ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
|
||||
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
|
||||
}
|
||||
|
||||
static String getSignatureAlgorithm(Key key) throws Exception {
|
||||
if ("EC".equals(key.getAlgorithm())) {
|
||||
int curveSize;
|
||||
KeyFactory factory = KeyFactory.getInstance("EC");
|
||||
if (key instanceof PublicKey) {
|
||||
ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else if (key instanceof PrivateKey) {
|
||||
ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
|
||||
curveSize = spec.getParams().getCurve().getField().getFieldSize();
|
||||
} else {
|
||||
throw new InvalidKeySpecException();
|
||||
}
|
||||
if (curveSize <= 256) {
|
||||
return "SHA256withECDSA";
|
||||
} else if (curveSize <= 384) {
|
||||
return "SHA384withECDSA";
|
||||
} else {
|
||||
return "SHA512withECDSA";
|
||||
}
|
||||
} else if ("RSA".equals(key.getAlgorithm())) {
|
||||
return "SHA256withRSA";
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
||||
static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
|
||||
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
|
||||
}
|
||||
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
|
||||
}
|
||||
|
||||
public static X509Certificate readCertificate(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) cf.generateCertificate(input);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Read a PKCS#8 format private key. */
|
||||
public static PrivateKey readPrivateKey(InputStream input)
|
||||
throws IOException, GeneralSecurityException {
|
||||
try {
|
||||
ByteArrayStream buf = new ByteArrayStream();
|
||||
buf.readFrom(input);
|
||||
byte[] bytes = buf.toByteArray();
|
||||
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
|
||||
/*
|
||||
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
|
||||
* OID and use that to construct a KeyFactory.
|
||||
*/
|
||||
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
|
||||
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
|
||||
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
|
||||
return KeyFactory.getInstance(algOid).generatePrivate(spec);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,382 +0,0 @@
|
|||
package com.topjohnwu.magisk.signing;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1InputStream;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Object;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.DERPrintableString;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SignBoot {
|
||||
|
||||
private static final int BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET = 1632;
|
||||
private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
|
||||
|
||||
// Arbitrary maximum header version value; when greater assume the field is dt/extra size
|
||||
private static final int BOOT_IMAGE_HEADER_VERSION_MAXIMUM = 8;
|
||||
|
||||
// Maximum header size byte value to read (currently the bootimg minimum page size)
|
||||
private static final int BOOT_IMAGE_HEADER_SIZE_MAXIMUM = 2048;
|
||||
|
||||
private static class PushBackRWStream extends FilterInputStream {
|
||||
private OutputStream out;
|
||||
private int pos = 0;
|
||||
private byte[] backBuf;
|
||||
|
||||
PushBackRWStream(InputStream in, OutputStream o) {
|
||||
super(in);
|
||||
out = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int b;
|
||||
if (backBuf != null && backBuf.length > pos) {
|
||||
b = backBuf[pos++];
|
||||
} else {
|
||||
b = super.read();
|
||||
out.write(b);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||
int read = 0;
|
||||
if (backBuf != null && backBuf.length > pos) {
|
||||
read = Math.min(len, backBuf.length - pos);
|
||||
System.arraycopy(backBuf, pos, bytes, off, read);
|
||||
pos += read;
|
||||
off += read;
|
||||
len -= read;
|
||||
}
|
||||
if (len > 0) {
|
||||
int ar = super.read(bytes, off, len);
|
||||
read += ar;
|
||||
out.write(bytes, off, ar);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
void unread(byte[] buf) {
|
||||
backBuf = buf;
|
||||
}
|
||||
}
|
||||
|
||||
private static int fullRead(InputStream in, byte[] b) throws IOException {
|
||||
return fullRead(in, b, 0, b.length);
|
||||
}
|
||||
|
||||
private static int fullRead(InputStream in, byte[] b, int off, int len) throws IOException {
|
||||
int n = 0;
|
||||
while (n < len) {
|
||||
int count = in.read(b, off + n, len - n);
|
||||
if (count <= 0)
|
||||
break;
|
||||
n += count;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public static boolean doSignature(
|
||||
@Nullable X509Certificate cert, @Nullable PrivateKey key,
|
||||
@NonNull InputStream imgIn, @NonNull OutputStream imgOut, @NonNull String target
|
||||
) {
|
||||
try {
|
||||
PushBackRWStream in = new PushBackRWStream(imgIn, imgOut);
|
||||
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
|
||||
// First read the header
|
||||
fullRead(in, hdr);
|
||||
int signableSize = getSignableImageSize(hdr);
|
||||
// Unread header
|
||||
in.unread(hdr);
|
||||
BootSignature bootsig = new BootSignature(target, signableSize);
|
||||
if (cert == null) {
|
||||
cert = CryptoUtils.readCertificate(
|
||||
new ByteArrayInputStream(KeyData.verityCert()));
|
||||
}
|
||||
bootsig.setCertificate(cert);
|
||||
if (key == null) {
|
||||
key = CryptoUtils.readPrivateKey(
|
||||
new ByteArrayInputStream(KeyData.verityKey()));
|
||||
}
|
||||
byte[] sig = bootsig.sign(key, in, signableSize);
|
||||
bootsig.setSignature(sig, CryptoUtils.getSignatureAlgorithmIdentifier(key));
|
||||
byte[] encoded_bootsig = bootsig.getEncoded();
|
||||
imgOut.write(encoded_bootsig);
|
||||
imgOut.flush();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verifySignature(InputStream imgIn, X509Certificate cert) {
|
||||
try {
|
||||
// Read the header for size
|
||||
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
|
||||
if (fullRead(imgIn, hdr) != hdr.length) {
|
||||
System.err.println("Unable to read image header");
|
||||
return false;
|
||||
}
|
||||
int signableSize = getSignableImageSize(hdr);
|
||||
|
||||
// Read the rest of the image
|
||||
byte[] rawImg = Arrays.copyOf(hdr, signableSize);
|
||||
int remain = signableSize - hdr.length;
|
||||
if (fullRead(imgIn, rawImg, hdr.length, remain) != remain) {
|
||||
System.err.println("Unable to read image");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read footer, which contains the signature
|
||||
byte[] signature = new byte[4096];
|
||||
if (imgIn.read(signature) == -1 || Arrays.equals(signature, new byte [signature.length])) {
|
||||
System.err.println("Invalid image: not signed");
|
||||
return false;
|
||||
}
|
||||
|
||||
BootSignature bootsig = new BootSignature(signature);
|
||||
if (cert != null) {
|
||||
bootsig.setCertificate(cert);
|
||||
}
|
||||
if (bootsig.verify(rawImg, signableSize)) {
|
||||
System.err.println("Signature is VALID");
|
||||
return true;
|
||||
} else {
|
||||
System.err.println("Signature is INVALID");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getSignableImageSize(byte[] data) throws Exception {
|
||||
if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
|
||||
"ANDROID!".getBytes("US-ASCII"))) {
|
||||
throw new IllegalArgumentException("Invalid image header: missing magic");
|
||||
}
|
||||
ByteBuffer image = ByteBuffer.wrap(data);
|
||||
image.order(ByteOrder.LITTLE_ENDIAN);
|
||||
image.getLong(); // magic
|
||||
int kernelSize = image.getInt();
|
||||
image.getInt(); // kernel_addr
|
||||
int ramdskSize = image.getInt();
|
||||
image.getInt(); // ramdisk_addr
|
||||
int secondSize = image.getInt();
|
||||
image.getLong(); // second_addr + tags_addr
|
||||
int pageSize = image.getInt();
|
||||
if (pageSize >= 0x02000000) {
|
||||
throw new IllegalArgumentException("Invalid image header: PXA header detected");
|
||||
}
|
||||
int length = pageSize // include the page aligned image header
|
||||
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
|
||||
+ ((secondSize + pageSize - 1) / pageSize) * pageSize;
|
||||
int headerVersion = image.getInt(); // boot image header version or dt/extra size
|
||||
if (headerVersion > 0 && headerVersion < BOOT_IMAGE_HEADER_VERSION_MAXIMUM) {
|
||||
image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
|
||||
int recoveryDtboLength = image.getInt();
|
||||
length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
|
||||
image.getLong(); // recovery_dtbo address
|
||||
int headerSize = image.getInt();
|
||||
if (headerVersion == 2) {
|
||||
image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
|
||||
int dtbLength = image.getInt();
|
||||
length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
|
||||
image.getLong(); // dtb address
|
||||
}
|
||||
if (image.position() != headerSize) {
|
||||
throw new IllegalArgumentException("Invalid image header: invalid header length");
|
||||
}
|
||||
} else {
|
||||
// headerVersion is 0 or actually dt/extra size in this case
|
||||
length += ((headerVersion + pageSize - 1) / pageSize) * pageSize;
|
||||
}
|
||||
length = ((length + pageSize - 1) / pageSize) * pageSize;
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException("Invalid image header: invalid length");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static class BootSignature extends ASN1Object {
|
||||
private ASN1Integer formatVersion;
|
||||
private ASN1Encodable certificate;
|
||||
private AlgorithmIdentifier algId;
|
||||
private DERPrintableString target;
|
||||
private ASN1Integer length;
|
||||
private DEROctetString signature;
|
||||
private PublicKey publicKey;
|
||||
private static final int FORMAT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Initializes the object for signing an image file
|
||||
* @param target Target name, included in the signed data
|
||||
* @param length Length of the image, included in the signed data
|
||||
*/
|
||||
public BootSignature(String target, int length) {
|
||||
this.formatVersion = new ASN1Integer(FORMAT_VERSION);
|
||||
this.target = new DERPrintableString(target);
|
||||
this.length = new ASN1Integer(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the object for verifying a signed image file
|
||||
* @param signature Signature footer
|
||||
*/
|
||||
public BootSignature(byte[] signature) throws Exception {
|
||||
ASN1InputStream stream = new ASN1InputStream(signature);
|
||||
ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
|
||||
formatVersion = (ASN1Integer) sequence.getObjectAt(0);
|
||||
if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
|
||||
throw new IllegalArgumentException("Unsupported format version");
|
||||
}
|
||||
certificate = sequence.getObjectAt(1);
|
||||
byte[] encoded = ((ASN1Object) certificate).getEncoded();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
|
||||
publicKey = c.getPublicKey();
|
||||
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
|
||||
this.algId = new AlgorithmIdentifier((ASN1ObjectIdentifier) algId.getObjectAt(0));
|
||||
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
|
||||
target = (DERPrintableString) attrs.getObjectAt(0);
|
||||
length = (ASN1Integer) attrs.getObjectAt(1);
|
||||
this.signature = (DEROctetString) sequence.getObjectAt(4);
|
||||
}
|
||||
|
||||
public ASN1Object getAuthenticatedAttributes() {
|
||||
ASN1EncodableVector attrs = new ASN1EncodableVector();
|
||||
attrs.add(target);
|
||||
attrs.add(length);
|
||||
return new DERSequence(attrs);
|
||||
}
|
||||
|
||||
public byte[] getEncodedAuthenticatedAttributes() throws IOException {
|
||||
return getAuthenticatedAttributes().getEncoded();
|
||||
}
|
||||
|
||||
public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
|
||||
this.algId = algId;
|
||||
signature = new DEROctetString(sig);
|
||||
}
|
||||
|
||||
public void setCertificate(X509Certificate cert)
|
||||
throws CertificateEncodingException, IOException {
|
||||
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
|
||||
certificate = s.readObject();
|
||||
publicKey = cert.getPublicKey();
|
||||
}
|
||||
|
||||
public byte[] sign(PrivateKey key, InputStream is, int len) throws Exception {
|
||||
Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
|
||||
signer.initSign(key);
|
||||
int read;
|
||||
byte buffer[] = new byte[4096];
|
||||
while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {
|
||||
signer.update(buffer, 0, read);
|
||||
len -= read;
|
||||
}
|
||||
signer.update(getEncodedAuthenticatedAttributes());
|
||||
return signer.sign();
|
||||
}
|
||||
|
||||
public boolean verify(byte[] image, int length) throws Exception {
|
||||
if (this.length.getValue().intValue() != length) {
|
||||
throw new IllegalArgumentException("Invalid image length");
|
||||
}
|
||||
String algName = CryptoUtils.ID_TO_ALG.get(algId.getAlgorithm().getId());
|
||||
if (algName == null) {
|
||||
throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
|
||||
}
|
||||
Signature verifier = Signature.getInstance(algName);
|
||||
verifier.initVerify(publicKey);
|
||||
verifier.update(image, 0, length);
|
||||
verifier.update(getEncodedAuthenticatedAttributes());
|
||||
return verifier.verify(signature.getOctets());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASN1Primitive toASN1Primitive() {
|
||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||
v.add(formatVersion);
|
||||
v.add(certificate);
|
||||
v.add(algId);
|
||||
v.add(getAuthenticatedAttributes());
|
||||
v.add(signature);
|
||||
return new DERSequence(v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0 && "-verify".equals(args[0])) {
|
||||
X509Certificate cert = null;
|
||||
if (args.length >= 2) {
|
||||
// args[1] is the path to a public key certificate
|
||||
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
|
||||
}
|
||||
boolean signed = SignBoot.verifySignature(System.in, cert);
|
||||
System.exit(signed ? 0 : 1);
|
||||
} else if (args.length > 0 && "-sign".equals(args[0])) {
|
||||
X509Certificate cert = null;
|
||||
PrivateKey key = null;
|
||||
String name = "/boot";
|
||||
|
||||
if (args.length >= 3) {
|
||||
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
|
||||
key = CryptoUtils.readPrivateKey(new FileInputStream(args[2]));
|
||||
}
|
||||
if (args.length == 2) {
|
||||
name = args[1];
|
||||
} else if (args.length >= 4) {
|
||||
name = args[3];
|
||||
}
|
||||
|
||||
boolean result = SignBoot.doSignature(cert, key, System.in, System.out, name);
|
||||
System.exit(result ? 0 : 1);
|
||||
} else {
|
||||
System.err.println(
|
||||
"BootSigner <actions> [args]\n" +
|
||||
"Input from stdin, output to stdout\n" +
|
||||
"\n" +
|
||||
"Actions:\n" +
|
||||
" -verify [x509.pem]\n" +
|
||||
" verify image. cert is optional.\n" +
|
||||
" -sign [x509.pem] [pk8] [name]\n" +
|
||||
" sign image. name and the cert/key pair are optional.\n" +
|
||||
" name should be either /boot (default) or /recovery.\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,6 @@ import androidx.core.content.pm.ShortcutManagerCompat
|
|||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
|
@ -24,13 +23,10 @@ import com.topjohnwu.magisk.core.Const
|
|||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class MainViewModel : BaseViewModel()
|
||||
|
@ -62,7 +58,6 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
|
|||
setContentView()
|
||||
showUnsupportedMessage()
|
||||
askForHomeShortcut()
|
||||
checkStubComponent()
|
||||
|
||||
// Ask permission to post notifications for background update check
|
||||
if (Config.checkUpdate) {
|
||||
|
@ -231,22 +226,4 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
|
|||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun checkStubComponent() {
|
||||
if (intent.component?.className?.contains(HideAPK.PLACEHOLDER) == true) {
|
||||
// The stub APK was not properly patched, re-apply our changes
|
||||
withPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES) { granted ->
|
||||
if (granted) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val apk = File(applicationInfo.sourceDir)
|
||||
HideAPK.upgrade(this@MainActivity, apk)?.let {
|
||||
startActivity(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,23 +8,30 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.JobService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@SuppressLint("CustomSplashScreen")
|
||||
abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
|
||||
|
@ -58,7 +65,7 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
|
|||
showInvalidStateMessage()
|
||||
return@getShell
|
||||
}
|
||||
preLoad(savedInstanceState)
|
||||
initialize(savedInstanceState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +109,7 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
|
|||
}
|
||||
}
|
||||
|
||||
private fun preLoad(savedState: Bundle?) {
|
||||
private fun initialize(savedState: Bundle?) {
|
||||
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)?.let {
|
||||
// Make sure the calling package matches (prevent DoS)
|
||||
if (it == realCallingPackage)
|
||||
|
@ -112,7 +119,21 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
|
|||
}
|
||||
|
||||
Config.load(prevPkg)
|
||||
handleRepackage(prevPkg)
|
||||
|
||||
if (packageName != APPLICATION_ID) {
|
||||
runCatching {
|
||||
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
|
||||
packageManager.getApplicationInfo(APPLICATION_ID, 0)
|
||||
Shell.cmd("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
|
||||
}
|
||||
} else {
|
||||
if (Config.suManager.isNotEmpty())
|
||||
Config.suManager = ""
|
||||
if (prevPkg != null) {
|
||||
Shell.cmd("(pm uninstall $prevPkg)& >/dev/null 2>&1").exec()
|
||||
}
|
||||
}
|
||||
|
||||
if (prevPkg != null) {
|
||||
runOnUiThread {
|
||||
// Relaunch the process after package migration
|
||||
|
@ -121,6 +142,31 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
|
|||
return
|
||||
}
|
||||
|
||||
// Validate stub APK
|
||||
if (isRunningAsStub && (
|
||||
// Version mismatch
|
||||
Info.stub!!.version != BuildConfig.STUB_VERSION ||
|
||||
// Not properly patched
|
||||
intent.component!!.className.contains(HideAPK.PLACEHOLDER)
|
||||
)) {
|
||||
withPermission(REQUEST_INSTALL_PACKAGES) { granted ->
|
||||
if (granted) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val apk = File(cacheDir, "stub.apk")
|
||||
try {
|
||||
assets.open("stub.apk").writeTo(apk)
|
||||
HideAPK.upgrade(this@SplashActivity, apk)?.let {
|
||||
startActivity(it)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
JobService.schedule(this)
|
||||
Shortcuts.setupDynamic(this)
|
||||
|
||||
|
@ -144,19 +190,4 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRepackage(pkg: String?) {
|
||||
if (packageName != APPLICATION_ID) {
|
||||
runCatching {
|
||||
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
|
||||
packageManager.getApplicationInfo(APPLICATION_ID, 0)
|
||||
Shell.cmd("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
|
||||
}
|
||||
} else {
|
||||
if (Config.suManager.isNotEmpty())
|
||||
Config.suManager = ""
|
||||
pkg ?: return
|
||||
Shell.cmd("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,12 @@ import android.annotation.SuppressLint
|
|||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.ComponentInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.*
|
||||
import android.content.pm.PackageManager.GET_ACTIVITIES
|
||||
import android.content.pm.PackageManager.GET_PROVIDERS
|
||||
import android.content.pm.PackageManager.GET_RECEIVERS
|
||||
import android.content.pm.PackageManager.GET_SERVICES
|
||||
import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
|
||||
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
|
@ -12,7 +17,7 @@ import android.os.Build.VERSION.SDK_INT
|
|||
import androidx.core.os.ProcessCompat
|
||||
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import java.util.*
|
||||
import java.util.TreeSet
|
||||
|
||||
class CmdlineListItem(line: String) {
|
||||
val packageName: String
|
||||
|
|
|
@ -5,7 +5,11 @@ import android.content.Context
|
|||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
|
|
|
@ -5,7 +5,7 @@ import androidx.databinding.Bindable
|
|||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
|
@ -34,7 +34,7 @@ class FlashViewModel : BaseViewModel() {
|
|||
|
||||
private val _state = MutableLiveData(State.FLASHING)
|
||||
val state: LiveData<State> get() = _state
|
||||
val flashing = Transformations.map(state) { it == State.FLASHING }
|
||||
val flashing = state.map { it == State.FLASHING }
|
||||
|
||||
@get:Bindable
|
||||
var showReboot = Info.isRooted
|
||||
|
@ -72,6 +72,7 @@ class FlashViewModel : BaseViewModel() {
|
|||
MagiskInstaller.Direct(outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.FLASH_INACTIVE_SLOT -> {
|
||||
showReboot = false
|
||||
MagiskInstaller.SecondSlot(outItems, logItems).exec()
|
||||
}
|
||||
Const.Value.PATCH_FILE -> {
|
||||
|
|
|
@ -24,6 +24,10 @@ private interface RikkaImpl : Dev {
|
|||
override val name get() = "RikkaW"
|
||||
}
|
||||
|
||||
private interface CanyieImpl : Dev {
|
||||
override val name get() = "canyie"
|
||||
}
|
||||
|
||||
sealed class DeveloperItem : Dev {
|
||||
|
||||
abstract val items: List<IconLink>
|
||||
|
@ -61,6 +65,14 @@ sealed class DeveloperItem : Dev {
|
|||
object : IconLink.Github.User(), RikkaImpl {}
|
||||
)
|
||||
}
|
||||
|
||||
object Canyie : DeveloperItem(), CanyieImpl {
|
||||
override val items =
|
||||
listOf<IconLink>(
|
||||
object : IconLink.Twitter() { override val name = "canyie2977" },
|
||||
object : IconLink.Github.User(), CanyieImpl {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class IconLink : RvItem() {
|
||||
|
|
|
@ -14,7 +14,7 @@ import com.topjohnwu.magisk.R
|
|||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
||||
|
||||
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||
|
@ -25,7 +25,7 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.setTitle(R.string.section_home)
|
||||
DownloadService.observeProgress(this, viewModel::onProgressUpdate)
|
||||
DownloadEngine.observeProgress(this, viewModel::onProgressUpdate)
|
||||
}
|
||||
|
||||
private fun checkTitle(text: TextView, icon: ImageView) {
|
||||
|
|
|
@ -7,6 +7,8 @@ import android.view.MenuItem
|
|||
import android.widget.PopupMenu
|
||||
import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.core.ktx.reboot as systemReboot
|
||||
|
||||
|
@ -20,6 +22,11 @@ object RebootMenu {
|
|||
R.id.action_reboot_download -> systemReboot("download")
|
||||
R.id.action_reboot_edl -> systemReboot("edl")
|
||||
R.id.action_reboot_recovery -> systemReboot("recovery")
|
||||
R.id.action_reboot_safe_mode -> {
|
||||
val status = !item.isChecked
|
||||
item.isChecked = status
|
||||
Config.bootloop = if (status) 2 else 0
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
return true
|
||||
|
@ -29,10 +36,16 @@ object RebootMenu {
|
|||
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
|
||||
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
|
||||
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
||||
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
|
||||
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
||||
menu.setOnMenuItemClickListener(RebootMenu::reboot)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
||||
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
|
||||
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
||||
}
|
||||
if (Const.Version.isCanary()) {
|
||||
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
|
||||
} else {
|
||||
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false
|
||||
}
|
||||
return menu
|
||||
}
|
||||
|
||||
|
|
|
@ -37,8 +37,7 @@ import java.io.IOException
|
|||
class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() {
|
||||
|
||||
val isRooted get() = Info.isRooted
|
||||
val hideVbmeta = Info.vbmeta || Info.isSamsung || Info.isAB
|
||||
val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && hideVbmeta && Info.ramdisk)
|
||||
val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk)
|
||||
val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator
|
||||
|
||||
@get:Bindable
|
||||
|
@ -105,7 +104,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
|||
step,
|
||||
Config.keepVerity,
|
||||
Config.keepEnc,
|
||||
Config.patchVbmeta,
|
||||
Config.recovery
|
||||
))
|
||||
}
|
||||
|
@ -116,7 +114,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
|||
step = it.step
|
||||
Config.keepVerity = it.keepVerity
|
||||
Config.keepEnc = it.keepEnc
|
||||
Config.patchVbmeta = it.patchVbmeta
|
||||
Config.recovery = it.recovery
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +134,6 @@ class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel()
|
|||
val step: Int,
|
||||
val keepVerity: Boolean,
|
||||
val keepEnc: Boolean,
|
||||
val patchVbmeta: Boolean,
|
||||
val recovery: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.log
|
|||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.ktx.timeDateFormat
|
||||
import com.topjohnwu.magisk.core.ktx.toTime
|
||||
import com.topjohnwu.magisk.core.model.su.SuLog
|
||||
|
@ -14,7 +15,7 @@ class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
|
|||
|
||||
override val layoutRes = R.layout.item_log_access_md2
|
||||
|
||||
val date = log.time.toTime(timeDateFormat)
|
||||
val info = genInfo()
|
||||
|
||||
@get:Bindable
|
||||
var isTop = false
|
||||
|
@ -25,4 +26,28 @@ class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
|
|||
set(value) = set(value, field, { field = it }, BR.bottom)
|
||||
|
||||
override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName
|
||||
|
||||
private fun genInfo(): String {
|
||||
val res = AppContext.resources
|
||||
val sb = StringBuilder()
|
||||
val date = log.time.toTime(timeDateFormat)
|
||||
val toUid = res.getString(R.string.target_uid, log.toUid)
|
||||
val fromPid = res.getString(R.string.pid, log.fromPid)
|
||||
sb.append("$date\n$toUid $fromPid")
|
||||
if (log.target != -1) {
|
||||
val pid = if (log.target == 0) "magiskd" else log.target.toString()
|
||||
val target = res.getString(R.string.target_pid, pid)
|
||||
sb.append(" $target")
|
||||
}
|
||||
if (log.context.isNotEmpty()) {
|
||||
val context = res.getString(R.string.selinux_context, log.context)
|
||||
sb.append("\n$context")
|
||||
}
|
||||
if (log.gids.isNotEmpty()) {
|
||||
val gids = res.getString(R.string.supp_group, log.gids)
|
||||
sb.append("\n$gids")
|
||||
}
|
||||
sb.append("\n${log.command}")
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import com.topjohnwu.magisk.core.Const
|
|||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.activity
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.availableLocales
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
|
@ -228,25 +227,14 @@ object Zygisk : BaseSettingsItem.Toggle() {
|
|||
get() = Config.zygisk
|
||||
set(value) {
|
||||
Config.zygisk = value
|
||||
DenyList.isEnabled = value
|
||||
DenyListConfig.isEnabled = value
|
||||
notifyPropertyChanged(BR.description)
|
||||
DenyList.notifyPropertyChanged(BR.description)
|
||||
}
|
||||
val mismatch get() = value != Info.isZygiskEnabled
|
||||
}
|
||||
|
||||
object DenyList : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_denylist_title.asText()
|
||||
override val description get() =
|
||||
if (isEnabled) {
|
||||
if (Zygisk.mismatch)
|
||||
R.string.reboot_apply_change.asText()
|
||||
else
|
||||
R.string.settings_denylist_summary.asText()
|
||||
} else {
|
||||
R.string.settings_denylist_error.asText(R.string.zygisk.asText())
|
||||
}
|
||||
override val description get() = R.string.settings_denylist_summary.asText()
|
||||
|
||||
override var value = Config.denyList
|
||||
set(value) {
|
||||
|
@ -261,18 +249,11 @@ object DenyList : BaseSettingsItem.Toggle() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = Zygisk.value
|
||||
}
|
||||
}
|
||||
|
||||
object DenyListConfig : BaseSettingsItem.Blank() {
|
||||
override val title = R.string.settings_denylist_config_title.asText()
|
||||
override val description = R.string.settings_denylist_config_summary.asText()
|
||||
override fun refresh() {
|
||||
isEnabled = Zygisk.value
|
||||
}
|
||||
}
|
||||
|
||||
// --- Superuser
|
||||
|
@ -283,17 +264,15 @@ object Tapjack : BaseSettingsItem.Toggle() {
|
|||
override var value by Config::suTapjack
|
||||
}
|
||||
|
||||
object Biometrics : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_biometric_title.asText()
|
||||
override var description = R.string.settings_su_biometric_summary.asText()
|
||||
override var value by Config::suBiometric
|
||||
object Authentication : BaseSettingsItem.Toggle() {
|
||||
override val title = R.string.settings_su_auth_title.asText()
|
||||
override var description = R.string.settings_su_auth_summary.asText()
|
||||
override var value by Config::userAuth
|
||||
|
||||
override fun refresh() {
|
||||
isEnabled = BiometricHelper.isSupported
|
||||
isEnabled = Info.isDeviceSecure
|
||||
if (!isEnabled) {
|
||||
value = false
|
||||
description = R.string.no_biometric.asText()
|
||||
notifyPropertyChanged(BR.checked)
|
||||
description = R.string.settings_su_auth_insecure.asText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import com.topjohnwu.magisk.core.ktx.toast
|
|||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.databinding.bindExtra
|
||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||
import com.topjohnwu.magisk.events.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.AuthEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -72,7 +72,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||
if (Info.showSuperUser) {
|
||||
list.addAll(listOf(
|
||||
Superuser,
|
||||
Tapjack, Biometrics, AccessMode, MultiuserMode, MountNamespaceMode,
|
||||
Tapjack, Authentication, AccessMode, MultiuserMode, MountNamespaceMode,
|
||||
AutomaticResponse, RequestTimeout, SUNotification
|
||||
))
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
|
@ -92,7 +92,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||
when (item) {
|
||||
DownloadPath -> withExternalRW(andThen)
|
||||
UpdateChecker -> withPostNotificationPermission(andThen)
|
||||
Biometrics -> authenticate(andThen)
|
||||
Authentication -> AuthEvent(andThen).publish()
|
||||
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
||||
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
|
||||
SystemlessHosts -> createHosts()
|
||||
|
@ -119,13 +119,6 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
private fun authenticate(callback: () -> Unit) {
|
||||
BiometricEvent {
|
||||
// allow the change on success
|
||||
onSuccess { callback() }
|
||||
}.publish()
|
||||
}
|
||||
|
||||
private fun createHosts() {
|
||||
Shell.cmd("add_hosts_module").submit {
|
||||
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||
|
|
|
@ -73,6 +73,8 @@ class PolicyRvItem(
|
|||
viewModel.deletePressed(this)
|
||||
}
|
||||
|
||||
override fun itemSameAs(other: PolicyRvItem) = item.uid == other.item.uid
|
||||
override fun itemSameAs(other: PolicyRvItem) = packageName == other.packageName
|
||||
|
||||
override fun contentSameAs(other: PolicyRvItem) = item.policy == other.item.policy
|
||||
|
||||
}
|
||||
|
|
|
@ -10,16 +10,20 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.databinding.*
|
||||
import com.topjohnwu.magisk.databinding.MergeObservableList
|
||||
import com.topjohnwu.magisk.databinding.RvItem
|
||||
import com.topjohnwu.magisk.databinding.bindExtra
|
||||
import com.topjohnwu.magisk.databinding.diffList
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog
|
||||
import com.topjohnwu.magisk.events.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.AuthEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.view.TextItem
|
||||
|
@ -106,17 +110,15 @@ class SuperuserViewModel(
|
|||
fun updateState() = viewModelScope.launch {
|
||||
db.delete(item.item.uid)
|
||||
val list = ArrayList(itemsPolicies)
|
||||
list.removeAll { it.itemSameAs(item) }
|
||||
list.removeAll { it.item.uid == item.item.uid }
|
||||
itemsPolicies.update(list)
|
||||
if (list.isEmpty() && itemsHelpers.isEmpty()) {
|
||||
itemsHelpers.add(itemNoData)
|
||||
}
|
||||
}
|
||||
|
||||
if (BiometricHelper.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess { updateState() }
|
||||
}.publish()
|
||||
if (Config.userAuth) {
|
||||
AuthEvent { updateState() }.publish()
|
||||
} else {
|
||||
SuperuserRevokeDialog(item.title) { updateState() }.show()
|
||||
}
|
||||
|
@ -155,24 +157,21 @@ class SuperuserViewModel(
|
|||
}
|
||||
|
||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
|
||||
fun updateState() {
|
||||
viewModelScope.launch {
|
||||
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||
db.update(item.item)
|
||||
itemsPolicies.forEach {
|
||||
if (it.item.uid == item.item.uid) {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
items.forEach {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
}
|
||||
|
||||
if (BiometricHelper.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess { updateState() }
|
||||
}.publish()
|
||||
if (Config.userAuth) {
|
||||
AuthEvent { updateState() }.publish()
|
||||
} else {
|
||||
updateState()
|
||||
}
|
||||
|
|
|
@ -27,12 +27,12 @@ import com.topjohnwu.magisk.core.ktx.toast
|
|||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.events.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.AuthEvent
|
||||
import com.topjohnwu.magisk.events.DieEvent
|
||||
import com.topjohnwu.magisk.events.ShowUIEvent
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
|
||||
|
@ -70,17 +70,14 @@ class SuRequestViewModel(
|
|||
}
|
||||
|
||||
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
|
||||
private lateinit var timer: CountDownTimer
|
||||
private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
private var timer = SuTimer(millis, 1000)
|
||||
private var initialized = false
|
||||
|
||||
fun grantPressed() {
|
||||
cancelTimer()
|
||||
if (BiometricHelper.isEnabled) {
|
||||
BiometricEvent {
|
||||
onSuccess {
|
||||
respond(ALLOW)
|
||||
}
|
||||
}.publish()
|
||||
if (Config.userAuth) {
|
||||
AuthEvent { respond(ALLOW) }.publish()
|
||||
} else {
|
||||
respond(ALLOW)
|
||||
}
|
||||
|
@ -96,7 +93,7 @@ class SuRequestViewModel(
|
|||
}
|
||||
|
||||
fun handleRequest(intent: Intent) {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
if (handler.start(intent))
|
||||
showDialog()
|
||||
else
|
||||
|
@ -125,8 +122,7 @@ class SuRequestViewModel(
|
|||
selectedItemPosition = timeoutPrefs.getInt(packageName, 0)
|
||||
|
||||
// Set timer
|
||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
timer = SuTimer(millis, 1000).apply { start() }
|
||||
timer.start()
|
||||
|
||||
// Actually show the UI
|
||||
ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()
|
||||
|
|
|
@ -11,7 +11,7 @@ import androidx.core.content.getSystemService
|
|||
import androidx.core.graphics.drawable.toIcon
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.AppContext
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.ktx.getBitmap
|
||||
import com.topjohnwu.magisk.core.ktx.selfLaunchIntent
|
||||
|
@ -67,7 +67,7 @@ object Notifications {
|
|||
|
||||
fun updateAvailable() {
|
||||
AppContext.apply {
|
||||
val intent = DownloadService.getPendingIntent(this, Subject.App())
|
||||
val intent = DownloadEngine.getPendingIntent(this, Subject.App())
|
||||
val bitmap = getBitmap(R.drawable.ic_magisk_outline)
|
||||
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Notification.Builder(this, UPDATE_CHANNEL)
|
||||
|
|
|
@ -31,7 +31,7 @@ object Shortcuts {
|
|||
val info = ShortcutInfoCompat.Builder(context, Const.Nav.HOME)
|
||||
.setShortLabel(context.getString(R.string.magisk))
|
||||
.setIntent(intent)
|
||||
.setIcon(IconCompat.createFromIcon(context, context.getIcon(R.drawable.ic_launcher)))
|
||||
.setIcon(context.getIconCompat(R.drawable.ic_launcher))
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(context, info, null)
|
||||
}
|
||||
|
@ -47,6 +47,18 @@ object Shortcuts {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getIconCompat(id: Int): IconCompat {
|
||||
return if (isRunningAsStub) {
|
||||
val bitmap = getBitmap(id)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
IconCompat.createWithAdaptiveBitmap(bitmap)
|
||||
else
|
||||
IconCompat.createWithBitmap(bitmap)
|
||||
} else {
|
||||
IconCompat.createWithResource(this, id)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = 25)
|
||||
private fun getShortCuts(context: Context): List<ShortcutInfo> {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
|
|
|
@ -242,6 +242,14 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l_50" />
|
||||
|
||||
<include
|
||||
item="@{DeveloperItem.Canyie.INSTANCE}"
|
||||
layout="@layout/item_developer"
|
||||
viewModel="@{viewModel}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/l_50" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
@ -108,15 +108,6 @@
|
|||
android:text="@string/keep_force_encryption"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{viewModel.hideVbmeta}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={Config.patchVbmeta}"
|
||||
android:text="@string/patch_vbmeta"
|
||||
app:tint="?colorPrimary" />
|
||||
|
||||
<CheckBox
|
||||
style="@style/WidgetFoundation.Checkbox"
|
||||
gone="@{Info.ramdisk}"
|
||||
|
|
|
@ -29,8 +29,13 @@
|
|||
tools:listitem="@layout/item_log_access_md2"
|
||||
tools:paddingTop="24dp" />
|
||||
|
||||
<ProgressBar
|
||||
style="@style/WidgetFoundation.ProgressBar.Indeterminate"
|
||||
goneUnless="@{viewModel.loading}"
|
||||
android:layout_marginTop="@dimen/l1" />
|
||||
|
||||
<FrameLayout
|
||||
gone="@{!viewModel.items.empty}"
|
||||
gone="@{viewModel.loading || !viewModel.items.empty}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/log_track_container"
|
||||
bullet="@{item.log.action ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
||||
bullet="@{item.log.action == 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
||||
isBottom="@{item.isBottom}"
|
||||
isSelected="@{!item.log.action}"
|
||||
isSelected="@{item.log.action != 2}"
|
||||
isTop="@{item.isTop}"
|
||||
layout="@layout/item_log_track_md2"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -54,48 +54,22 @@
|
|||
android:text="@{item.log.appName}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_date"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_info"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/magisk" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_date"
|
||||
android:id="@+id/log_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.date}"
|
||||
android:text="@{item.info}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toTopOf="@+id/log_app_details"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_app_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="06:00 PM, 10 Oct 2019" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_app_details"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{String.format(`%s %s`, @string/pid(item.log.fromPid), @string/target_uid(item.log.toUid))}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toTopOf="@id/log_command"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_date"
|
||||
tools:text="PID: 7196 Target UID: 0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_command"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:text="@{item.log.command}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_app_details"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:text="/system/bin/sh" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -24,11 +24,15 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/module"
|
||||
style="@style/WidgetFoundation.Card"
|
||||
isEnabled="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:focusable="@{!item.removed && item.enabled && !item.showNotice}"
|
||||
android:nextFocusRight="@id/module_indicator"
|
||||
android:onClick="@{() -> item.setEnabled(!item.enabled)}"
|
||||
app:cardBackgroundColor="@color/color_card_background_color_selector"
|
||||
tools:isEnabled="false"
|
||||
tools:layout_gravity="center"
|
||||
|
@ -95,6 +99,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/l_50"
|
||||
android:checked="@={item.enabled}"
|
||||
android:nextFocusLeft="@id/module"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_version_author"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
android:layout_gravity="center">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/policy"
|
||||
style="@style/WidgetFoundation.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="@{item.enabled ? 1f : .5f}"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/policy_indicator"
|
||||
android:onClick="@{() -> item.toggleExpand()}">
|
||||
|
||||
<LinearLayout
|
||||
|
@ -35,10 +38,10 @@
|
|||
<ImageView
|
||||
android:id="@+id/policy_app_icon"
|
||||
style="@style/WidgetFoundation.Image"
|
||||
srcCompat="@{item.icon}"
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:layout_marginTop="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l1"
|
||||
srcCompat="@{item.icon}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -86,8 +89,9 @@
|
|||
android:id="@+id/policy_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={item.enabled}"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:checked="@={item.enabled}"
|
||||
android:nextFocusLeft="@id/policy"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -96,20 +100,20 @@
|
|||
|
||||
<LinearLayout
|
||||
android:id="@+id/policy_expand_container"
|
||||
android:orientation="horizontal"
|
||||
gone="@{!item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?colorSurfaceVariant"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
<Button
|
||||
android:id="@+id/policy_notify"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
isSelected="@{item.shouldNotify}"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.toggleNotify()}"
|
||||
android:text="@string/superuser_toggle_notification"
|
||||
|
@ -131,9 +135,9 @@
|
|||
android:id="@+id/policy_log"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
isSelected="@{item.shouldLog}"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.toggleLog()}"
|
||||
android:text="@string/logs"
|
||||
|
@ -154,9 +158,9 @@
|
|||
<Button
|
||||
android:id="@+id/policy_delete"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minHeight="24dp"
|
||||
android:onClick="@{() -> item.revoke()}"
|
||||
android:text="@string/superuser_toggle_revoke"
|
||||
|
|
|
@ -26,4 +26,9 @@
|
|||
android:id="@+id/action_reboot_edl"
|
||||
android:title="@string/reboot_edl" />
|
||||
|
||||
</menu>
|
||||
<item
|
||||
android:id="@+id/action_reboot_safe_mode"
|
||||
android:checkable="true"
|
||||
android:title="@string/reboot_safe_mode" />
|
||||
|
||||
</menu>
|
||||
|
|
|
@ -14,7 +14,7 @@ env_check() {
|
|||
[ -f "$MAGISKBIN/magiskpolicy" ] || return 1
|
||||
fi
|
||||
if [ "$2" -ge 25210 ]; then
|
||||
[ -b "$MAGISKTMP/.magisk/block/preinit" ] || return 2
|
||||
[ -b "$MAGISKTMP/.magisk/device/preinit" ] || [ -b "$MAGISKTMP/.magisk/block/preinit" ] || return 2
|
||||
fi
|
||||
grep -xqF "MAGISK_VER='$1'" "$MAGISKBIN/util_functions.sh" || return 3
|
||||
grep -xqF "MAGISK_VER_CODE=$2" "$MAGISKBIN/util_functions.sh" || return 3
|
||||
|
@ -154,7 +154,7 @@ check_boot_ramdisk() {
|
|||
$ISAB && return 0
|
||||
|
||||
# If we are using legacy SAR, but not A/B, assume we do not have ramdisk
|
||||
if grep ' / ' /proc/mounts | grep -q '/dev/root'; then
|
||||
if $LEGACYSAR; then
|
||||
# Override recovery mode to true
|
||||
RECOVERYMODE=true
|
||||
return 1
|
||||
|
@ -193,24 +193,25 @@ check_encryption() {
|
|||
mount_partitions() {
|
||||
[ "$(getprop ro.build.ab_update)" = "true" ] && SLOT=$(getprop ro.boot.slot_suffix)
|
||||
# Check whether non rootfs root dir exists
|
||||
SYSTEM_ROOT=false
|
||||
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_ROOT=true
|
||||
SYSTEM_AS_ROOT=false
|
||||
grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_AS_ROOT=true
|
||||
|
||||
LEGACYSAR=false
|
||||
grep ' / ' /proc/mounts | grep -q '/dev/root' && LEGACYSAR=true
|
||||
}
|
||||
|
||||
get_flags() {
|
||||
KEEPVERITY=$SYSTEM_ROOT
|
||||
KEEPVERITY=$SYSTEM_AS_ROOT
|
||||
ISENCRYPTED=false
|
||||
[ "$(getprop ro.crypto.state)" = "encrypted" ] && ISENCRYPTED=true
|
||||
KEEPFORCEENCRYPT=$ISENCRYPTED
|
||||
# Although this most certainly won't work without root, keep it just in case
|
||||
if [ -e /dev/block/by-name/vbmeta_a ] || [ -e /dev/block/by-name/vbmeta ]; then
|
||||
VBMETAEXIST=true
|
||||
if [ -n "$(getprop ro.boot.vbmeta.device)" -o -n "$(getprop ro.boot.vbmeta.size)" ]; then
|
||||
PATCHVBMETAFLAG=false
|
||||
elif getprop ro.product.ab_ota_partitions | grep -wq vbmeta; then
|
||||
PATCHVBMETAFLAG=false
|
||||
else
|
||||
VBMETAEXIST=false
|
||||
PATCHVBMETAFLAG=true
|
||||
fi
|
||||
# Preset PATCHVBMETAFLAG to false in the non-root case
|
||||
PATCHVBMETAFLAG=false
|
||||
# Make sure RECOVERYMODE has value
|
||||
[ -z $RECOVERYMODE ] && RECOVERYMODE=false
|
||||
}
|
||||
|
||||
|
|
|
@ -142,9 +142,6 @@
|
|||
<string name="superuser_notification">إشعارات طلبات الروت</string>
|
||||
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
|
||||
<string name="settings_su_reauth_summary">أعد مصادقة صلاحيات الروت بعد إجراء ترقيات للتطبيق</string>
|
||||
<string name="settings_su_biometric_title">تفعيل التحقق بالبصمة</string>
|
||||
<string name="settings_su_biometric_summary">تحقق إضافي بالبصمة لطلبات الروت </string>
|
||||
<string name="no_biometric">جهازك غير مدعوم، أو أنك لم تضبّط البصماتَ بجهازك</string>
|
||||
<string name="settings_customization">تخصيص</string>
|
||||
|
||||
<string name="multiuser_mode">نمط مستخدمين متعددين</string>
|
||||
|
|
|
@ -155,9 +155,6 @@
|
|||
<string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string>
|
||||
<string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>
|
||||
<string name="settings_su_biometric_title">Autenticación biométrica</string>
|
||||
<string name="settings_su_biometric_summary">Usa l\'autenticación biométrica pa permitir les solicitúes de superusuariu</string>
|
||||
<string name="no_biometric">El preséu nun ye compatible o nun s\'activaron los axustes biométricos</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer darréu d\'anubrir l\'aplicación</string>
|
||||
<string name="settings_doh_title">DNS per HTTPS</string>
|
||||
|
|
|
@ -165,9 +165,6 @@
|
|||
<string name="settings_su_reauth_summary">Tətbiqləri yüksəltdikdən sonra Superuser icazəsi istə</string>
|
||||
<string name="settings_su_tapjack_title">Tapjack Qoruması</string>
|
||||
<string name="settings_su_tapjack_summary">Superuser icazə pəncərəsi digər pəncərələr tərəfindən əngəlləndikdə heçbir girişə cavab verməyəcək</string>
|
||||
<string name="settings_su_biometric_title">Biometrik İcazə</string>
|
||||
<string name="settings_su_biometric_summary">Superuser istəkləri üçün biometrik icazədən istifadə et</string>
|
||||
<string name="no_biometric">Cihaz ya dəstəklənmir ya da biometrik ayarlar yoxdur</string>
|
||||
<string name="settings_customization">Özəlləşdirmə</string>
|
||||
<string name="setting_add_shortcut_summary">Tətbiqi gizlətdikdən sonra tapmaq çətin olmasın deyə ana ekrana qısayol əlavə et</string>
|
||||
<string name="settings_doh_title">HTTPS əvəzinə DNS</string>
|
||||
|
|
|
@ -142,9 +142,6 @@
|
|||
<string name="settings_su_reauth_summary">Паўторна запытваць правы суперкарыстальніка пасля абнаўлення праграмы</string>
|
||||
<string name="settings_su_tapjack_title">Уключыць абарону ад перахоплівання ўводу</string>
|
||||
<string name="settings_su_tapjack_summary">Дыялог выдачы правоў суперкарыстальніка не будзе адказваць на ўвод, калі па-над ім знаходзяцца іншыя вокны</string>
|
||||
<string name="settings_su_biometric_title">Уключыць біяметрычную аўтэнтыфікацыю</string>
|
||||
<string name="settings_su_biometric_summary">Выкарыстанне біяметрычнай аўтэнтыфікацыі для запытаў правоў суперкарыстальніка</string>
|
||||
<string name="no_biometric">Не падтрымліваецца прыладай альбо не ўключана ў наладах</string>
|
||||
<string name="settings_customization">Персаналізацыя</string>
|
||||
<string name="setting_add_shortcut_summary">Дадаць на хатні экран прыгожы цэтлік на той выпадак, калі пасля хавання праграмы будзе цяжка разглядзець значок і назву</string>
|
||||
<string name="settings_doh_title">DNS паверх HTTPS</string>
|
||||
|
|
|
@ -1,79 +1,154 @@
|
|||
<resources>
|
||||
|
||||
<!--Welcome Activity-->
|
||||
<!--Sections-->
|
||||
<string name="modules">Модули</string>
|
||||
<string name="superuser">Superuser</string>
|
||||
<string name="logs">Дневник</string>
|
||||
<string name="settings">Настройки</string>
|
||||
<string name="install">Инсталиране</string>
|
||||
<string name="section_home">Начало</string>
|
||||
<string name="section_theme">Теми</string>
|
||||
<string name="denylist">Черен списък</string>
|
||||
|
||||
<!--Status Fragment-->
|
||||
<string name="invalid_update_channel">Невалиден канал за актуализации</string>
|
||||
|
||||
<!--Install Fragment-->
|
||||
<string name="keep_force_encryption">Запазване на наложеното криптиране</string>
|
||||
<string name="keep_dm_verity">Запазване на AVB 2.0/dm-verity</string>
|
||||
<string name="uninstall_magisk_title">Деинсталиране на Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Всички модули ще бъдат изключени/премахнати. Руут достъпът ще бъде премахнат и е възможно криптиране на данните Ви.</string>
|
||||
<string name="update">Актуализация</string>
|
||||
|
||||
<!--Module Fragment-->
|
||||
<string name="no_info_provided">(Не е представена информация)</string>
|
||||
<string name="reboot_recovery">Рестартиране в режима за възстановяване</string>
|
||||
<string name="reboot_bootloader">Рестартиране в буутлоудъра</string>
|
||||
<string name="reboot_download">Рестартиране в даунлоуд режима</string>
|
||||
|
||||
<!--Repo Fragment-->
|
||||
<string name="update_available">Налице е актуализация.</string>
|
||||
<string name="home_installed_version">Инсталирани</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Запазване на доклад</string>
|
||||
<string name="menuClearLog">Изчистване на дневника</string>
|
||||
<string name="logs_cleared">Успешно изчистване на дневника.</string>
|
||||
|
||||
<!--About Activity-->
|
||||
<!--Home-->
|
||||
<string name="no_connection">Няма връзка</string>
|
||||
<string name="app_changelog">Списък с промени</string>
|
||||
<string name="loading">Зареждане…</string>
|
||||
<string name="update">Обновяване</string>
|
||||
<string name="not_available">Липсва</string>
|
||||
<string name="hide">Скриване</string>
|
||||
<string name="home_package">Пакет</string>
|
||||
<string name="home_app_title">Приложение</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="repo_install_title">Инсталиране на %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Изтегляне</string>
|
||||
<string name="reboot">Рестартиране</string>
|
||||
<string name="magisk_update_title">Достъпно е издание на Magisk.</string>
|
||||
<string name="release_notes">Бележки</string>
|
||||
<string name="manager_download_install">Докоснете за изтегляне и инсталиране.</string>
|
||||
<string name="update_channel">Актуализации на Magisk</string>
|
||||
<string name="flashing">Инсталиране</string>
|
||||
<string name="open_link_failed_toast">Не е намерено приложение за отваряне на препратката.</string>
|
||||
<string name="direct_install">Директно инсталиране (Препоръчва се.)</string>
|
||||
<string name="install_inactive_slot">Инсталиране на неактивен слот (След OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Вашето устройство НАЛОЖИТЕЛНО ще стартира текущия неактивен слот при следващото рестартиране.\nИзползвайте тази опция само след като приключи инсталирането на OTA.\nПродължаване?</string>
|
||||
.
|
||||
<string name="complete_uninstall">Пълно деинсталиране</string>
|
||||
<string name="restore_img">Възстановяване на образи</string>
|
||||
<string name="restore_img_msg">Възстановяване…</string>
|
||||
<string name="restore_done">Възстановяването е успешно!</string>
|
||||
<string name="restore_fail">Не е налице архив на стоковия образ!</string>
|
||||
<string name="setup_fail">Първоначалната настройка е неуспешна.</string>
|
||||
<string name="env_fix_title">Изисква допълнително настройване</string>
|
||||
<string name="setup_title">Допълнително надстройване</string>
|
||||
<string name="setup_msg">Надстройването на средата е в ход…</string>
|
||||
<string name="download_file_error">Грешка при изтегляне на файла.</string>
|
||||
<string name="home_notice_content">Изтегляйте Magisk САМО от официалната страница в GitHub. Файловете от неизвестни източници могат да бъдат зловредни!</string>
|
||||
<string name="home_support_title">Подкрепете ни</string>
|
||||
<string name="home_follow_title">Последвайте ни</string>
|
||||
<string name="home_item_source">Изходен код</string>
|
||||
<string name="home_support_content">Magisk е, и винаги ще бъде, безплатен и с отворен изходен код. Въпреки това можете да покажете подкрапата си чрез дарение.</string>
|
||||
<string name="home_installed_version">Инсталиранo</string>
|
||||
<string name="home_latest_version">Последно</string>
|
||||
<string name="invalid_update_channel">Каналът за обновяване е недействителен</string>
|
||||
<string name="uninstall_magisk_title">Премахване на Magisk</string>
|
||||
<string name="uninstall_magisk_msg">Всички модули ще бъдат изключени/премахнати. Достъпът до правата на суперпотребителя ще бъде премахнат!\nАко вътрешното хранилище е разшифровано с Magisk, то ще бъде шифровано отново!</string>
|
||||
|
||||
<!--Settings Activity -->
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Запазване на наложеното шифроване</string>
|
||||
<string name="keep_dm_verity">Запазване на AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">Закърпване на vbmeta в образ на boot</string>
|
||||
<string name="recovery_mode">Режим за възстановяване</string>
|
||||
<string name="install_options_title">Настройки</string>
|
||||
<string name="install_method_title">Метод</string>
|
||||
<string name="install_next">Напред</string>
|
||||
<string name="install_start">Начало</string>
|
||||
<string name="manager_download_install">За да изтеглите и инсталирате, докоснете</string>
|
||||
<string name="direct_install">Директно инсталиране (препоръчително)</string>
|
||||
<string name="install_inactive_slot">Инсталиране в неактивен дял (след OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Устройство ЗАДЪЛЖИТЕЛНО ще зареди от текущия неактивен дял при следващия рестарт.\nИзползвайте само при приключила инсталация на OTA.\nПродължаване?</string>
|
||||
<string name="setup_title">Допълнителни настройки</string>
|
||||
<string name="select_patch_file">Избор и закърпване на файл</string>
|
||||
<string name="patch_file_msg">Изберете образ (*.img) или архив на ODIN (*.tar)</string>
|
||||
<string name="reboot_delay_toast">Рестартиране след 5 секунди…</string>
|
||||
<string name="flash_screen_title">Инсталиране</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Запитване за достъп</string>
|
||||
<string name="touch_filtered_warning">Тъй като друго приложение закрива заявката за достъп до правата на суперпотребителя, Magisk не може да потвърди вашия отговор</string>
|
||||
<string name="deny">Отказ</string>
|
||||
<string name="prompt">Запитване</string>
|
||||
<string name="grant">Разрешаване</string>
|
||||
<string name="su_warning">Дава пълен достъп до устройството.\nОткажете, ако не сте сигурни.</string>
|
||||
<string name="forever">Винаги</string>
|
||||
<string name="once">Веднъж</string>
|
||||
<string name="tenmin">10 мин.</string>
|
||||
<string name="twentymin">20 мин.</string>
|
||||
<string name="thirtymin">30 мин.</string>
|
||||
<string name="sixtymin">60 мин.</string>
|
||||
<string name="su_allow_toast">%1$s получи достъп до суперпотребителя</string>
|
||||
<string name="su_deny_toast">%1$s не получи достъп до суперпотребителя</string>
|
||||
<string name="su_snack_grant">Достъп до суперпотребителя е разрешен на %1$s</string>
|
||||
<string name="su_snack_deny">Достъп до суперпотребителя е отказан на %1$s</string>
|
||||
<string name="su_snack_notif_on">Известията за %1$s са включени</string>
|
||||
<string name="su_snack_notif_off">Известията за %1$s са изключени</string>
|
||||
<string name="su_snack_log_on">Записването в дневника за %1$s е включено</string>
|
||||
<string name="su_snack_log_off">Записването в дневника за %1$s е изключено</string>
|
||||
<string name="su_revoke_title">Оттегляне?</string>
|
||||
<string name="su_revoke_msg">Потвърждавате ли оттегляне на достъпа на %1$s?</string>
|
||||
<string name="toast">Тост</string>
|
||||
<string name="none">Без</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Известия</string>
|
||||
<string name="superuser_toggle_revoke">Оттегляне</string>
|
||||
<string name="superuser_policy_none">За момента няма приложения, които да са поискали достъп до правата на суперпотребителя.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Дневникът е празен</string>
|
||||
<string name="log_data_magisk_none">Дневникът на Magisk е празен</string>
|
||||
<string name="menuSaveLog">Запазване на дневника</string>
|
||||
<string name="menuClearLog">Изчистване на дневника</string>
|
||||
<string name="logs_cleared">Дневникът е изчистен</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Целеви UID: %1$d</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Показване на системни приложения</string>
|
||||
<string name="show_os_app">Показване на приложения на ОС</string>
|
||||
<string name="hide_filter_hint">Филтрира по име</string>
|
||||
<string name="hide_search">Търсене</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(Липсва информация)</string>
|
||||
<string name="reboot_userspace">Бърз рестарт</string>
|
||||
<string name="reboot_recovery">Рестарт в режим за възстановяване</string>
|
||||
<string name="reboot_bootloader">Рестарт в bootloader</string>
|
||||
<string name="reboot_download">Рестарт в режим за изтегляне</string>
|
||||
<string name="reboot_edl">Рестарт в EDL</string>
|
||||
<string name="module_version_author">%1$s от %2$s</string>
|
||||
<string name="module_state_remove">Премахване</string>
|
||||
<string name="module_state_restore">Възстановяване</string>
|
||||
<string name="module_action_install_external">Инсталиране от хранилището</string>
|
||||
<string name="update_available">Има обновяване</string>
|
||||
<string name="suspend_text_riru">Модулът е спрян, защото е включено: %1$s</string>
|
||||
<string name="suspend_text_zygisk">Модулът е спрян, защото не е включено: %1$s</string>
|
||||
<string name="zygisk_module_unloaded">Поради несъвместимост модулът Zygisk не е зареден</string>
|
||||
<string name="module_empty">Не са инсталирани модули</string>
|
||||
<string name="confirm_install">Инсталиране на модула %1$s?</string>
|
||||
<string name="confirm_install_title">Подвърждаване на инсталиране</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Режим на темата</string>
|
||||
<string name="settings_dark_mode_message">Изберете режима, който ви отива най-много!</string>
|
||||
<string name="settings_dark_mode_light">Винаги светло</string>
|
||||
<string name="settings_dark_mode_system">Според системата</string>
|
||||
<string name="settings_dark_mode_dark">Винаги тъмно</string>
|
||||
<string name="settings_download_path_title">Папка за изтегляния</string>
|
||||
<string name="settings_download_path_message">Файловете ще бъдат запазвани в %1$s</string>
|
||||
<string name="settings_hide_app_title">Скриване на приложението на Magisk</string>
|
||||
<string name="settings_hide_app_summary">Инсталира междинно приложение с произволен идестификатор и име</string>
|
||||
<string name="settings_restore_app_title">Възстановяване на приложението на Magisk</string>
|
||||
<string name="settings_restore_app_summary">Показва и възстановява оригиналното приложение</string>
|
||||
<string name="language">Език</string>
|
||||
<string name="system_default">(Системен)</string>
|
||||
<string name="settings_check_update_title">Проверка за актуализации</string>
|
||||
<string name="settings_check_update_summary">Периодично проверяване за актуализации във фонов режим.</string>
|
||||
<string name="settings_update_channel_title">Канал за актуализации</string>
|
||||
<string name="system_default">(Според системата)</string>
|
||||
<string name="settings_check_update_title">Проверка за обновяване</string>
|
||||
<string name="settings_check_update_summary">Периодична проверка за обновяване във фонов режим</string>
|
||||
<string name="settings_update_channel_title">Канал за обновяване</string>
|
||||
<string name="settings_update_stable">Стабилен</string>
|
||||
<string name="settings_update_beta">Бета</string>
|
||||
<string name="settings_update_custom">Потребителски</string>
|
||||
<string name="settings_update_custom_msg">Въведете потребителски адрес</string>
|
||||
<string name="settings_hosts_title">Несистемни хостове</string>
|
||||
<string name="settings_hosts_summary">Поддръжка на несистемни хостове за използване на приложения, блокиращи реклами.</string>
|
||||
<string name="settings_hosts_toast">Добавен е модул с несистемни хостове.</string>
|
||||
|
||||
<string name="settings_update_custom_msg">Адрес на потребителски канал</string>
|
||||
<string name="settings_zygisk_summary">Изпълняване на части от Magisk в демон на zygote</string>
|
||||
<string name="settings_denylist_title">Налагане на черен списък</string>
|
||||
<string name="settings_denylist_summary">На процесите в черния списък ще бъдат възстановени всички модификации, направени от Magisk</string>
|
||||
<string name="settings_denylist_error">Изизква %1$s да бъде включено</string>
|
||||
<string name="settings_denylist_config_title">Настройка на черен списък</string>
|
||||
<string name="settings_denylist_config_summary">Изберете процесите, които да бъдат включени в черния списък</string>
|
||||
<string name="settings_hosts_title">Безсистемни хостове</string>
|
||||
<string name="settings_hosts_summary">Поддръжка на безсистемни хостове, ползвани от приложения за спиране на реклами</string>
|
||||
<string name="settings_hosts_toast">Модулът за безсистемни хостове е добавен</string>
|
||||
<string name="settings_app_name_hint">Ново име</string>
|
||||
<string name="settings_app_name_helper">Приложението ще бъде пакетирано с това име</string>
|
||||
<string name="settings_app_name_error">Форматът е недействителен</string>
|
||||
<string name="settings_su_app_adb">Приложения и ADB</string>
|
||||
<string name="settings_su_app">Само приложения</string>
|
||||
<string name="settings_su_adb">Само ADB</string>
|
||||
|
@ -84,55 +159,83 @@
|
|||
<string name="settings_su_request_30">30 секунди</string>
|
||||
<string name="settings_su_request_45">45 секунди</string>
|
||||
<string name="settings_su_request_60">60 секунди</string>
|
||||
<string name="superuser_access">Superuser достъп</string>
|
||||
<string name="superuser_access">Достъп до суперпотребителя</string>
|
||||
<string name="auto_response">Автоматичен отговор</string>
|
||||
<string name="request_timeout">Време за запитване</string>
|
||||
<string name="superuser_notification">Superuser известие</string>
|
||||
<string name="settings_su_reauth_title">Повторно запитване след актуализация</string>
|
||||
<string name="settings_su_reauth_summary">Повторно запитване за Superuser достъп след актуализация на приложенията.</string>
|
||||
<string name="superuser_notification">Известие за суперпотребителя</string>
|
||||
<string name="settings_su_reauth_title">Повторно запитване след обновяване</string>
|
||||
<string name="settings_su_reauth_summary">Повторно запитване за достъп до правата на суперпотребителя след обновяване на приложения</string>
|
||||
<string name="settings_su_tapjack_title">Предпазване от измамно докосване</string>
|
||||
<string name="settings_su_tapjack_summary">Прозорецът за достъп до суперпотребителя няма да реагира, докато е покрит от друг прозорец или слой</string>
|
||||
<string name="settings_customization">Приспособяване</string>
|
||||
<string name="setting_add_shortcut_summary">Добавя красив пряк път на началния екран ако името или текущата икона на скритото приложение са трудни за разпознаване</string>
|
||||
<string name="settings_doh_title">DNS през HTTPS</string>
|
||||
<string name="settings_doh_description">Заобикаля „отравянето“ на DNS в някои държави</string>
|
||||
|
||||
<string name="multiuser_mode">Потребителски достъп</string>
|
||||
<string name="settings_owner_only">Само собственик</string>
|
||||
<string name="multiuser_mode">Многопотребителски режим</string>
|
||||
<string name="settings_owner_only">Само собственика</string>
|
||||
<string name="settings_owner_manage">Управление от страна на собственика</string>
|
||||
<string name="settings_user_independent">Независими потребители</string>
|
||||
<string name="owner_only_summary">Само собственикът има руут достъп.</string>
|
||||
<string name="owner_manage_summary">Само собственикът може да управлява руут достъпа и да получава запитвания за достъп.</string>
|
||||
<string name="user_independent_summary">Всеки потребител има собствени правила за руут достъп.</string>
|
||||
<string name="settings_user_independent">Независимо от потребителя</string>
|
||||
<string name="owner_only_summary">Само собственикът има достъп до суперпотребителя</string>
|
||||
<string name="owner_manage_summary">Само собственикът може да управлява достъпа до суперпотребителя и да получава запитвания за достъп</string>
|
||||
<string name="user_independent_summary">Всеки потребител има собствени правила за достъп до суперпотребителя</string>
|
||||
|
||||
<string name="mount_namespace_mode">Монтиране по именни пространства</string>
|
||||
<string name="settings_ns_global">Глобално</string>
|
||||
<string name="mount_namespace_mode">Монтиране по пространства от имена</string>
|
||||
<string name="settings_ns_global">Общо</string>
|
||||
<string name="settings_ns_requester">Наследено</string>
|
||||
<string name="settings_ns_isolate">Изолирано</string>
|
||||
<string name="global_summary">Всички сесии с руут достъп използват глобалното именно пространство.</string>
|
||||
<string name="requester_summary">Всички сесии с руут достъп наследяват именното пространство на запитващото приложение.</string>
|
||||
<string name="isolate_summary">Всички сесии с руут достъп имат собствени именни пространства.</string>
|
||||
<string name="global_summary">Всички сесии с достъп до суперпотребителя използват общото пространство от имена</string>
|
||||
<string name="requester_summary">Всички сесии с достъп до суперпотребителя наследяват пространството от имена на запитващото приложение</string>
|
||||
<string name="isolate_summary">Всички сесии с достъп до суперпотребителя получават изолирани пространства от имена</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Запитване за Superuser достъп</string>
|
||||
<string name="deny">Отказ</string>
|
||||
<string name="prompt">Запитване</string>
|
||||
<string name="grant">Разрешаване</string>
|
||||
<string name="su_warning">Дава пълен достъп до устройството.\Откажете, ако не сте сигурни.</string>
|
||||
<string name="forever">Завинаги</string>
|
||||
<string name="once">Веднъж</string>
|
||||
<string name="tenmin">10 мин</string>
|
||||
<string name="twentymin">20 мин</string>
|
||||
<string name="thirtymin">30 мин</string>
|
||||
<string name="sixtymin">60 мин</string>
|
||||
<string name="su_allow_toast">На %1$s е разрешен Superuser достъп.</string>
|
||||
<string name="su_deny_toast">На %1$s е отказан Superuser достъп.</string>
|
||||
<string name="su_snack_grant">На %1$s е предоставен Superuser достъп.</string>
|
||||
<string name="su_snack_deny">На %1$s е отказан Superuser достъп.</string>
|
||||
<string name="su_snack_notif_on">Известията за %1$s са включени.</string>
|
||||
<string name="su_snack_notif_off">Известията за %1$s са изключени.</string>
|
||||
<string name="su_snack_log_on">Записването в дневника за %1$s е включено.</string>
|
||||
<string name="su_snack_log_off">Записването в дневника за %1$s е изключено.</string>
|
||||
<string name="su_revoke_title">Оттегляне?</string>
|
||||
<string name="su_revoke_msg">Потвърждавате ли оттегляне на достъпа на %1$s?</string>
|
||||
<string name="toast">Toast</string>
|
||||
<string name="none">Без</string>
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Обновяване на Magisk</string>
|
||||
<string name="progress_channel">Известия за напредък</string>
|
||||
<string name="updated_channel">Завършено обновяване</string>
|
||||
<string name="download_complete">Завършено изтегляне</string>
|
||||
<string name="download_file_error">Грешка при изтегляне на файл</string>
|
||||
<string name="magisk_update_title">Има обновяване на Magisk!</string>
|
||||
<string name="updated_title">Magisk е обновен</string>
|
||||
<string name="updated_text">За да отворите приложението, докоснете</string>
|
||||
|
||||
<!--Superuser logs-->
|
||||
<string name="target_uid">Целеви UID: %1$d</string>
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Не</string>
|
||||
<string name="repo_install_title">Инсталиране на %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Изтегляне</string>
|
||||
<string name="reboot">Рестартиране</string>
|
||||
<string name="release_notes">Бележки по изданието</string>
|
||||
<string name="flashing">Инсталиране…</string>
|
||||
<string name="done">Готово!</string>
|
||||
<string name="failure">Грешка!</string>
|
||||
<string name="hide_app_title">Скриване на приложението на Magisk…</string>
|
||||
<string name="open_link_failed_toast">Не е намерено приложение, с което препратката да бъде отворена</string>
|
||||
<string name="complete_uninstall">Премахване</string>
|
||||
<string name="restore_img">Възстановяване на образи</string>
|
||||
<string name="restore_img_msg">Възстановяване…</string>
|
||||
<string name="restore_done">Възстановяването е успешно!</string>
|
||||
<string name="restore_fail">На устройството липсва резервно копие на заводския образ!</string>
|
||||
<string name="setup_fail">Грешка при първоначална настройка</string>
|
||||
<string name="env_fix_title">Необходима е допълнителна настройка</string>
|
||||
<string name="env_fix_msg">За да работи Magisk нормално, устройството се нуждае от допълнителна настройка. Да бъде ли продължено след рестарт?</string>
|
||||
<string name="env_full_fix_msg">За да работи Magisk нормално, е необходимо да бъде инсталиран отново. Преинсталирайте Magisk от приложението, защото режимът за възстановяване не получава необходимата информация за устройството.</string>
|
||||
<string name="setup_msg">Надстройка на средата…</string>
|
||||
<string name="authenticate">Удостоверяване</string>
|
||||
<string name="unsupport_magisk_title">Неподдържано издание на Magisk</string>
|
||||
<string name="unsupport_magisk_msg">Това издание на приложението не поддържа издания на Magisk преди %1$s.\n\nПриложението ще се държи все едно няма инсталиран Magisk. Обновете Magisk възможно най-скоро.</string>
|
||||
<string name="unsupport_general_title">Неочаквано състояние</string>
|
||||
<string name="unsupport_system_app_msg">Приложението не се поддържа да работи като системно. Върнете го като потребителско.</string>
|
||||
<string name="unsupport_other_su_msg">Намерен е двоичен файл „su“, който не е от Magisk. Премахнете всички други решения за достъп до правата на суперпотребителя и/или инсталирайте Magisk отново.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk е инсталиран във външно хранилище. Преместете приложението във вътрешното хранилище.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Скритото приложение на Magisk не може да продължи да работи, защото достъпът до правата на суперпотребителя е загубен. Възстановете оригиналното приложение.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">За да използвате нази възможност, разрешете достъп до хранилището</string>
|
||||
<string name="post_notifications_denied">За да използвате нази възможност, разрешете достъп до известията</string>
|
||||
<string name="install_unknown_denied">За да използвате тази възможност, разрешете „инсталиране на неизвестни приложения“</string>
|
||||
<string name="add_shortcut_title">Добавяне на пряк път на началния екран</string>
|
||||
<string name="add_shortcut_msg">След скриване на приложението името или икона може да станат трудни за разпознаване. Желаете ли да бъде добаве красив пряк път на началния екран?</string>
|
||||
<string name="app_not_found">Не е намерено приложение, което да извърши действието</string>
|
||||
<string name="reboot_apply_change">Рестартиране за прилагане на промените</string>
|
||||
<string name="restore_app_confirmation">По този начин ще възстановите скритото приложение в първоначалното му състояние. Това ли желаете да бъде направено?</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -164,9 +164,6 @@
|
|||
<string name="settings_su_reauth_summary">অ্যাপগুলি আপগ্রেড করার পরে আবার সুপার ইউজার অনুমতির জন্য জিজ্ঞাসা করুন</string>
|
||||
<string name="settings_su_tapjack_title">ট্যাপজ্যাকিং সুরক্ষা</string>
|
||||
<string name="settings_su_tapjack_summary">সুপার ইউজার প্রম্পট ডায়ালগ অন্য কোনো উইন্ডো বা ওভারলে দ্বারা অস্পষ্ট থাকাকালীন ইনপুটটিতে সাড়া দেবে না</string>
|
||||
<string name="settings_su_biometric_title">বায়োমেট্রিক প্রমাণীকরণ</string>
|
||||
<string name="settings_su_biometric_summary">সুপার ইউজার অনুরোধের অনুমতি দিতে বায়োমেট্রিক প্রমাণীকরণ ব্যবহার করুন</string>
|
||||
<string name="no_biometric">অসমর্থিত ডিভাইস বা কোনো বায়োমেট্রিক সেটিংস সক্ষম করা নেই৷</string>
|
||||
<string name="settings_customization">কাস্টমাইজেশন</string>
|
||||
<string name="setting_add_shortcut_summary">অ্যাপটি লুকানোর পরে নাম এবং আইকন সনাক্ত করা কঠিন হলে হোম স্ক্রিনে একটি সুন্দর শর্টকাট যোগ করুন</string>
|
||||
<string name="settings_doh_title">HTTPS এর উপর ডিএনএস</string>
|
||||
|
|
|
@ -152,9 +152,6 @@
|
|||
<string name="settings_su_reauth_summary">Demanar permisos de superusuari novament si una aplicació és actualitzada o reinstal·lada</string>
|
||||
<string name="settings_su_tapjack_title">Activa la protecció contra \'TapJacking\'</string>
|
||||
<string name="settings_su_tapjack_summary">El diàleg per donar permisos de superusuari no respondrà mentre estigui ofuscat per alguna altra finestra o superposició</string>
|
||||
<string name="settings_su_biometric_title">Activar autenticació biomètrica</string>
|
||||
<string name="settings_su_biometric_summary">Utilitza l\'autenticació biomètrica per permetre sol·licituds de superusuari</string>
|
||||
<string name="no_biometric">El dispositiu no suporta o no té establerta configuració biomètrica</string>
|
||||
<string name="settings_customization">Personalització</string>
|
||||
<string name="setting_add_shortcut_summary">Afegeix una bonica drecera a la pantalla d\'inici en cas que el nom i la icona siguin difícils de reconèixer després d\'amagar l\'aplicació.</string>
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<string name="settings">Nastavení</string>
|
||||
<string name="install">Instalovat</string>
|
||||
<string name="section_home">Domů</string>
|
||||
<string name="section_theme">Téma</string>
|
||||
<string name="section_theme">Témata</string>
|
||||
<string name="denylist">DenyList</string>
|
||||
|
||||
<!--Home-->
|
||||
|
@ -52,7 +52,7 @@
|
|||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Požadavek SuperUser</string>
|
||||
<string name="touch_filtered_warning">Protože aplikace zavírá požadavek SuperUser, Magisk nemůže ověřit Vaši odpověď</string>
|
||||
<string name="touch_filtered_warning">Magisk nemůže ověřit Vaši odpověď, protože aplikace zavírá požadavek SuperUser</string>
|
||||
<string name="deny">Zakázat</string>
|
||||
<string name="prompt">Zeptat se</string>
|
||||
<string name="grant">Povolit</string>
|
||||
|
@ -72,7 +72,7 @@
|
|||
<string name="su_snack_log_on">Protokolování %1$s je povoleno</string>
|
||||
<string name="su_snack_log_off">Protokolování %1$s je zakázáno</string>
|
||||
<string name="su_revoke_title">Smazat?</string>
|
||||
<string name="su_revoke_msg">Chcete smazat protokol k oprávnění pro %1$s?</string>
|
||||
<string name="su_revoke_msg">Potvrzením odeberete %1$s oprávnění SuperUser</string>
|
||||
<string name="toast">Text</string>
|
||||
<string name="none">Žádné</string>
|
||||
|
||||
|
@ -81,13 +81,16 @@
|
|||
<string name="superuser_policy_none">Žádná aplikace nepožádala o oprávnění SuperUser.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Žádné protokoly. Vyzkoušejte nějakou aplikaci vyžadující oprávnění SuperUser.</string>
|
||||
<string name="log_data_magisk_none">Protokoly Magisk jsou prázdné. To je zvláštní!</string>
|
||||
<string name="log_data_none">Nemáte žádné protokoly. Vyzkoušejte nějakou aplikaci vyžadující oprávnění SuperUser</string>
|
||||
<string name="log_data_magisk_none">Protokoly Magisk jsou prázdné. To je zvláštní</string>
|
||||
<string name="menuSaveLog">Uložit protokol</string>
|
||||
<string name="menuClearLog">Smazat protokol</string>
|
||||
<string name="logs_cleared">Protokol byl smazán</string>
|
||||
<string name="pid">PID:%1$d</string>
|
||||
<string name="target_uid">Cílové UID: %1$d</string>
|
||||
<string name="target_pid">Připojit PID cílového ns: %s</string>
|
||||
<string name="selinux_context">SELinux kontext: %s</string>
|
||||
<string name="supp_group">Sekundární skupina: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@ -109,8 +112,8 @@
|
|||
<string name="module_state_restore">Obnovit</string>
|
||||
<string name="module_action_install_external">Instalace z úložiště</string>
|
||||
<string name="update_available">Dostupná aktualizace</string>
|
||||
<string name="suspend_text_riru">Modul pozastaven, protože je povolen %1$s</string>
|
||||
<string name="suspend_text_zygisk">Modul pozastaven, protože %1$s není povoleno</string>
|
||||
<string name="suspend_text_riru">Modul byl pozastaven, protože je povolen %1$s</string>
|
||||
<string name="suspend_text_zygisk">Modul byl pozastaven, protože %1$s není povoleno</string>
|
||||
<string name="zygisk_module_unloaded">Modul Zygisk nebyl načten z důvodu nekompatibility</string>
|
||||
<string name="module_empty">Není nainstalován žádný modul</string>
|
||||
<string name="confirm_install">Instalovat modul %1$s?</string>
|
||||
|
@ -125,29 +128,29 @@
|
|||
<string name="settings_download_path_title">Složka pro stahování</string>
|
||||
<string name="settings_download_path_message">Soubory budou uloženy do %1$s.</string>
|
||||
<string name="settings_hide_app_title">Skrýt aplikaci Magisk</string>
|
||||
<string name="settings_hide_app_summary">Skryjete aplikaci náhodným ID balíčku a vlastním názvem aplikace.</string>
|
||||
<string name="settings_hide_app_summary">Skryje aplikaci náhodným ID balíčku a vlastním názvem aplikace</string>
|
||||
<string name="settings_restore_app_title">Obnovit aplikaci Magisk</string>
|
||||
<string name="settings_restore_app_summary">Obnovíte aplikaci zpět do původního APK.</string>
|
||||
<string name="settings_restore_app_summary">Obnoví aplikaci zpět do původního APK</string>
|
||||
<string name="language">Jazyk</string>
|
||||
<string name="system_default">(výchozí podle systému)</string>
|
||||
<string name="settings_check_update_title">Zkontrolovat aktualizace</string>
|
||||
<string name="settings_check_update_summary">Povolíte pravidelnou kontrolu aktualizace na pozadí.</string>
|
||||
<string name="settings_check_update_summary">Povolí pravidelnou kontrolu aktualizace na pozadí</string>
|
||||
<string name="settings_update_channel_title">Kanál aktualizace</string>
|
||||
<string name="settings_update_stable">Stabilní</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Vlastní</string>
|
||||
<string name="settings_update_custom_msg">Vložte vlastní URL</string>
|
||||
<string name="settings_zygisk_summary">Spusťte části Magisku v Zygote daemon</string>
|
||||
<string name="settings_zygisk_summary">Spustí části Magisku v démonu Zygote</string>
|
||||
<string name="settings_denylist_title">Vynutit DenyList</string>
|
||||
<string name="settings_denylist_summary">Na procesy v DenyListu nebudou aplikovány žádné změny nebo modifikace</string>
|
||||
<string name="settings_denylist_summary">Na procesy v Denylistu nebudou aplikovány žádné změny, nebo modifikace způsobené Magiskem</string>
|
||||
<string name="settings_denylist_error">Tato funkce vyžaduje povolení %1$s</string>
|
||||
<string name="settings_denylist_config_title">Nastavit DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Vyberte procesy, které mají být zahrnuty do DenyListu</string>
|
||||
<string name="settings_hosts_title">Nesystémový hostitel</string>
|
||||
<string name="settings_hosts_summary">Přidáte modul pro podporu nesystémových hostitelů v aplikaci AdBlock.</string>
|
||||
<string name="settings_hosts_toast">Přidán systémový modul hostitelů</string>
|
||||
<string name="settings_denylist_config_summary">Vyberte procesy, které mají být zahrnuty v DenyListu</string>
|
||||
<string name="settings_hosts_title">Nesystémoví hostitelé</string>
|
||||
<string name="settings_hosts_summary">Přidá modul pro podporu nesystémových hostitelů v aplikaci AdBlock</string>
|
||||
<string name="settings_hosts_toast">Modul nesystémových hostitelů přidán</string>
|
||||
<string name="settings_app_name_hint">Nový název</string>
|
||||
<string name="settings_app_name_helper">Název aplikace bude nahrazen tímto názvem.</string>
|
||||
<string name="settings_app_name_helper">Název aplikace bude nahrazen tímto názvem</string>
|
||||
<string name="settings_app_name_error">Neplatný formát</string>
|
||||
<string name="settings_su_app_adb">Aplikace a ADB</string>
|
||||
<string name="settings_su_app">Pouze aplikace</string>
|
||||
|
@ -166,22 +169,19 @@
|
|||
<string name="settings_su_reauth_title">Opětovné ověření po aktualizaci</string>
|
||||
<string name="settings_su_reauth_summary">Opětovné ověření oprávnění SuperUser po aktualizaci aplikace.</string>
|
||||
<string name="settings_su_tapjack_title">Povolit ochranu před TapJack</string>
|
||||
<string name="settings_su_tapjack_summary">Okno dialogu SuperUser nebude reagovat na kliknutí v případě, že je zavřené nebo překryté jiným oknem.</string>
|
||||
<string name="settings_su_biometric_title">Povolit biometrické ověření</string>
|
||||
<string name="settings_su_biometric_summary">Použijte biometrické ověření pro povolení požadavků SuperUser.</string>
|
||||
<string name="no_biometric">Nepodporované zařízení nebo není biometrické ověření povolené</string>
|
||||
<string name="settings_su_tapjack_summary">Okno dialogu SuperUser nebude reagovat na kliknutí v případě, že je zavřené nebo překryté jiným oknem</string>
|
||||
<string name="settings_customization">Přizpůsobit</string>
|
||||
<string name="setting_add_shortcut_summary">Přidejte odkaz na domovskou obrazovku v případě, že se po skrytí aplikace její název a ikona těžko rozpoznávají.</string>
|
||||
<string name="settings_doh_title">DNS nebo HTTPS</string>
|
||||
<string name="settings_doh_description">Řešení pro opravy DNS v některých zemích.</string>
|
||||
<string name="setting_add_shortcut_summary">Přidá odkaz na domovskou obrazovku v případě, že se po skrytí aplikace její název a ikona těžko rozpoznávají.</string>
|
||||
<string name="settings_doh_title">DNS přes HTTPS</string>
|
||||
<string name="settings_doh_description">Obejde DNS v některých zemích</string>
|
||||
|
||||
<string name="multiuser_mode">Režim více uživatelů</string>
|
||||
<string name="settings_owner_only">Vlastník zařízení</string>
|
||||
<string name="settings_owner_manage">Správce zařízení</string>
|
||||
<string name="settings_user_independent">Všichni uživatelé</string>
|
||||
<string name="owner_only_summary">Pouze vlastník má přístup ROOT.</string>
|
||||
<string name="owner_manage_summary">Přístup ROOT má správce zařízení, který přijímá požadavky k přístupu.</string>
|
||||
<string name="user_independent_summary">Každý uživatel má svá vlastní pravidla přístupu ROOT.</string>
|
||||
<string name="owner_only_summary">Pouze vlastník má přístup ROOT</string>
|
||||
<string name="owner_manage_summary">Přístup ROOT má správce zařízení, který přijímá požadavky k přístupu</string>
|
||||
<string name="user_independent_summary">Každý uživatel má svá vlastní pravidla přístupu ROOT</string>
|
||||
|
||||
<string name="mount_namespace_mode">Režim připojení jmenného prostoru</string>
|
||||
<string name="settings_ns_global">Globální jmenný prostor</string>
|
||||
|
@ -199,7 +199,7 @@
|
|||
<string name="download_file_error">Chyba při stahování souboru</string>
|
||||
<string name="magisk_update_title">Aktualizace Magisk je dostupná!</string>
|
||||
<string name="updated_title">Magisk aktualizován</string>
|
||||
<string name="updated_text">Ťukněte pro otevření aplikace</string>
|
||||
<string name="updated_text">Klepnutím otevřete aplikaci</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Ano</string>
|
||||
|
@ -211,7 +211,7 @@
|
|||
<string name="flashing">Flashuji…</string>
|
||||
<string name="done">Hotovo!</string>
|
||||
<string name="failure">Nepodařilo se!</string>
|
||||
<string name="hide_app_title">Skrývám Magisk aplikaci…</string>
|
||||
<string name="hide_app_title">Skrývám aplikaci Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nebyla nalezena žádná aplikace pro otevření odkazu</string>
|
||||
<string name="complete_uninstall">Kompletní odinstalace</string>
|
||||
<string name="restore_img">Obnovit obrazy</string>
|
||||
|
@ -220,25 +220,25 @@
|
|||
<string name="restore_fail">Původní záloha neexistuje!</string>
|
||||
<string name="setup_fail">Nastavení selhalo</string>
|
||||
<string name="env_fix_title">Vyžadováno dodatečné nastavení</string>
|
||||
<string name="env_fix_msg">Aby Magisk správně fungoval, potřebuje vaše zařízení další nastavení. Chcete pokračovat a restartovat?</string>
|
||||
<string name="env_full_fix_msg">Aby vaše zařízení správně fungovalo, potřebuje se Magisk znovu flashnotu. Znovu nainstalujte Magisk v aplikaci, režim obnovení nemůže získat správné informace o zařízení.</string>
|
||||
<string name="env_fix_msg">Aby mohl Magisk správně fungovat, je třeba zařízení dodatečně nastavit. Chcete pokračovat a restartovat zařízení?</string>
|
||||
<string name="env_full_fix_msg">Aby vaše zařízení správně fungovalo, potřebuje se Magisk znovu flashnout. Nainstalujte znovu Magisk v aplikaci, režim Recovery nemůže získat správné informace o zařízení.</string>
|
||||
<string name="setup_msg">Probíhá nastavení prostředí…</string>
|
||||
<string name="authenticate">Ověřit</string>
|
||||
<string name="unsupport_magisk_title">Nepodporovaná verze Magisk</string>
|
||||
<string name="unsupport_magisk_msg">ato verze aplikace nepodporuje verze Magisk nižší než %1$s.\n\nAplikace se bude chovat, jako by Magisk nebyl nainstalován, aktualizujte prosím Magisk co nejdříve.</string>
|
||||
<string name="unsupport_magisk_msg">Tato verze aplikace nepodporuje Magisk ve verzi nižší než %1$s.\n\nAplikace se bude chovat, jako by žádný Magisk nebyl nainstalován. Aktualizujte prosím Magisk co nejdříve.</string>
|
||||
<string name="unsupport_general_title">Abnormální stav</string>
|
||||
<string name="unsupport_system_app_msg">Spuštění této aplikace jako systémové aplikace není podporováno. Vraťte aplikaci zpět na uživatelskou aplikaci.</string>
|
||||
<string name="unsupport_other_su_msg">Byl detekován binární soubor \"su\", který nepochází z Magisk. Odeberte jakékoli konkurenční kořenové řešení a/nebo přeinstalujte Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk je nainstalován na externí úložiště. Přesuňte aplikaci do interního úložiště.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Skrytá aplikace Magisk nemůže pokračovat v práci, protože byl ztracen root. Obnovte prosím původní soubor APK.</string>
|
||||
<string name="unsupport_system_app_msg">Spuštění této aplikace jako systémové, není podporováno. Změňte prosím aplikaci zpět na uživatelskou.</string>
|
||||
<string name="unsupport_other_su_msg">Byl detekován binární soubor \"su\", který nepochází z Magisk. Odeberte jakékoli konkurenční ROOT řešení a/nebo přeinstalujte Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk je nainstalován na externím úložišti. Přesuňte prosím aplikaci do interního úložiště.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Skrytá aplikace Magisk nemůže dále fungovat, protože byl ztracen ROOT. Obnovte prosím původní soubor APK.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Chcete-li tuto funkci povolit, udělte oprávnění úložiště</string>
|
||||
<string name="post_notifications_denied">Chcete-li tuto funkci povolit, udělte oprávnění oznámení</string>
|
||||
<string name="install_unknown_denied">Povolte "instalovat aplikace z neznámých zdrojů" pro aktivaci této funkce</string>
|
||||
<string name="external_rw_permission_denied">Pro povolení této funkce, povolte oprávnění pro přístup k úložišti</string>
|
||||
<string name="post_notifications_denied">Pro povolení této funkce, povolte oznámení</string>
|
||||
<string name="install_unknown_denied">Pro povolení této funkce, povolte "instalovat aplikace z neznámých zdrojů"</string>
|
||||
<string name="add_shortcut_title">Přidat zástupce na domovskou obrazovku</string>
|
||||
<string name="add_shortcut_msg">Po skrytí této aplikace může být obtížné rozpoznat její název a ikonu. Chcete přidat hezkou zkratku na domovskou obrazovku?</string>
|
||||
<string name="app_not_found">Nebyla nalezena žádná aplikace, která by tuto akci zvládla</string>
|
||||
<string name="reboot_apply_change">Restartujte pro použití změn</string>
|
||||
<string name="restore_app_confirmation">Tím se skrytá aplikace obnoví zpět do původní aplikace. Opravdu to chcete udělat?</string>
|
||||
<string name="add_shortcut_msg">Po skrytí této aplikace může být obtížné rozpoznat její název a ikonu. Chcete přidat na domovskou obrazovku hezkého zástupce?</string>
|
||||
<string name="app_not_found">Nebyla nalezena žádná aplikace, která by tuto akci provedla</string>
|
||||
<string name="reboot_apply_change">Pro použití změn restartujte zařízení</string>
|
||||
<string name="restore_app_confirmation">Tímto skrytou aplikaci obnovíte zpět na původní aplikaci. Opravdu to chcete udělat?</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
<string name="install">Installieren</string>
|
||||
<string name="section_home">Startseite</string>
|
||||
<string name="section_theme">Themen</string>
|
||||
<string name="denylist">Verweigerungsliste</string>
|
||||
<string name="denylist">Ausnahmeliste</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Keine Verbindung verfügbar</string>
|
||||
<string name="app_changelog">Änderungen</string>
|
||||
<string name="loading">Laden …</string>
|
||||
<string name="update">Aktualisieren</string>
|
||||
<string name="not_available">N/A</string>
|
||||
<string name="not_available">Nicht verfügbar</string>
|
||||
<string name="hide">Verstecken</string>
|
||||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">App</string>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<string name="install_inactive_slot_msg">Dein Gerät wird gezwungen, nach einem Neustart in den inaktiven Slot zu booten! Diese Option nur verwenden, wenn OTA abgeschlossen ist.\nWeiter?</string>
|
||||
<string name="setup_title">Zusätzliche Einstellungen</string>
|
||||
<string name="select_patch_file">Eine Datei auswählen und patchen</string>
|
||||
<string name="patch_file_msg">Raw Image (*.img) oder ein ODIN tarfile (*.tar) auswählen</string>
|
||||
<string name="patch_file_msg">Ein Raw Image (*.img), eine ODIN Tar-Datei (*.tar) oder eine Payload.bin (*.bin) auswählen</string>
|
||||
<string name="reboot_delay_toast">Neustart in 5 Sekunden …</string>
|
||||
<string name="flash_screen_title">Installieren</string>
|
||||
|
||||
|
@ -88,6 +88,9 @@
|
|||
<string name="logs_cleared">Protokoll erfolgreich gelöscht.</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Ziel-UID: %1$d</string>
|
||||
<string name="target_pid">NS Target PID einbinden: %s</string>
|
||||
<string name="selinux_context">SELinux Kontext: %s</string>
|
||||
<string name="supp_group">Ergänzende Gruppe: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@ -138,11 +141,11 @@
|
|||
<string name="settings_update_custom">Benutzerdefiniert</string>
|
||||
<string name="settings_update_custom_msg">Benutzerdefinierte URL einfügen</string>
|
||||
<string name="settings_zygisk_summary">Teile von Magisk im Zygoten-Daemon ausführen</string>
|
||||
<string name="settings_denylist_title">Verweigerungsliste erzwingen</string>
|
||||
<string name="settings_denylist_summary">Bei Prozessen, die auf der Verweigerungsliste stehen, werden alle Magisk-Änderungen rückgängig gemacht.</string>
|
||||
<string name="settings_denylist_title">Ausnahmeliste erzwingen</string>
|
||||
<string name="settings_denylist_summary">Bei Prozessen, die auf der Ausnahmeliste stehen, werden alle Magisk-Änderungen rückgängig gemacht.</string>
|
||||
<string name="settings_denylist_error">Für diese Funktion muss %1$s aktiviert sein</string>
|
||||
<string name="settings_denylist_config_title">Verweigerungsliste konfigurieren</string>
|
||||
<string name="settings_denylist_config_summary">Auswahl der Prozesse, die in die Verweigerungsliste aufgenommen werden sollen</string>
|
||||
<string name="settings_denylist_config_title">Ausnahmeliste konfigurieren</string>
|
||||
<string name="settings_denylist_config_summary">Auswahl der Prozesse, die in die Ausnahmeliste aufgenommen werden sollen</string>
|
||||
<string name="settings_hosts_title">Systemlose Hosts-Datei</string>
|
||||
<string name="settings_hosts_summary">Systemlose Unterstützung für Werbeblocker</string>
|
||||
<string name="settings_hosts_toast">Systemloses Hosts-Modul hinzugefügt</string>
|
||||
|
@ -167,9 +170,9 @@
|
|||
<string name="settings_su_reauth_summary">Superuser-Berechtigungen nach App-Aktualisierung erneut authentifizieren</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking-Schutz aktivieren</string>
|
||||
<string name="settings_su_tapjack_summary">Das Dialogfeld der Superuser-Eingabeaufforderung reagiert nicht auf Eingaben, wenn es durch ein anderes Fenster oder Überlagerung verdeckt wird</string>
|
||||
<string name="settings_su_biometric_title">Biometrische Authentifizierung aktivieren</string>
|
||||
<string name="settings_su_biometric_summary">Biometrie verwenden, um Superuser-Anfragen zuzulassen</string>
|
||||
<string name="no_biometric">Gerät unterstützt keine biometrischen Daten oder ist nicht mit diesen konfiguriert</string>
|
||||
<string name="settings_su_auth_title">Benutzerauthentifizierung</string>
|
||||
<string name="settings_su_auth_summary">Nachfrage nach Benutzerauthentifizierung bei Superuser-Anfragen</string>
|
||||
<string name="settings_su_auth_insecure">Auf dem Gerät ist keine Authentifizierungsmethode konfiguriert</string>>
|
||||
<string name="settings_customization">Personalisierung</string>
|
||||
<string name="setting_add_shortcut_summary">Hinzufügen einer hübschen Startbildschirm-Verknüpfung, falls der Name und das Symbol nach dem Ausblenden der App schwer zu erkennen sind</string>
|
||||
<string name="settings_doh_title">DNS über HTTPS</string>
|
||||
|
|
|
@ -165,9 +165,6 @@
|
|||
<string name="settings_su_reauth_summary">Επαναπιστοποίηση αδειών υπερχρήστη μετά την αναβάθμιση μίας εφαρμογής</string>
|
||||
<string name="settings_su_tapjack_title">Ενεργοποίηση προστασίας Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">Το παράθυρο διαλόγου προτροπής του υπερχρήστη δεν αποκρίνεται στην είσοδο ενώ κρύβεται από οποιοδήποτε άλλο παράθυρο</string>
|
||||
<string name="settings_su_biometric_title">Ενεργοποίηση βιομετρικού ελέγχου ταυτότητας</string>
|
||||
<string name="settings_su_biometric_summary">Χρησιμοποιήστε βιομετρικό έλεγχο ταυτότητας για να επιτρέψετε αιτήματα υπερχρηστών</string>
|
||||
<string name="no_biometric">Δεν υποστηρίζεται η συσκευή ή δεν υπάρχει καμία βιομετρική ρύθμιση</string>
|
||||
<string name="settings_customization">Προσαρμογή</string>
|
||||
<string name="setting_add_shortcut_summary">Προσθέστε μια όμορφη συντόμευση στην αρχική οθόνη σε περίπτωση που το όνομα και το εικονίδιο είναι δύσκολο να αναγνωριστούν αφού κρύψετε την εφαρμογή</string>
|
||||
<string name="settings_doh_title">DNS μέσω HTTPS</string>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<resources>
|
||||
|
||||
<!--Sections-->
|
||||
<!--Secciones-->
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="superuser">Superusuario</string>
|
||||
<string name="logs">Registros</string>
|
||||
|
@ -8,148 +8,151 @@
|
|||
<string name="install">Instalar</string>
|
||||
<string name="section_home">Inicio</string>
|
||||
<string name="section_theme">Temas</string>
|
||||
<string name="denylist">Lista de Denegacion</string>
|
||||
<string name="denylist">Lista de denegación</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Conexión no disponible</string>
|
||||
<!--Inicio/Pantalla principal-->
|
||||
<string name="no_connection">Sin conexión disponible</string>
|
||||
<string name="app_changelog">Registro de cambios</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="update">Actualizar</string>
|
||||
<string name="not_available">No disponible</string>
|
||||
<string name="hide">Ocultar</string>
|
||||
<string name="home_package">Paquete</string>
|
||||
<string name="home_package">Nombre de paquete:</string>
|
||||
<string name="home_app_title">App</string>
|
||||
|
||||
<string name="home_notice_content">Descarga Magisk SÓLO de la página de GitHub oficial. Archivos de fuentes desconocidas pueden resultar maliciosos!</string>
|
||||
<string name="home_support_title">Apóyenos</string>
|
||||
<string name="home_follow_title">Síganos</string>
|
||||
<string name="home_notice_content">Descarga Magisk únicamente desde la página oficial de GitHub. ¡Las descargas desde fuentes desconocidas pueden ser maliciosas!</string>
|
||||
<string name="home_support_title">Apóyanos</string>
|
||||
<string name="home_follow_title">Síguenos</string>
|
||||
<string name="home_item_source">Código fuente</string>
|
||||
<string name="home_support_content">Magisk es, y siempre será gratuito y de código abierto. Sin embargo, puede apoyarnos enviando una pequeña donación.</string>
|
||||
<string name="home_installed_version">Instalado</string>
|
||||
<string name="home_latest_version">Última</string>
|
||||
<string name="home_support_content">Magisk es y siempre será gratis y de código abierto. Sin embargo, puedes mostrarnos tu apoyo mediante una donación.</string>
|
||||
<string name="home_installed_version">Versión instalada:</string>
|
||||
<string name="home_latest_version">Última versión disponible:</string>
|
||||
<string name="invalid_update_channel">Canal de actualización inválido</string>
|
||||
<string name="uninstall_magisk_title">Desinstalar Magisk</string>
|
||||
<string name="uninstall_magisk_msg">¡Todos los módulos serán desactivados/eliminados!\nSe eliminará la raíz!\n¡Sus datos serán potencialmente encriptados si no lo están ya!</string>
|
||||
<string name="uninstall_magisk_msg">Antes de desinstalar Magisk, ten en cuenta lo siguiente:\n• Todos los módulos serán desinstalados\n• Perderás el acceso root\n• Tu almacenamiento interno será reencriptado en caso de que no lo esté</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">Mantener cifrado forzado</string>
|
||||
<!--Instalación-->
|
||||
<string name="keep_force_encryption">Mantener FDE (Full-Disk Encryption)</string>
|
||||
<string name="keep_dm_verity">Mantener AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">Parche vbmeta en la imagen de arranque</string>
|
||||
<string name="patch_vbmeta">Parchar vbmeta en el archivo boot.img</string>
|
||||
<string name="recovery_mode">Modo recovery</string>
|
||||
<string name="install_options_title">Opciones</string>
|
||||
<string name="install_method_title">Método</string>
|
||||
<string name="install_options_title">Opciones de instalación:</string>
|
||||
<string name="install_method_title">Método de instalación:</string>
|
||||
<string name="install_next">Siguiente</string>
|
||||
<string name="install_start">Comencemos</string>
|
||||
<string name="manager_download_install">Pulse para descargar e instalar</string>
|
||||
<string name="install_start">Instalar</string>
|
||||
<string name="manager_download_install">Pulsa para descargar e instalar</string>
|
||||
<string name="direct_install">Instalación directa (recomendado)</string>
|
||||
<string name="install_inactive_slot">Instalar en ranura inactiva (después de OTA)</string>
|
||||
<string name="install_inactive_slot_msg">¡Su dispositivo será forzado a arrancar en la ranura inactiva actual después de un reinicio!\nSólo use esta opción después de que OTA haya terminado.\n¿Continuar?</string>
|
||||
<string name="install_inactive_slot">Instalar en el slot inactivo (tras una OTA)</string>
|
||||
<string name="install_inactive_slot_msg">Tu dispositivo será forzado a bootear en el slot inactivo actual después de un reinicio\nUsa esta opción solo después de que la actualización OTA se complete.\n¿Quieres continuar?</string>
|
||||
<string name="setup_title">Configuración adicional</string>
|
||||
<string name="select_patch_file">Seleccionar y parchar un archivo</string>
|
||||
<string name="patch_file_msg">Seleccione una imagen raw (*.img) o un archivo tar de ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Selecciona una imagen raw (.img) o un archivo comprimido que la contenga (.tar), en caso de que quieras usar Odin (Samsung)</string>
|
||||
<string name="reboot_delay_toast">Reiniciando en 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalación</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Petición de superusuario</string>
|
||||
<string name="touch_filtered_warning">Como otra app está escondiendo la solicitud de superusuario, Magisk no puede verificar tu respuesta</string>
|
||||
<!--Superusuario-->
|
||||
<string name="su_request_title">Solicitud de superusuario</string>
|
||||
<string name="touch_filtered_warning">Hay una app superponiéndose a la solicitud de superusuario, por lo que Magisk no puede verificar tu respuesta.</string>
|
||||
<string name="deny">Denegar</string>
|
||||
<string name="prompt">Preguntar</string>
|
||||
<string name="grant">Permitir</string>
|
||||
<string name="su_warning">Permite acceso total a tu dispositivo.\n¡Denegar si no está seguro!</string>
|
||||
<string name="grant">Conceder</string>
|
||||
<string name="su_warning">Le concedes a esta app acceso completo a tu dispositivo.\n¡Rechaza la solicitud si desconfías de ella!</string>
|
||||
<string name="forever">Siempre</string>
|
||||
<string name="once">Una vez</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</string>
|
||||
<string name="su_allow_toast">Permitidos derechos de superusuario para %1$s</string>
|
||||
<string name="su_deny_toast">Denegados derechos de superusuario para %1$s</string>
|
||||
<string name="su_snack_grant">Derechos de superusuario para %1$s permitidos</string>
|
||||
<string name="su_snack_deny">Derechos de superusuario para %1$s denegados</string>
|
||||
<string name="tenmin">10 minutos</string>
|
||||
<string name="twentymin">20 minutos</string>
|
||||
<string name="thirtymin">30 minutos</string>
|
||||
<string name="sixtymin">1 hora</string>
|
||||
<string name="su_allow_toast">Le concediste privilegios de superusuario a %1$s</string>
|
||||
<string name="su_deny_toast">Le denegaste privilegios de superusuario a %1$s</string>
|
||||
<string name="su_snack_grant">Privilegios de superusuario para %1$s concedidos</string>
|
||||
<string name="su_snack_deny">Privilegios de superusuario para %1$s denegados</string>
|
||||
<string name="su_snack_notif_on">Notificaciones de %1$s habilitadas</string>
|
||||
<string name="su_snack_notif_off">Notificaciones de %1$s deshabilitadas</string>
|
||||
<string name="su_snack_log_on">Registros de %1$s habilitados</string>
|
||||
<string name="su_snack_log_off">Registros de %1$s deshabilitados</string>
|
||||
<string name="su_revoke_title">¿Revocar?</string>
|
||||
<string name="su_revoke_msg">¿Confirmar para revocar derechos de %1$s?</string>
|
||||
<string name="toast">Aviso</string>
|
||||
<string name="none">Nada</string>
|
||||
|
||||
<string name="su_revoke_title">Revocar privilegios</string>
|
||||
<string name="su_revoke_msg">Confirma para revocarle los privilegios de superusuario a %1$s</string>
|
||||
<string name="toast">Mensaje emergente</string>
|
||||
<string name="none">Ninguno</string>
|
||||
<string name="superuser_toggle_notification">Notificaciones</string>
|
||||
<string name="superuser_toggle_revoke">Revocar</string>
|
||||
<string name="superuser_policy_none">Ninguna aplicación ha pedido permiso de superusuario todavía.</string>
|
||||
<string name="superuser_policy_none">Ninguna app ha solicitado privilegios de superusuario aún.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">No se tiene registro, intente usar más sus aplicaciones habilitadas para SU.</string>
|
||||
<string name="log_data_magisk_none">Los registros de Magisk están vacíos, esto es muy raro.</string>
|
||||
<!--Registros-->
|
||||
<string name="log_data_none">No hay registros de apps disponibles</string>
|
||||
<string name="log_data_magisk_none">No hay registros de Magisk disponibles</string>
|
||||
<string name="menuSaveLog">Guardar registro</string>
|
||||
<string name="menuClearLog">Limpiar registro ahora</string>
|
||||
<string name="logs_cleared">Registro limpiado correctamente</string>
|
||||
<string name="menuClearLog">Limpiar registro</string>
|
||||
<string name="logs_cleared">Registros limpiados correctamente</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">UID de objetivo: %1$d</string>
|
||||
<string name="target_uid">Objetivo UID: %1$d</string>
|
||||
<string name="target_pid">Montar ns objetivo PID: %s</string>
|
||||
<string name="selinux_context">Contexto SELinux: %s</string>
|
||||
<string name="supp_group">Grupo suplementario: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
<!--SafetyNet/PlayIntegrity-->
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<!--Denylist-->
|
||||
<string name="show_system_app">Mostrar apps del sistema</string>
|
||||
<string name="show_os_app">Mostrar apps del OS</string>
|
||||
<string name="hide_filter_hint">Filtrar por nombre</string>
|
||||
<string name="show_os_app">Mostrar apps del sistema operativo</string>
|
||||
<string name="hide_filter_hint">Nombre de la app o del paquete</string>
|
||||
<string name="hide_search">Buscar</string>
|
||||
|
||||
<!--Module -->
|
||||
<string name="no_info_provided">(No hay información)</string>
|
||||
<!--Módulos-->
|
||||
<string name="no_info_provided">El creador del módulo no proporcionó ninguna descripción.</string>
|
||||
<string name="reboot_userspace">Reinicio suave</string>
|
||||
<string name="reboot_recovery">Reiniciar en modo recovery</string>
|
||||
<string name="reboot_bootloader">Reiniciar en modo bootloader</string>
|
||||
<string name="reboot_download">Reiniciar en modo download</string>
|
||||
<string name="reboot_edl">Reiniciar en modo EDL</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Eliminar</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="reboot_recovery">Modo Recovery</string>
|
||||
<string name="reboot_bootloader">Modo Bootloader</string>
|
||||
<string name="reboot_download">Modo Download</string>
|
||||
<string name="reboot_edl">Modo EDL</string>
|
||||
<string name="module_version_author">Versión %1$s, por %2$s</string>
|
||||
<string name="module_state_remove">Desinstalar</string>
|
||||
<string name="module_state_restore">Reinstalar</string>
|
||||
<string name="module_action_install_external">Instalar desde almacenamiento</string>
|
||||
<string name="update_available">Actualización disponible</string>
|
||||
<string name="suspend_text_riru">Módulo suspendido porque %1$s está habilitado</string>
|
||||
<string name="suspend_text_zygisk">Módulo suspendido porque %1$s no está habilitado</string>
|
||||
<string name="zygisk_module_unloaded">Módulo Zygisk no cargado debido a incompatibilidad</string>
|
||||
<string name="module_empty">Ningún módulo instalado</string>
|
||||
<string name="suspend_text_riru">Módulo suspendido porque %1$s está activado</string>
|
||||
<string name="suspend_text_zygisk">Módulo suspendido porque %1$s está desactivado</string>
|
||||
<string name="zygisk_module_unloaded">El módulo Zygisk no se cargó por problemas de compatibilidad</string>
|
||||
<string name="module_empty">No hay módulos instalados</string>
|
||||
<string name="confirm_install">¿Quieres instalar el módulo %1$s?</string>
|
||||
<string name="confirm_install_title">Confirmar instalación</string>
|
||||
|
||||
<!--Settings -->
|
||||
<string name="settings_dark_mode_title">Elegir modo</string>
|
||||
<string name="settings_dark_mode_message">¡Seleccione el modo que mejor se adapte a su estilo!</string>
|
||||
<!--Ajustes-->
|
||||
<string name="settings_dark_mode_title">Modo del tema</string>
|
||||
<string name="settings_dark_mode_message">¡Elige el modo que más se adapte a tu estilo!</string>
|
||||
<string name="settings_dark_mode_light">Claro</string>
|
||||
<string name="settings_dark_mode_system">Seguir al sistema</string>
|
||||
<string name="settings_dark_mode_dark">Oscuro</string>
|
||||
<string name="settings_download_path_title">Ruta de Descarga</string>
|
||||
<string name="settings_download_path_message">Los archivos se guardarán en %1$s</string>
|
||||
<string name="settings_hide_app_title">Esconder la app de Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instalar una nueva app proxy con una ID de paquete aleatoria y una etiqueta personalizada</string>
|
||||
<string name="settings_restore_app_title">Restaurar la app de Magisk</string>
|
||||
<string name="settings_restore_app_summary">Descubrir la app y restaurarla al APK original</string>
|
||||
<string name="settings_dark_mode_system">Predeterminado del sistema</string>
|
||||
<string name="settings_download_path_title">Ruta de descarga</string>
|
||||
<string name="settings_download_path_message">Los archivos serán guardados en la siguiente ruta:\n%1$s</string>
|
||||
<string name="settings_hide_app_title">Ocultar la app de Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instalar una versión proxy de la app con un nombre de paquete aleatorio y una etiqueta personalizada</string>
|
||||
<string name="settings_restore_app_title">Restaurar la app</string>
|
||||
<string name="settings_restore_app_summary">Desoculta la app y la reemplaza por la original</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="system_default">(Idioma del sistema)</string>
|
||||
<string name="settings_check_update_title">Comprobar Actualizaciones</string>
|
||||
<string name="settings_check_update_summary">Buscar periódicamente en segundo plano si existen actualizaciones</string>
|
||||
<string name="system_default">Predeterminado del sistema</string>
|
||||
<string name="settings_check_update_title">Buscar actualizaciones</string>
|
||||
<string name="settings_check_update_summary">Busca actualizaciones periódicamente en segundo plano</string>
|
||||
<string name="settings_update_channel_title">Canal de actualización</string>
|
||||
<string name="settings_update_stable">Estable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Personalizado</string>
|
||||
<string name="settings_update_custom_msg">Insertar una URL personalizada</string>
|
||||
<string name="settings_zygisk_summary">Corre partes de magisk en zygote</string>
|
||||
<string name="settings_denylist_title">Aplicar lista de denegacion</string>
|
||||
<string name="settings_denylist_summary">Los procesos en la lista de denegacion tandran todas las modificaciones de magisk revertidas</string>
|
||||
<string name="settings_denylist_error">Esta requere de %1$s para ser activada</string>
|
||||
<string name="settings_denylist_config_title">Configurar lista de denegacion</string>
|
||||
<string name="settings_denylist_config_summary">Seleccione los procesos para ser incluidos en la lista de denegacion</string>
|
||||
<string name="settings_hosts_title">Systemless Hosts</string>
|
||||
<string name="settings_hosts_summary">Soporte para bloqueadores de publicidad fuera de la partición system</string>
|
||||
<string name="settings_hosts_toast">Módulo systemless hosts agregado</string>
|
||||
<string name="settings_app_name_hint">Nuevo Nombre</string>
|
||||
<string name="settings_app_name_helper">La app se reempaquetará</string>
|
||||
<string name="settings_app_name_error">Formato inválido</string>
|
||||
<string name="settings_su_app_adb">Aplicaciones y ADB</string>
|
||||
<string name="settings_su_app">Sólo aplicaciones</string>
|
||||
<string name="settings_su_adb">Sólo ADB</string>
|
||||
<string name="settings_update_custom_msg">Ingresa la URL del canal personalizado</string>
|
||||
<string name="settings_zygisk_summary">Ejecuta Magisk en el proceso Zygote de Android</string>
|
||||
<string name="settings_denylist_title">Aplicar lista de denegación</string>
|
||||
<string name="settings_denylist_summary">Las apps en la lista de denegación no serán modificadas por Magisk</string>
|
||||
<string name="settings_denylist_error">Necesitas activar %1$s para usar esta función</string>
|
||||
<string name="settings_denylist_config_title">Configurar lista de denegación</string>
|
||||
<string name="settings_denylist_config_summary">Selecciona los procesos de cada app que se incluirán en la lista</string>
|
||||
<string name="settings_hosts_title">Systemless hosts</string>
|
||||
<string name="settings_hosts_summary">Prepara al archivo hosts para que los bloqueadores de anuncios puedan modificarlo sin alterar el sistema</string>
|
||||
<string name="settings_hosts_toast">El módulo se instaló correctamente, reinicia para aplicar los cambios.</string>
|
||||
<string name="settings_app_name_hint">Nuevo nombre de la app</string>
|
||||
<string name="settings_app_name_helper">La app de Magisk será reempaquetada con este nombre</string>
|
||||
<string name="settings_app_name_error">El nombre es inválido</string>
|
||||
<string name="settings_su_app_adb">Permitir tanto a las apps como a ADB</string>
|
||||
<string name="settings_su_app">Permitir solo a las apps</string>
|
||||
<string name="settings_su_adb">Permitir solo a ADB</string>
|
||||
<string name="settings_su_disable">Deshabilitado</string>
|
||||
<string name="settings_su_request_10">10 segundos</string>
|
||||
<string name="settings_su_request_15">15 segundos</string>
|
||||
|
@ -158,48 +161,46 @@
|
|||
<string name="settings_su_request_45">45 segundos</string>
|
||||
<string name="settings_su_request_60">60 segundos</string>
|
||||
<string name="superuser_access">Acceso de superusuario</string>
|
||||
<string name="auto_response">Respuesta automática</string>
|
||||
<string name="request_timeout">Tiempo de petición</string>
|
||||
<string name="auto_response">Respuesta predeterminada</string>
|
||||
<string name="request_timeout">Duración de la solicitud</string>
|
||||
<string name="superuser_notification">Notificación de superusuario</string>
|
||||
<string name="settings_su_reauth_title">Re-autenticación</string>
|
||||
<string name="settings_su_reauth_summary">Pedir permisos de superusuario nuevamente si una aplicación es actualizada o reinstalada</string>
|
||||
<string name="settings_su_tapjack_title">Habilitar Protección contra Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El cuadro de solicitud de superusuario no responderá mientras esté oculto por cualquier otra ventana o superposición</string>
|
||||
<string name="settings_su_biometric_title">Habilitar autenticación biométrica</string>
|
||||
<string name="settings_su_biometric_summary">Usar autenticación biométrica para permitir solicitudes de superusuario</string>
|
||||
<string name="no_biometric">Dispositivo no compatible o las configuraciones biométricas no están habilitadas</string>
|
||||
<string name="settings_su_reauth_title">Reautenticar después de una actualización</string>
|
||||
<string name="settings_su_reauth_summary">Las apps volverán a solicitar privilegios de superusuario tras actualizarse</string>
|
||||
<string name="settings_su_tapjack_title">Protección contra tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">No podrás interactuar con las solicitudes de superusuario mientras estén tapadas por cualquier otra ventana o superposición</string>
|
||||
<string name="settings_su_auth_title">Autenticación de usuario</string>
|
||||
<string name="settings_su_auth_summary">Preguntar por la autenticación de usuario durante las peticiones de Superusuario</string>
|
||||
<string name="settings_su_auth_insecure">No hay método de autenticación configurado en el dispositivo</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Añade un bonito atajo en la pantalla de inicio en caso de que el nombre y el icono sean difíciles de reconocer después de ocultar la aplicación</string>
|
||||
<string name="setting_add_shortcut_summary">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Evitar envenenamiento de DNS en algunos países</string>
|
||||
|
||||
<string name="settings_doh_description">Proporciona una solución alternativa (workaround) contra el envenenamiento de DNS en algunos países</string>
|
||||
<string name="multiuser_mode">Modo multiusuario</string>
|
||||
<string name="settings_owner_only">Sólo administrador del dispositivo</string>
|
||||
<string name="settings_owner_manage">Administrador del dispositivo</string>
|
||||
<string name="settings_user_independent">Usuario independiente</string>
|
||||
<string name="owner_only_summary">Sólo el administrador tiene acceso root</string>
|
||||
<string name="owner_manage_summary">Sólo el administrador puede supervisar el acceso root y recibir solicitudes de otros usuarios</string>
|
||||
<string name="user_independent_summary">Cada usuario tiene separadas sus propias reglas de root </string>
|
||||
<string name="settings_owner_only">Propietario del dispositivo</string>
|
||||
<string name="settings_owner_manage">Administrado por el propietario del dispositivo</string>
|
||||
<string name="settings_user_independent">Acceso independiente</string>
|
||||
<string name="owner_only_summary">Solo el administrador tiene acceso root</string>
|
||||
<string name="owner_manage_summary">Solo el administrador puede manipular y recibir solicitudes de superusuario</string>
|
||||
<string name="user_independent_summary">Cada usuario tiene sus propias reglas y acceso al root</string>
|
||||
<string name="mount_namespace_mode">Modo del namespace de montaje</string>
|
||||
<string name="settings_ns_global">Namespace global</string>
|
||||
<string name="settings_ns_requester">Namespace heredado</string>
|
||||
<string name="settings_ns_isolate">Namespace aislado</string>
|
||||
<string name="global_summary">Todas las sesiones de superusuario usarán el namespace de montaje global</string>
|
||||
<string name="requester_summary">Las sesiones de superusuario heredarán el namespace de montaje de quien las solicita</string>
|
||||
<string name="isolate_summary">Cada sesión de superusuario tendrá su propio namespace de montaje aislado</string>
|
||||
|
||||
<string name="mount_namespace_mode">Montar Namespace </string>
|
||||
<string name="settings_ns_global">Global Namespace</string>
|
||||
<string name="settings_ns_requester">Heredar Namespace</string>
|
||||
<string name="settings_ns_isolate">Aislar Namespace</string>
|
||||
<string name="global_summary">Todas las sesiones de root utilizan el soporte Global Namespace</string>
|
||||
<string name="requester_summary">Las sesiones de root heredarán las peticiones Namespace</string>
|
||||
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Actualización de Magisk</string>
|
||||
<!--Notificaciones-->
|
||||
<string name="update_channel">Actualizaciones de Magisk</string>
|
||||
<string name="progress_channel">Notificaciones de progreso</string>
|
||||
<string name="updated_channel">Actualización completa</string>
|
||||
<string name="download_complete">Descarga Completa</string>
|
||||
<string name="download_file_error">Error descargando archivo</string>
|
||||
<string name="updated_channel">Actualización finalizada</string>
|
||||
<string name="download_complete">Descarga finalizada</string>
|
||||
<string name="download_file_error">Ocurrió un error en la descarga</string>
|
||||
<string name="magisk_update_title">¡Actualización de Magisk disponible!</string>
|
||||
<string name="updated_title">Magisk Actualizado</string>
|
||||
<string name="updated_text">Toca para abrir la aplicación</string>
|
||||
<string name="updated_title">Se actualizó Magisk</string>
|
||||
<string name="updated_text">Pulsa para abrir la app</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<!--Alertas y diálogos-->
|
||||
<string name="yes">Sí</string>
|
||||
<string name="no">No</string>
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
|
@ -207,34 +208,36 @@
|
|||
<string name="reboot">Reiniciar</string>
|
||||
<string name="release_notes">Notas de lanzamiento</string>
|
||||
<string name="flashing">Flasheando…</string>
|
||||
<string name="done">¡Hecho!</string>
|
||||
<string name="failure">Ha fallado</string>
|
||||
<string name="hide_app_title">Escondiendo la app de Magisk…</string>
|
||||
<string name="open_link_failed_toast">No se encontró ninguna aplicación para abrir el enlace…</string>
|
||||
<string name="done">¡Listo!</string>
|
||||
<string name="failure">¡Ocurrió un error!</string>
|
||||
<string name="hide_app_title">Ocultando la app de Magisk…</string>
|
||||
<string name="open_link_failed_toast">No se encontró ninguna app que pueda abrir este enlace</string>
|
||||
<string name="complete_uninstall">Desinstalación completa</string>
|
||||
<string name="restore_img">Restaurar imágenes</string>
|
||||
<string name="restore_img">Solo restaurar las imágenes</string>
|
||||
<string name="restore_img_msg">Restaurando…</string>
|
||||
<string name="restore_done">¡Restauración Terminada!</string>
|
||||
<string name="restore_fail">¡El respaldo de la imagen boot Stock no existe!</string>
|
||||
<string name="setup_fail">Instalación fallida</string>
|
||||
<string name="env_fix_title">Se requiere una instalación adicional</string>
|
||||
<string name="env_fix_msg">Tu dispositivo necesita configuración adicional para el correcto funcionamiento de Magisk. ¿Quieres proceder y reiniciar?</string>
|
||||
<string name="restore_done">¡Restauración completa!</string>
|
||||
<string name="restore_fail">Oops... no se encontró una copia de seguridad de las imágenes originales</string>
|
||||
<string name="setup_fail">Ocurrió un error en la configuración</string>
|
||||
<string name="env_fix_title">Configuración adicional</string>
|
||||
<string name="env_fix_msg">Tu dispositivo necesita una configuración adicional para que Magisk funcione correctamente. ¿Quieres continuar y reiniciar?</string>
|
||||
<string name="env_full_fix_msg">Tu dispositivo necesita reinstalar Magisk para que este funcione correctamente. Por favor, reinstala Magisk desde la app, el modo recovery no puede obtener la información correcta del dispositivo.</string>
|
||||
<string name="setup_msg">Ejecutando configuración de entorno…</string>
|
||||
<string name="authenticate">Autenticar</string>
|
||||
<string name="unsupport_magisk_title">Versión de Magisk no soportada</string>
|
||||
<string name="unsupport_magisk_msg">Esta versión de la app no soporta versiones de Magisk inferiores a la %1$s.\n\nLa aplicación se comportará como si no tuvieses Magisk instalado, por favor actualiza Magisk tan pronto como sea posible.</string>
|
||||
<string name="unsupport_magisk_msg">La versión actual de la app no soporta versiones de Magisk inferiores a la %1$s.\n\nLa app se comportará como si Magisk no estuviese instalado, por favor, actualízalo tan pronto como puedas</string>
|
||||
<string name="unsupport_general_title">Estado anormal</string>
|
||||
<string name="unsupport_system_app_msg">No se admite la ejecución de esta aplicación como una aplicación del sistema. Revierta la aplicación a una aplicación de usuario.</string>
|
||||
<string name="unsupport_other_su_msg">Se ha detectado un binario \"su\" que no es de Magisk. Elimine cualquier solución raíz de la competencia y/o reinstale Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk está instalado en el almacenamiento externo. Mueva la aplicación al almacenamiento interno.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">La aplicación Magisk oculta no puede seguir funcionando porque se perdió la raíz. Restaura el APK original.</string>
|
||||
<string name="unsupport_system_app_msg">La ejecución de esta app como app del sistema no está soportada. Por favor, vuelve a instalarla como usuario</string>
|
||||
<string name="unsupport_other_su_msg">Se detectó un binario \"su\" ajeno a Magisk. Por favor, desinstala cualquier solución root de la competencia y/o reinstala Magisk</string>
|
||||
<string name="unsupport_external_storage_msg">La app de Magisk está instalada en un almacenamiento externo, por favor, muévela al almacenamiento interno.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">La app de Magisk oculta no puede seguir funcionando porque se perdió el acceso root. Por favor, restaura el APK original</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Conceder permiso de almacenamiento para habilitar esta funcionalidad</string>
|
||||
<string name="install_unknown_denied">Permitir "instalar aplicaciones desconocidas" para habilitar esta funcionalidad</string>
|
||||
<string name="external_rw_permission_denied">Permite el acceso al almacenamiento para activar esta funcionalidad</string>
|
||||
<string name="post_notifications_denied">Permite mostrar notificaciones para activar esta funcionalidad</string>
|
||||
<string name="install_unknown_denied">Permite la instalación de apps desconocidas para activar esta funcionalidad</string>
|
||||
<string name="add_shortcut_title">Añadir un atajo a la pantalla de inicio</string>
|
||||
<string name="add_shortcut_msg">Tras esconder la app, su nombre e icono pueden resultar difíciles de reconocer. ¿Quieres añadir un acceso directo más bonito a la pantalla principal?</string>
|
||||
<string name="app_not_found">No se ha encontrado ninguna app para manejar esta acción</string>
|
||||
<string name="reboot_apply_change">Reiniciar para aplicar cambios</string>
|
||||
<string name="restore_app_confirmation">Esto restaurará la aplicación oculta a la aplicación original. ¿Realmente quieres hacer esto?</string>
|
||||
<string name="add_shortcut_msg">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>
|
||||
<string name="app_not_found">No se encontró ninguna app que pueda realizar esta acción</string>
|
||||
<string name="reboot_apply_change">Reinicia para aplicar los cambios</string>
|
||||
<string name="restore_app_confirmation">Se restaurará la app oculta de vuelta a la original. ¿Quieres continuar?</string>
|
||||
|
||||
</resources>
|
||||
</resources>
|
|
@ -163,9 +163,6 @@
|
|||
<string name="settings_su_reauth_summary">Pärast rakenduste uuendamist küsi superkasutaja luba uuesti</string>
|
||||
<string name="settings_su_tapjack_title">Nupu varjamise kaitse</string>
|
||||
<string name="settings_su_tapjack_summary">Superkasutaja taotluse hüpik ei reageeri vajutusele, kui seda katab mõni teine aken või ülekate</string>
|
||||
<string name="settings_su_biometric_title">Luba biomeetriaga autentimine</string>
|
||||
<string name="settings_su_biometric_summary">Kasuta superkasutaja taotluste kinnitamiseks biomeetrilist autentimist</string>
|
||||
<string name="no_biometric">Mittetoetatud seade või ükski biomeetriaseadistus pole lubatud</string>
|
||||
<string name="settings_customization">Kohandamine</string>
|
||||
<string name="setting_add_shortcut_summary">Lisa avakuvale ilus otsetee juhuks, kui nime ja ikooni on pärast rakenduse peitmist raske tuvastada</string>
|
||||
<string name="settings_doh_title">DNS üle HTTPSi</string>
|
||||
|
|
|
@ -112,9 +112,9 @@
|
|||
<string name="settings_check_update_title">چک کردن بروز رسانی ها</string>
|
||||
<string name="settings_check_update_summary"> برسی کردن به صورت دوره ای برای به روزرسانی در پس زمینه</string>
|
||||
<string name="settings_update_channel_title">کانال بروزرسانی</string>
|
||||
<string name="settings_update_stable">Stable</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Custom Channel</string>
|
||||
<string name="settings_update_stable">پایدار</string>
|
||||
<string name="settings_update_beta">آزمایشی</string>
|
||||
<string name="settings_update_custom">شخصی سازی شده</string>
|
||||
<string name="settings_update_custom_msg">اضافه کردن یک URL سفارشی</string>
|
||||
<string name="settings_hosts_title">نصب بدون حذف یا تغییر در فایل ها</string>
|
||||
<string name="settings_hosts_summary">نصب بدون حذف یا تغییر در فایل ها رای ساپورت از برنامه های Adblock</string>
|
||||
|
@ -138,9 +138,6 @@
|
|||
<string name="superuser_notification">اعلان روت</string>
|
||||
<string name="settings_su_reauth_title">احراز هویت دوباره پس از بروز رسانی</string>
|
||||
<string name="settings_su_reauth_summary">تأیید کردندوباره مجوزهای روت پس از ارتقاء برنامه</string>
|
||||
<string name="settings_su_biometric_title">فعال کردن احراز هویت بیومتریک</string>
|
||||
<string name="settings_su_biometric_summary">استفاده کردن از احراز هویت بیومتریک برای تأیید درخواست های روت</string>
|
||||
<string name="no_biometric">دستگاه پشتیبانی نمی شود یا تنظیمات بیومتریک فعال نیست</string>
|
||||
<string name="settings_customization">سفارشی سازی</string>
|
||||
<string name="setting_add_shortcut_summary">اضافه کردن یک میانبر زیبا را در صفحه اصلی در صورت شناسایی نام و نماد پس از پنهان کردن برنامه</string>
|
||||
|
||||
|
@ -153,16 +150,16 @@
|
|||
<string name="user_independent_summary">هر کاربر قوانین روت جداگانه خود را دارد</string>
|
||||
|
||||
<string name="mount_namespace_mode">نصب کردن Namespace Mode</string>
|
||||
<string name="settings_ns_global">Global Namespace</string>
|
||||
<string name="settings_ns_requester">Inherit Namespace</string>
|
||||
<string name="settings_ns_isolate">Isolated Namespace</string>
|
||||
<string name="settings_ns_global">سراسری Namespace</string>
|
||||
<string name="settings_ns_requester">وراثتی Namespace</string>
|
||||
<string name="settings_ns_isolate">منزوی Namespace</string>
|
||||
<string name="global_summary">همه سشن های روت از global namespace نصب شده استفاده میکنند</string>
|
||||
<string name="requester_summary">سشن های روت namespace دراخواست کننده را به ارث می برند</string>
|
||||
<string name="isolate_summary">هر سشن روت namespace جداگانه خود را دارد</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk بروزرسانی های</string>
|
||||
<string name="progress_channel">Progress اعلان</string>
|
||||
<string name="progress_channel">اعلان پیشرفت</string>
|
||||
<string name="download_complete">دانلود کامل شد</string>
|
||||
<string name="download_file_error">خطا در دانلود فایل</string>
|
||||
<string name="magisk_update_title">بروزرسانی Magisk در دسترس است!</string>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<string name="install_inactive_slot_msg">Votre appareil sera obligatoirement réamorcé à partir de l’espace (slot) actuellement inactif après son redémarrage !\nN’utilisez uniquement cette option qu’après que la mise à jour OTA a été effectuée.\nVoulez‑vous continuer ?</string>
|
||||
<string name="setup_title">Configuration supplémentaire</string>
|
||||
<string name="select_patch_file">Sélectionnez le fichier cible du correctif</string>
|
||||
<string name="patch_file_msg">Sélectionnez une image brute (*.img) ou une archive TAR ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Sélectionnez une image brute (*.img) ou une archive TAR ODIN (*.tar) ou un fichier payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Redémarrage dans 5 secondes…</string>
|
||||
<string name="flash_screen_title">Installation</string>
|
||||
|
||||
|
@ -88,6 +88,9 @@
|
|||
<string name="logs_cleared">Le journal a été effacé.</string>
|
||||
<string name="pid">PID : %1$d</string>
|
||||
<string name="target_uid">UID cible : %1$d</string>
|
||||
<string name="target_pid">Montage ns PID cible: %s</string>
|
||||
<string name="selinux_context">Contexte SELinux: %s</string>
|
||||
<string name="supp_group">Groupe supplémentaire: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@ -167,9 +170,9 @@
|
|||
<string name="settings_su_reauth_summary">Redemander une authentification pour autoriser l’accès en super‑utilisateur après une mise à jour de l’application</string>
|
||||
<string name="settings_su_tapjack_title">Activer la protection contre le détournement du tapotement</string>
|
||||
<string name="settings_su_tapjack_summary">Le dialogue d’invite du super‑utilisateur ne répondra pas à la saisie lorsqu’il est masqué par une autre fenêtre ou une surcouche</string>
|
||||
<string name="settings_su_biometric_title">Activer l’authentification biométrique</string>
|
||||
<string name="settings_su_biometric_summary">Utiliser l’authentification biométrique pour autoriser les accès en super‑utilisateur</string>
|
||||
<string name="no_biometric">L’appareil n’est pas pris en charge ou alors aucun paramètre biométrique n’est activé</string>
|
||||
<string name="settings_su_auth_title">Authentification utilisateur</string>
|
||||
<string name="settings_su_auth_summary">Demander l’authentification utilisateur lors de la requête super-utilisateur</string>
|
||||
<string name="settings_su_auth_insecure">Aucun méthode d’authentification utilisateur n’est configurée sur le périphérique</string>
|
||||
<string name="settings_customization">Personnalisation</string>
|
||||
<string name="setting_add_shortcut_summary">Ajouter un joli raccourci dans l’écran d’accueil au cas où le nom et l’icône seraient difficiles à reconnaître après avoir masqué l’application</string>
|
||||
<string name="settings_doh_title">DNS sur HTTPS</string>
|
||||
|
|
|
@ -165,9 +165,6 @@
|
|||
<string name="settings_su_reauth_summary">एक ऐप अपडेट होने के बाद सुपरयूज़र अनुमति प्रमाणित करें</string>
|
||||
<string name="settings_su_tapjack_title">टैपजैकिंग सुरक्षा</string>
|
||||
<string name="settings_su_tapjack_summary">सुपरयुसर प्रॉम्प्ट डायलॉग किसी अन्य विंडो या ओवरले द्वारा अस्पष्ट होने पर इनपुट का जवाब नहीं देगा</string>
|
||||
<string name="settings_su_biometric_title">बायोमेट्रिक प्रमाणीकरण सक्षम करें</string>
|
||||
<string name="settings_su_biometric_summary">सुपरयूज़र अनुरोधों की अनुमति देने के लिए बायोमेट्रिक प्रमाणीकरण का उपयोग करें</string>
|
||||
<string name="no_biometric">असमर्थित डिवाइस या कोई बायोमेट्रिक सेटिंग सक्षम नहीं हैं</string>
|
||||
<string name="settings_customization">कस्टमाईजेशन</string>
|
||||
<string name="setting_add_shortcut_summary">ऐप को छिपाने के बाद नाम और आइकन को पहचानना मुश्किल है, तो होम स्क्रीन में एक सुंदर शॉर्टकट जोड़ें</string>
|
||||
<string name="settings_doh_title">HTTPS पर DNS</string>
|
||||
|
|
|
@ -142,9 +142,6 @@
|
|||
<string name="superuser_notification">Superuser obavijest</string>
|
||||
<string name="settings_su_reauth_title">Ponovno provjerite autentičnost nakon ažuriranja</string>
|
||||
<string name="settings_su_reauth_summary">Ponovno provjerite autentičnost Superuser-a dopuštenja nakon ažuriranja aplikacije</string>
|
||||
<string name="settings_su_biometric_title">Omogući biometrijsku provjeru autentičnosti</string>
|
||||
<string name="settings_su_biometric_summary">Koristi biometrijsku provjeru autentičnosti da biste omogućili zahtjeve Superuser-a</string>
|
||||
<string name="no_biometric">Nepodržani uređaj ili nije omogućena biometrijska provjera autentičnosti</string>
|
||||
<string name="settings_customization">Prilagodba</string>
|
||||
<string name="setting_add_shortcut_summary">Dodajte lijepi prečac na početni zaslon u slučaju da je naziv i ikonu teško prepoznati nakon skrivanja aplikacije</string>
|
||||
<string name="settings_doh_title">DNS preko HTTPS-a</string>
|
||||
|
|
|
@ -165,9 +165,6 @@
|
|||
<string name="settings_su_reauth_summary">Az appok frissítés után kérjenek ismét Superuser engedélyeket</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking védelem</string>
|
||||
<string name="settings_su_tapjack_summary">A Superuser kérés párbeszédpanel nem válaszol a bevitelre, ha más ablak vagy átfedés eltakarja</string>
|
||||
<string name="settings_su_biometric_title">Biometrikus azonosítás</string>
|
||||
<string name="settings_su_biometric_summary">Használjon biometrikus hitelesítést a Superuser kérések engedélyezéséhez</string>
|
||||
<string name="no_biometric">Nem támogatott eszköz, vagy nincsenek engedélyezve a biometrikus beállítások</string>
|
||||
<string name="settings_customization">Testreszabás</string>
|
||||
<string name="setting_add_shortcut_summary">Adjon hozzá egy szép parancsikont a kezdőképernyőhöz arra az esetre, ha a nevet és az ikont nehéz felismerni az alkalmazás elrejtése után</string>
|
||||
<string name="settings_doh_title">DNS HTTPS-en keresztül</string>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">Aplikasi</string>
|
||||
|
||||
<string name="home_notice_content">Unduh Magisk HANYA dari halaman GitHub resmi kami. File dari sumber yang tidak dikenal bisa berbahaya!</string>
|
||||
<string name="home_notice_content">Unduh Magisk HANYA dari halaman GitHub resmi kami. File dari sumber yang tidak dikenal dapat berbahaya!</string>
|
||||
<string name="home_support_title">Dukung kami</string>
|
||||
<string name="home_follow_title">Ikuti Kami</string>
|
||||
<string name="home_item_source">Sumber</string>
|
||||
|
@ -113,6 +113,8 @@
|
|||
<string name="suspend_text_zygisk">Modul ditangguhkan karena %1$s tidak diaktifkan</string>
|
||||
<string name="zygisk_module_unloaded">Modul Zygisk tidak dimuat karena ketidakcocokan</string>
|
||||
<string name="module_empty">Tidak ada modul terpasang</string>
|
||||
<string name="confirm_install">Pasang modul %1$s?</string>
|
||||
<string name="confirm_install_title">Konfirmasi Pasang</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Mode tema</string>
|
||||
|
@ -165,9 +167,6 @@
|
|||
<string name="settings_su_reauth_summary">Autentikasi ulang izin akses superuser setelah aplikasi ditingkatkan</string>
|
||||
<string name="settings_su_tapjack_title">Aktifkan perlindungan tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">Dialog permintaan superuser tidak akan menanggapi masukan saat terhalangi oleh lapisan atau jendela lainnya</string>
|
||||
<string name="settings_su_biometric_title">Aktifkan autentikasi biometrik</string>
|
||||
<string name="settings_su_biometric_summary">Gunakan autentikasi biometrik untuk mengizinkan permintaan superuser</string>
|
||||
<string name="no_biometric">Perangkat tidak mendukung atau setelan biometrik tidak diaktifkan</string>
|
||||
<string name="settings_customization">Personalisasi</string>
|
||||
<string name="setting_add_shortcut_summary">Tambahkan pintasan yang menarik pada layar utama seandainya nama dan ikon sulit untuk dikenali setelah menyembunyikan aplikasi</string>
|
||||
<string name="settings_doh_title">DNS melalui HTTPS</string>
|
||||
|
@ -219,6 +218,7 @@
|
|||
<string name="setup_fail">Penyiapan gagal</string>
|
||||
<string name="env_fix_title">Perlu penyiapan tambahan</string>
|
||||
<string name="env_fix_msg">Perangkat Anda membutuhkan pengaturan tambahan untuk Magisk agar berfungsi dengan benar. Apakah Anda ingin melanjutkan dan Menyalakan ulang?</string>
|
||||
<string name="env_full_fix_msg">Perangkat Anda membutuhkan pemasangan ulang Magisk agar berfungsi dengan baik. Silakan pasang ulang Magisk dalam aplikasi, mode recovery tidak dapat memperoleh info perangkat yang benar.</string>
|
||||
<string name="setup_msg">Memproses penyiapan lingkungan…</string>
|
||||
<string name="authenticate">Autentikasi</string>
|
||||
<string name="unsupport_magisk_title">Versi Magisk tidak didukung</string>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<string name="loading">Caricamento…</string>
|
||||
<string name="update">Aggiorna</string>
|
||||
<string name="not_available">N/D</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="hide">Nascondi</string>
|
||||
<string name="home_package">Pacchetto</string>
|
||||
<string name="home_app_title">App</string>
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
|||
<string name="install_inactive_slot_msg">Questo dispositivo verrà FORZATO ad avviarsi usando lo slot inattivo!\nUsa questo metodo solo dopo che un aggiornamento OTA è stato installato.\nVuoi continuare?</string>
|
||||
<string name="setup_title">Configurazione aggiuntiva</string>
|
||||
<string name="select_patch_file">Seleziona e aggiorna un file</string>
|
||||
<string name="patch_file_msg">Seleziona un\'immagine in formato .img o un file ODIN .tar</string>
|
||||
<string name="patch_file_msg">Seleziona un\'immagine in formato .img, un file .tar di ODIN o un file payload.bin</string>
|
||||
<string name="reboot_delay_toast">Riavvio fra 5 secondi…</string>
|
||||
<string name="flash_screen_title">Installazione</string>
|
||||
|
||||
|
@ -113,6 +113,8 @@
|
|||
<string name="module_empty">Nessun modulo installato</string>
|
||||
|
||||
<!--Settings -->
|
||||
<string name="confirm_install">Vuoi installare il modulo %1$s?</string>
|
||||
<string name="confirm_install_title">Conferma installazione</string>
|
||||
<string name="settings_dark_mode_title">Impostazioni tema</string>
|
||||
<string name="settings_dark_mode_message">Seleziona il tema che si adatta meglio al tuo stile!</string>
|
||||
<string name="settings_dark_mode_light">Chiaro</string>
|
||||
|
@ -163,9 +165,9 @@
|
|||
<string name="settings_su_reauth_summary">Richiedi nuovamente i permessi di root dopo l\'aggiornamento di un\'app</string>
|
||||
<string name="settings_su_tapjack_title">Protezione anti-tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">La schermata di richiesta dei permessi di root non risponderà al tocco mentre è oscurata da altre finestre o elementi in sovraimpressione</string>
|
||||
<string name="settings_su_biometric_title">Autenticazione biometrica</string>
|
||||
<string name="settings_su_biometric_summary">Utilizza l\'autenticazione biometrica per accettare le richieste di accesso root</string>
|
||||
<string name="no_biometric">Il dispositivo non è supportato o le impostazioni biometriche sono disattivate</string>
|
||||
<string name="settings_su_auth_title">Autenticazione utente</string>
|
||||
<string name="settings_su_auth_summary">Richiedi l\'autenticazione dell\'utente per le richieste di accesso root</string>
|
||||
<string name="settings_su_auth_insecure">Sul dispositivo non è configurato alcun metodo di autenticazione</string>
|
||||
<string name="settings_customization">Personalizzazione</string>
|
||||
<string name="setting_add_shortcut_summary">Aggiungi un collegamento alla schermata iniziale se il nome e l\'icona sono difficili da riconoscere dopo aver nascosto l\'app</string>
|
||||
<string name="settings_doh_title">DNS over HTTPS</string>
|
||||
|
@ -217,6 +219,7 @@
|
|||
<string name="setup_fail">Configurazione fallita</string>
|
||||
<string name="env_fix_title">Configurazione aggiuntiva richiesta</string>
|
||||
<string name="env_fix_msg">Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Vuoi procedere e riavviare il dispositivo?</string>
|
||||
<string name="env_full_fix_msg">È necessario eseguire di nuovo il flash di Magisk per farlo funzionare correttamente. Reinstalla Magisk utilizzando l\'app, in modalità recovery non è possibile ottenere informazioni corrette sul dispositivo.</string>
|
||||
<string name="setup_msg">Configurazione dell\'ambiente in corso…</string>
|
||||
<string name="authenticate">Autentica</string>
|
||||
<string name="unsupport_magisk_title">Versione di Magisk non supportata</string>
|
||||
|
@ -228,6 +231,7 @@
|
|||
<string name="unsupport_nonroot_stub_msg">Dal momento che i permessi di root sono stati persi, l\'app di Magisk nascosta non può più funzionare. Ripristina l\'APK originale.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Consenti l\'accesso alla memoria del dispositivo per abilitare questa opzione</string>
|
||||
<string name="post_notifications_denied">Consenti l\'invio di notifiche per abilitare questa opzione</string>
|
||||
<string name="install_unknown_denied">Consenti l\'installazione di app sconosciute per abilitare questa funzionalità</string>
|
||||
<string name="add_shortcut_title">Aggiungi collegamento alla schermata iniziale</string>
|
||||
<string name="add_shortcut_msg">Dopo aver nascosto quest\'app, il suo nome e la sua icona potrebbero diventare difficili da riconoscere. Vuoi aggiungere una scorciatoia alla schermata principale?</string>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
<string name="install">התקנה</string>
|
||||
<string name="section_home">בית</string>
|
||||
<string name="section_theme">עיצוב</string>
|
||||
<string name="denylist">רשימת דחייה</string>
|
||||
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">אין חיבור זמין</string>
|
||||
|
@ -18,10 +20,11 @@
|
|||
<string name="hide">הסתרה</string>
|
||||
<string name="home_package">חבילה</string>
|
||||
<string name="home_app_title">יישום</string>
|
||||
<string name="home_notice_content">הורד את Magisk רק מהדף הרשמי של GitHub. קבצים ממקורות לא ידועים יכולים להיות זדוניים!</string>
|
||||
<string name="home_support_title">תמיכה בנו</string>
|
||||
<string name="home_notice_content">יש להוריד את Magisk רק מהדף הרשמי של GitHub. קבצים ממקורות לא ידועים יכולים להיות זדוניים!</string>
|
||||
<string name="home_support_title">תמיכה בנו</string>
|
||||
<string name="home_follow_title">עקבו אחרינו</string>
|
||||
<string name="home_item_source">מקור</string>
|
||||
<string name="home_support_content">Magisk היה ותמיד יהיה בעל קוד מקור פתוח. עם זאת אתה יכול להראות לנו שאכפת לך על ידי שליחת תרומה קטנה.</string>
|
||||
<string name="home_support_content">Magisk היה ותמיד יהיה בעל קוד מקור פתוח. עם זאת באפשרותך להראות לנו שאכפת לך על ידי שליחת תרומה קטנה.</string>
|
||||
<string name="home_installed_version">מותקנת</string>
|
||||
<string name="home_latest_version">אחרונה</string>
|
||||
<string name="invalid_update_channel">ערוץ עדכון לא חוקי</string>
|
||||
|
@ -31,18 +34,19 @@
|
|||
<!--Install-->
|
||||
<string name="keep_force_encryption">שמירה על הצפנה בכח</string>
|
||||
<string name="keep_dm_verity">שמירה על AVB 2.0/dm-verity</string>
|
||||
<string name="patch_vbmeta">תיקון vbmeta בתמונת האתחול</string>
|
||||
<string name="recovery_mode">מצב שחזור</string>
|
||||
<string name="install_options_title">אפשרויות</string>
|
||||
<string name="install_method_title">שיטה</string>
|
||||
<string name="install_next">הבא</string>
|
||||
<string name="install_start">צא לדרך</string>
|
||||
<string name="manager_download_install">לחץ להורדה והתקנה</string>
|
||||
<string name="manager_download_install">לחיצה להורדה והתקנה</string>
|
||||
<string name="direct_install">התקנה ישירה (מומלץ)</string>
|
||||
<string name="install_inactive_slot">התקן לחריץ לא פעיל (לאחר OTA)</string>
|
||||
<string name="install_inactive_slot">התקנה לחריץ לא פעיל (לאחר OTA)</string>
|
||||
<string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string>
|
||||
<string name="setup_title">התקנה נוספת</string>
|
||||
<string name="select_patch_file">בחר והתקן קובץ</string>
|
||||
<string name="patch_file_msg">בחר תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string>
|
||||
<string name="select_patch_file">בחירה והתקנת קובץ</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string>
|
||||
<string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string>
|
||||
<string name="flash_screen_title">התקנה</string>
|
||||
|
||||
|
@ -52,7 +56,7 @@
|
|||
<string name="deny">דחה</string>
|
||||
<string name="prompt">מיידי</string>
|
||||
<string name="grant">הענק</string>
|
||||
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nדחה באם אינך בטוח!</string>
|
||||
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string>
|
||||
<string name="forever">לצמיתות</string>
|
||||
<string name="once">פעם אחת</string>
|
||||
<string name="tenmin">10 דקות</string>
|
||||
|
@ -76,13 +80,16 @@
|
|||
<string name="superuser_policy_none">לא נתבקשו הרשאות משתמש על על ידי שום יישום</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">הינך ללא יומן רישום, נסה להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string>
|
||||
<string name="log_data_none">הינך ללא יומן רישום, יש לנסות להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string>
|
||||
<string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string>
|
||||
<string name="menuSaveLog">שמור יומן רישום</string>
|
||||
<string name="menuClearLog">נקה יומן רישום כעת</string>
|
||||
<string name="menuSaveLog">שמירת יומן רישום</string>
|
||||
<string name="menuClearLog">ניקוי יומן רישום כעת</string>
|
||||
<string name="logs_cleared">יומני רישום נוקו בהצלחה</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">יעד UID: %1$d</string>
|
||||
<string name="target_pid">מציב ns יעד PID: %s</string>
|
||||
<string name="selinux_context">הקשר SELinux: %s</string>
|
||||
<string name="supp_group">קבוצה משלימה: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
|
@ -100,35 +107,47 @@
|
|||
<string name="reboot_download">אתחול מצב הורדה</string>
|
||||
<string name="reboot_edl">אתחול למצב EDL</string>
|
||||
<string name="module_version_author">%1$s מאת %2$s</string>
|
||||
<string name="module_state_remove">הסר</string>
|
||||
<string name="module_state_restore">שחזר</string>
|
||||
<string name="module_action_install_external">התקן מהאחסון</string>
|
||||
<string name="module_state_remove">הסרה</string>
|
||||
<string name="module_state_restore">שיחזור</string>
|
||||
<string name="module_action_install_external">התקנה מהאחסון</string>
|
||||
<string name="update_available">עדכונים זמינים</string>
|
||||
<string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string>
|
||||
<string name="suspend_text_zygisk">המודול הושעה כי %1$s אינו מופעל</string>
|
||||
<string name="zygisk_module_unloaded">מודול Zygisk לא נטען עקב חוסר תאימות</string>
|
||||
<string name="module_empty">לא מותקן מודול</string>
|
||||
<string name="confirm_install">להתקין מודול %1$s?</string>
|
||||
<string name="confirm_install_title">אישור התקנה</string>
|
||||
|
||||
<!--Settings -->
|
||||
<string name="settings_dark_mode_title">מצב עיצוב</string>
|
||||
<string name="settings_dark_mode_message">בחר מצב המתאים ביותר לסגנון שלך!</string>
|
||||
<string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>
|
||||
<string name="settings_dark_mode_light">תמיד בהיר</string>
|
||||
<string name="settings_dark_mode_system">בהתאם למערכת</string>
|
||||
<string name="settings_dark_mode_dark">תמיד כהה</string>
|
||||
<string name="settings_download_path_title">נתיב הורדה</string>
|
||||
<string name="settings_download_path_message">הקבצים ישמרו אל %1$s</string>
|
||||
<string name="settings_hide_app_title">הסתר את היישום Magisk</string>
|
||||
<string name="settings_hide_app_summary">התקן יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
|
||||
<string name="settings_restore_app_title">שחזר את יישום Magisk</string>
|
||||
<string name="settings_restore_app_summary">בטל את הסתרת היישום ושחזר אותה ל-APK המקורי</string>
|
||||
<string name="language">שפה</string>
|
||||
<string name="settings_hide_app_title">הסתרת היישום Magisk</string>
|
||||
<string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
|
||||
<string name="settings_restore_app_title">שיחזור היישום Magisk</string>
|
||||
<string name="settings_restore_app_summary">יש לבטל את הסתרת היישום ולשחזור אותו ל-APK המקורי</string>
|
||||
<string name="language">שפה</string>
|
||||
<string name="system_default">(ברירת מחדל מערכת)</string>
|
||||
<string name="settings_check_update_title">בדוק עדכונים</string>
|
||||
<string name="settings_check_update_title">בדיקת עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדוק מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_update_channel_title">ערוץ עדכון</string>
|
||||
<string name="settings_update_stable">יציב</string>
|
||||
<string name="settings_update_beta">בטא</string>
|
||||
<string name="settings_update_custom">ערוץ מותאם אישית</string>
|
||||
<string name="settings_update_custom_msg">הזן כתובת מותאמת אישית</string>
|
||||
<string name="settings_update_custom">מותאם אישית</string>
|
||||
<string name="settings_update_custom_msg">הזנת כתובת מותאמת אישית</string>
|
||||
<string name="settings_zygisk_summary">הפעל חלקים של Magisk בדמון zygote</string>
|
||||
<string name="settings_denylist_title">אכיפת רשימת דחייה</string>
|
||||
<string name="settings_denylist_summary">כל השינויים של Magisk יוחזרו לתהליכים ברשימת הדחייה</string>
|
||||
<string name="settings_denylist_error">תכונה זו דורשת הפעלה של %1$s</string>
|
||||
<string name="settings_denylist_config_title">הגדרת רשימת הדחייה</string>
|
||||
<string name="settings_denylist_config_summary">בחירת התהליכים שייכללו ברשימת הדחייה</string>
|
||||
<string name="settings_hosts_title">מארחים חסרי מערכת</string>
|
||||
<string name="settings_hosts_summary">מארחים חסרי מערכת תומכים ביישומים חוסמי פרסומות</string>
|
||||
<string name="settings_hosts_toast">הוסף מודול מארחים חסרי מערכת</string>
|
||||
<string name="settings_hosts_toast">הוספת מודול מארחים חסרי מערכת</string>
|
||||
<string name="settings_app_name_hint">שם חדש</string>
|
||||
<string name="settings_app_name_helper">היישום יארז מחדש בשם זה</string>
|
||||
<string name="settings_app_name_error">פורמט לא חוקי</string>
|
||||
|
@ -146,15 +165,15 @@
|
|||
<string name="auto_response">תגובה אוטומטית</string>
|
||||
<string name="request_timeout">בקש פסק זמן</string>
|
||||
<string name="superuser_notification">התראות משתמש על</string>
|
||||
<string name="settings_su_reauth_title">אמת מחדש לאחר שדרוג</string>
|
||||
<string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string>
|
||||
<string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>
|
||||
<string name="settings_su_tapjack_title">הפעלת הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או כיסוי אחר</string>
|
||||
<string name="settings_su_biometric_title">אפשר אימות ביומטרי</string>
|
||||
<string name="settings_su_biometric_summary">השתמש באימות ביומטרי כדי לאפשר בקשות לשימוש במשתמש על</string>
|
||||
<string name="no_biometric">התקן לא נתמך או הגדרות ביומטריות אינן מאופשרות</string>
|
||||
<string name="settings_su_auth_title">אימות משתמש</string>
|
||||
<string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string>
|
||||
<string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string>
|
||||
<string name="settings_customization">התאמה אישית</string>
|
||||
<string name="setting_add_shortcut_summary">הוסף קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
|
||||
<string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
|
||||
<string name="settings_doh_title">DNS על HTTPS</string>
|
||||
<string name="settings_doh_description">עקיפת DNS מורעל במדינות מסוימות</string>
|
||||
<string name="multiuser_mode">מצב מרובה משתמשים</string>
|
||||
|
@ -175,13 +194,17 @@
|
|||
<!--Notifications-->
|
||||
<string name="update_channel">עדכוני Magisk</string>
|
||||
<string name="progress_channel">התראות התקדמות</string>
|
||||
<string name="updated_channel">העדכון הושלם</string>
|
||||
<string name="download_complete">ההורדה הושלמה</string>
|
||||
<string name="download_file_error">שגיאה בהורדת קובץ</string>
|
||||
<string name="magisk_update_title">עדכון Magisk זמין!</string>
|
||||
<string name="updated_title">עדכון Magisk</string>
|
||||
<string name="updated_text">הקשה כדי לפתוח יישום</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">כן</string>
|
||||
<string name="no">לא</string>
|
||||
<string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string>
|
||||
<string name="download">הורדה</string>
|
||||
<string name="reboot">הפעלה מחדש</string>
|
||||
<string name="release_notes">הערות שחרור</string>
|
||||
|
@ -189,7 +212,7 @@
|
|||
<string name="done">בוצע!</string>
|
||||
<string name="failure">נכשל</string>
|
||||
<string name="hide_app_title">מסתיר את יישום Magisk…</string>
|
||||
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
|
||||
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
|
||||
<string name="complete_uninstall">הסרה מלאה</string>
|
||||
<string name="restore_img">שיחזור תמונות</string>
|
||||
<string name="restore_img_msg">משחזר…</string>
|
||||
|
@ -197,8 +220,9 @@
|
|||
<string name="restore_fail">גיבוי מקורי אינו קיים!</string>
|
||||
<string name="setup_fail">ההתקנה כשלה</string>
|
||||
<string name="env_fix_title">דורש התקנה נוספת</string>
|
||||
<string name="env_fix_msg">המכשיר שלך זקוק להתקנה נוספת כדי ש-Magisk יפעל כראוי. האם ברצונך להמשיך ולהפעיל מחדש?</string>
|
||||
<string name="setup_msg">הגדרת סביבת ריצה…</string>
|
||||
<string name="env_fix_msg">ההתקן שלך זקוק להתקנה נוספת כדי ש-Magisk יפעל כראוי. האם ברצונך להמשיך ולהפעיל מחדש?</string>
|
||||
<string name="env_full_fix_msg">ההתקן שלך זקוק לצריבה מחדש של Magisk כדי לעבוד כראוי. נא להתקין מחדש את Magisk בתוך היישום, מצב השחזור אינו יכול לקבל מידע נכון על ההתקן.</string>
|
||||
<string name="setup_msg">הגדרת סביבת ריצה…</string>
|
||||
<string name="authenticate">אימות</string>
|
||||
<string name="unsupport_magisk_title">גרסת Magisk אינה נתמכת</string>
|
||||
<string name="unsupport_magisk_msg">גרסה זו של היישום אינה תומכת ביישום Magisk הנמוך מ- %1$s.\n\nהיישום יתנהג כאילו Magisk אינו מותקן,יש לשדרג את Magisk במהירות האפשריות.</string>
|
||||
|
@ -208,8 +232,12 @@
|
|||
<string name="unsupport_external_storage_msg">Magisk מותקן באחסון החיצוני. נא להעביר את היישום לאחסון הפנימי.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">היישום אינו יכול להמשיך לעבוד במצב הנסתר מכיוון שגישת השורש אבדה. נא לשחזר אותו חזרה ל-APK המקורי.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">הענק הרשאת אחסון להפעלת פונקציונליות זו</string>
|
||||
<string name="add_shortcut_title">הוסף קיצור דרך למסך הבית</string>
|
||||
<string name="external_rw_permission_denied">הענת הרשאת אחסון להפעלת פונקציה זו</string>
|
||||
<string name="post_notifications_denied">הענקת הרשאה להתראות כדי להפעיל פונקציה זו</string>
|
||||
<string name="install_unknown_denied">יש לאפשר "התקנה ממקורות לא ידועים" בכדי להפעיל פונקציה זו</string>
|
||||
<string name="add_shortcut_title">הוספת קיצור דרך למסך הבית</string>
|
||||
<string name="add_shortcut_msg">לאחר הסתרת היישום הזה, השם והסמליל שלה עשויים להיות קשים לזיהוי. האם ברצונך להוסיף קיצור דרך יפה למסך הבית?</string>
|
||||
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
|
||||
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
|
||||
<string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string>
|
||||
<string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>
|
||||
</resources>
|
||||
|
|
|
@ -165,9 +165,9 @@
|
|||
<string name="settings_su_reauth_summary">アプリのアップグレード後にスーパーユーザー権限を再認証します</string>
|
||||
<string name="settings_su_tapjack_title">タップジャッキング保護を有効にする</string>
|
||||
<string name="settings_su_tapjack_summary">他のウィンドウやオーバーレイで表示されている間は、スーパーユーザー権限の要求ダイアログが入力に応答しないようにします</string>
|
||||
<string name="settings_su_biometric_title">生体認証の有効化</string>
|
||||
<string name="settings_su_biometric_summary">スーパーユーザー権限の要求時に生体認証を使用します</string>
|
||||
<string name="no_biometric">生体認証に対応していないか、有効化されていません</string>
|
||||
<string name="settings_su_auth_title">ユーザー認証</string>
|
||||
<string name="settings_su_auth_summary">スーパーユーザー権限の要求でユーザー認証を行ないます</string>
|
||||
<string name="settings_su_auth_insecure">端末の認証方法が設定されていません</string>
|
||||
<string name="settings_customization">カスタマイズ</string>
|
||||
<string name="setting_add_shortcut_summary">アプリを隠した後に見つけられなくなったときは、ここでホーム画面にショートカットを追加できます</string>
|
||||
<string name="settings_doh_title">DNS over HTTPS</string>
|
||||
|
|
|
@ -152,9 +152,6 @@
|
|||
<string name="settings_su_reauth_summary">სუპერმომხმარებლის ნებართვის რეაუტენთიფიკაცია აპის განახლების შემდეგ</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking-სგან თავის დაცვა</string> <!-- TODO: tapjacking has no direct translation; must make something up later -->
|
||||
<string name="settings_su_tapjack_summary">superuser-ის დიალოგის ღილაკებზე დაჭერა არ იქნება შესაძლებელი თუ სხვა აპი არის მასზე გადახურული</string>
|
||||
<string name="settings_su_biometric_title">ბიომეტრიკული აუტენთიფიკაციის ჩართვა</string>
|
||||
<string name="settings_su_biometric_summary">ბიომეტრიკული აუტენთიფიკაციის გამოყენება სუპერმომხმარებლის ნებართვის გასაცემად</string>
|
||||
<string name="no_biometric">შეუთავსებელი მოწყობილობა ან არასწორად დაყენებული ბიომეტრიკული პარამეტრები</string>
|
||||
<string name="settings_customization">პერსონალიზაცია</string>
|
||||
<string name="setting_add_shortcut_summary">ლამაზი ხატულის დამატება საწყისს ეკრანზე, იმ შემთხვევაში თუ აპის ამოცნობა არის რთული დამალვის შემდეგ</string>
|
||||
<string name="settings_doh_title">DNS HTTPS-ზე</string>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue