Compare commits
1246 Commits
v0.5.0
...
private/master
| Author | SHA1 | Date | |
|---|---|---|---|
| e7c368ad7e | |||
| 16a14e6ac2 | |||
| a2f29ec9dc | |||
| 82df985201 | |||
| 740db4f1ab | |||
| c4711dec00 | |||
| 75d8d172aa | |||
| 81fb8c395d | |||
| e93efa9627 | |||
| 03a95e6652 | |||
| c117d380a3 | |||
| 5bdb945406 | |||
| 3064f51d25 | |||
| edf376e3dd | |||
| fd54dc1356 | |||
| 84af8bb285 | |||
| e50a62d915 | |||
| ad5a575732 | |||
| 06e93fd7bd | |||
| dff66547b4 | |||
| 6fef27bb56 | |||
| 505e966653 | |||
| 02cac5df10 | |||
| 6ee3348f50 | |||
| 8780597091 | |||
| c98899d501 | |||
| 7654653a25 | |||
| 0c1a0ef408 | |||
| e7f2192579 | |||
| 5c3b1e0b07 | |||
| 6ad59f2e2b | |||
| 03dd778fac | |||
| 6b833a38d1 | |||
| a83885353c | |||
| 10d7c7c06d | |||
| 3b575271cb | |||
| 5ad92413f3 | |||
| 7fbdb39319 | |||
| 92abdf7fb7 | |||
| ea0c666cfe | |||
| b5e395a039 | |||
| e530072a07 | |||
| f85cbe1ca5 | |||
| 5f9126eb2f | |||
| d77c57ede0 | |||
| 02f9a0d1d7 | |||
| 53192f56ca | |||
| 01d89cbb48 | |||
| be55cb974b | |||
| 34c394ed19 | |||
| 1433a21abd | |||
| ec28794655 | |||
| 72122b7cbf | |||
| 392a7990d2 | |||
| 0bdcf8b4ba | |||
| 8295440bfd | |||
| e52aa6daf4 | |||
| ef067ef5b9 | |||
| 76686db6a1 | |||
| 6bc5046773 | |||
| 1a5cfd8f58 | |||
| bf76962d23 | |||
| a8acca6a38 | |||
| 7891c627c1 | |||
| ee55145e45 | |||
| 5cda584568 | |||
| 031890deb6 | |||
| 0f149c9b33 | |||
| 41f22df16f | |||
| a11e5e623d | |||
| 489ffa1679 | |||
| f977d181a8 | |||
| 2249d237dd | |||
| c52457c80e | |||
| 3904cbf789 | |||
| 759ae9fca0 | |||
| 06954591c7 | |||
| bbdae74567 | |||
| 154e54d833 | |||
| f18e0f4a62 | |||
| 2b19bc850d | |||
| a0fb30a3ad | |||
| e5387ff5f7 | |||
| 123d8a2637 | |||
| 6c72659bd8 | |||
| 44d89506d4 | |||
| d8a5cdfb78 | |||
| 5b5e2b26f9 | |||
| 9fa51c8a1d | |||
| 7633d2156a | |||
| 400e059455 | |||
| f3d6bb4f22 | |||
| c50b5e7448 | |||
| 5b75b361f6 | |||
| 146290283f | |||
| 35bcbc69a2 | |||
| a58dcc6f19 | |||
| b4595b70d6 | |||
| 347b6faa82 | |||
| 162a6b70af | |||
| 3b0a05126b | |||
| 02da884f17 | |||
| c4d8bba5ca | |||
| b979db9acb | |||
| b35c120bd1 | |||
| 9e438566e4 | |||
| 3c518707eb | |||
| f8bee14808 | |||
| 9f60bb8f3e | |||
| 7eb752654b | |||
| a9d27acce3 | |||
| 3234c41333 | |||
| 339b28e7fb | |||
| c624c6d860 | |||
| 0cac39c19f | |||
| 37bd5c32ab | |||
| fc02f96b18 | |||
| 27a83c4915 | |||
| 68006c4e68 | |||
| b97a808e7b | |||
| 484f75374d | |||
| f45f6faf96 | |||
| 57fef90bc3 | |||
| e51ccdf2f0 | |||
| f39cf0f0f5 | |||
| 817589f710 | |||
| b6e79532a9 | |||
| 61ca48ccdd | |||
| 3b36ec550d | |||
| 39cae6cc2d | |||
| 4478042f40 | |||
| a1ee1458e3 | |||
| 78b50b0881 | |||
| 2af88056f2 | |||
| b20215731f | |||
| a76959c295 | |||
| 83c94c044d | |||
| 949829ae6b | |||
| 9d7f54be82 | |||
| aa8d27f679 | |||
| b58a716daa | |||
| ce9d080469 | |||
| 34d0ce69fa | |||
| b8842054a5 | |||
| 966b39e039 | |||
| 2fcbe44953 | |||
| f2795c972a | |||
| 1a67d4db11 | |||
| 6c4c2a175b | |||
| 0a7e6cce87 | |||
| 3e47859d88 | |||
| 8e405e3996 | |||
| 3b21442e25 | |||
| dd895157c6 | |||
| 436daeb87c | |||
| 00f5652db9 | |||
| c452a3548f | |||
| 1dc1932a84 | |||
| 3941986ab2 | |||
| 0f95e19c76 | |||
| bb09c558b6 | |||
| ed7e9f4a2e | |||
| 6b0fddd421 | |||
| 4dbd9d70d2 | |||
| 53c4659044 | |||
| fb41ad38f6 | |||
| ebcb9a9562 | |||
| 678a12490b | |||
| 4978461ac7 | |||
| d63965cc02 | |||
| 2a27491f9f | |||
| 748f0494b4 | |||
| c9575449da | |||
| 3e7c5e2948 | |||
| 21e64eb54a | |||
| c4b2f8582e | |||
| c1f2aae90d | |||
| 9d09a1fe5d | |||
| 7d006a19c3 | |||
| d23d10601e | |||
| b4db9ebdb0 | |||
| d8050dc483 | |||
| 4011a4c3ee | |||
| c35dd1a2c7 | |||
| bc6e28cabe | |||
| 68492bf591 | |||
| 29e7bab4e4 | |||
| 5be4d2a104 | |||
| 0585000cf3 | |||
| 045e4d23fb | |||
| 7b5d96189e | |||
| 5b46359057 | |||
| 44de584be3 | |||
| 52a9be3a7e | |||
| 8d119fd710 | |||
| b802c6977a | |||
| 1af1562473 | |||
| 0d79ac68f8 | |||
| 3ce9f72e3f | |||
| 9437e4243a | |||
| b92f9a2e4d | |||
| 6181467abb | |||
| 5b55858184 | |||
| 6dd789dedc | |||
| 8d3a144afd | |||
| 5577dff087 | |||
| f4e32bac1a | |||
| 02aada7f08 | |||
| fb05371ac2 | |||
| 9a9c0532de | |||
| 2e0f72f182 | |||
| f210bbc22a | |||
| 08c4f8bcc2 | |||
| 9062252939 | |||
| 5c79672d84 | |||
| 5e48b05270 | |||
| 28ab0af6d4 | |||
| cdb98d2175 | |||
| d95f4fe1e1 | |||
| 6e2be271c3 | |||
| bfccbaf731 | |||
| c7b4f226b3 | |||
| d142d3a25a | |||
| f8b00a4541 | |||
| 808e0ecae7 | |||
| a09cac1063 | |||
| 513af98872 | |||
| fafcaa222c | |||
| 1dd79a0b1e | |||
| bbd7e30298 | |||
| 2818fbe575 | |||
| a11db6662d | |||
| 904157a91a | |||
| 904d3980d6 | |||
| 89e2ba9f75 | |||
| 3f4dd2861e | |||
| c1a9b158e2 | |||
| 7db947eba2 | |||
| 450861d47a | |||
| 3df0106325 | |||
| 2b767eb488 | |||
| f8c77b3673 | |||
| aaaae4e719 | |||
| 679e2c0da9 | |||
| 055c1c47f6 | |||
| 275727ed90 | |||
| 257e1dd03d | |||
| 5bf2a4aed4 | |||
| dc79b4c90a | |||
| 4cdc7b348d | |||
| ddedceeded | |||
| 3179169913 | |||
| 8ef2877040 | |||
| 11b2a6b616 | |||
| 04ad0033d7 | |||
| 46e2ef125a | |||
| 9a33e3808a | |||
| 8ae451ece5 | |||
| a5d64be197 | |||
| 4482b325d7 | |||
| f46745d70c | |||
| b213de19cc | |||
| 75355f0784 | |||
| 8547159eec | |||
| d90bfb6e3e | |||
| 82ad2fbe80 | |||
| a414860626 | |||
| 392f66e938 | |||
| a40021a823 | |||
| c86f7c5356 | |||
| 3075888d26 | |||
| 7a0d3a1efe | |||
| 7b22397a82 | |||
| b2cfb5a1e9 | |||
| 283e38c30a | |||
| 02717b317c | |||
| 7fa1250a67 | |||
| 590e43c827 | |||
| 16e9c0b19a | |||
| 8661438f69 | |||
| 9049b4a090 | |||
| a2622fe3e1 | |||
| 664d5fe637 | |||
| 97f9180063 | |||
| eed8012521 | |||
| ab1e3e4302 | |||
| c12b6f39d8 | |||
| b280c03afa | |||
| 1d9991e562 | |||
| 87aae46a1f | |||
| 7e211ee9a1 | |||
| 8e8883ba37 | |||
| 02c4398e48 | |||
| ad7a8dd7dc | |||
| e3338211d6 | |||
| 7cab4b9229 | |||
| ac5f1a0d93 | |||
| 798b9d0c98 | |||
| 3ff29aa38a | |||
| f8c2b9ffb0 | |||
| 888bb8897a | |||
| 192136e66c | |||
| 5057a57f7f | |||
| d81a4e0b7f | |||
| c63a06730f | |||
| bef326d2d7 | |||
| b8e85422f0 | |||
| d050bfdc68 | |||
| 3bac176bf6 | |||
| 7c506a42ae | |||
| 2c436e2027 | |||
| df0078b725 | |||
| 09c950a890 | |||
| e7e76ed68d | |||
| 06c1eeb995 | |||
| d545d852c5 | |||
| 3486e8dcf3 | |||
| 1956b700fc | |||
| 64ad9af344 | |||
| 55fec0b82c | |||
| 8323ef5f41 | |||
| 8dbab23de7 | |||
| 825d232613 | |||
| d797a03502 | |||
| 0ef6d74514 | |||
| 6234e897a8 | |||
| fe121f59b0 | |||
| 90cf5fcdec | |||
| 8b5782a5b6 | |||
| 68a131dbeb | |||
| 1411c02e18 | |||
| 81fe3f0108 | |||
| c15cf23168 | |||
| a79dc580a5 | |||
| 8a62c6295d | |||
| 88e77e1547 | |||
| 534619bc1a | |||
| ae904753f7 | |||
| 8c4a2cb529 | |||
| 16d4893480 | |||
| a7446e2f4c | |||
| 9dcae193a9 | |||
| 36fac0f3f4 | |||
| 24a4c176c0 | |||
| 9c7f50e91e | |||
| e52bc255f3 | |||
| ae4c9887d8 | |||
| 52201e2488 | |||
| c3e2b0e002 | |||
| 709915bf59 | |||
| 9d7ec6fd60 | |||
| ee9de376a3 | |||
| b54dc6f967 | |||
| abea85d831 | |||
| 972137c035 | |||
| 1cdef5e0ee | |||
| bd7ea64b02 | |||
| 20c850c10b | |||
| 0b021e6c42 | |||
| 0d109cdd4f | |||
| 1dab9e1a7d | |||
| 593b01819d | |||
| 029aa9c01b | |||
| 149b549d8d | |||
| 786635010e | |||
| d7fe170067 | |||
| e3d4be9a5a | |||
| 4086a73727 | |||
| 483e3a760f | |||
| 2757f881dc | |||
| e2aff0ece7 | |||
| 327526330f | |||
| ea976a4d0f | |||
| 728ada5e70 | |||
| c091ac4d67 | |||
| ee4c852f1b | |||
| 1a5d334f6c | |||
| 7d72ff3514 | |||
| e224e91100 | |||
| 7c5edd1b73 | |||
| 2621415f7c | |||
| a3184c46b6 | |||
| 1575ffa6ae | |||
| ee27da3de6 | |||
| 83a7224f2d | |||
| ecea2ecdf5 | |||
| f04060b31b | |||
| d411d1966a | |||
| 507bf07104 | |||
| 09061a38bc | |||
| 611a7db2e1 | |||
| a5cf428ce5 | |||
| 31f06a2d43 | |||
| 1d7a60b630 | |||
| a2fadbe513 | |||
| dee61e191c | |||
| 32b6461c6a | |||
| 93fff42693 | |||
| 61f429896c | |||
| a9e03837a3 | |||
| 89421946af | |||
| 218af8ea54 | |||
| d0f79ca473 | |||
| ec870759cf | |||
| 0405a535c7 | |||
| 814e4ba744 | |||
| 5621c1ab58 | |||
| f1fd8bc446 | |||
| 60fdd6cda9 | |||
| 3332363a10 | |||
| 538bd3f126 | |||
| 336f985894 | |||
| ba6687355e | |||
| 983980d8da | |||
| 82d4a401fd | |||
| 76e9f42734 | |||
| 0c0035370a | |||
| a3ac136b3b | |||
| ed1509b54f | |||
| 1d0dcd097c | |||
| 785c0469ac | |||
| 7594ae5fa5 | |||
| 6b4e08fdd1 | |||
| 65435341f3 | |||
| 9bc9f963b7 | |||
| a27501371f | |||
| 9fafebc8e7 | |||
| 59d2151c92 | |||
| 1cc2a05f90 | |||
| 8aea6f5473 | |||
| 22df7e3074 | |||
| 1d5323a477 | |||
| f8d73819ea | |||
| cbe26b7291 | |||
| 93477f60c2 | |||
| 9feebbfe17 | |||
| 6e365491a9 | |||
| 2e58658129 | |||
| 256c564b91 | |||
| 96b50f52ec | |||
| 3167d8aa15 | |||
| 78fd09c728 | |||
| 4c5598cedf | |||
| c3347d94ab | |||
| 7ca4aa75a8 | |||
| 226fad5594 | |||
| d0ee1ba5af | |||
| 439e0c8284 | |||
| 7d079a8728 | |||
| 945a52653e | |||
| bdafc86990 | |||
| 57d425ab9f | |||
| 395ac8e944 | |||
| 2f801e7571 | |||
| d7636045fe | |||
| b745f10870 | |||
| d76849942c | |||
| b7a8a3ffe8 | |||
| 95d9293fe0 | |||
| 3be165a551 | |||
| cb498e2128 | |||
| 973f4d66e2 | |||
| 2c80672f6e | |||
| 2599813ef1 | |||
| 86f849a185 | |||
| 875f1f1506 | |||
| e418375963 | |||
| d528fc7f9e | |||
| 633ea97848 | |||
| 36cb899b91 | |||
| c4d849d6a3 | |||
| c8bd39b4bf | |||
| 733ba16af2 | |||
| 37f57c0c55 | |||
| 013dbd79b4 | |||
| f76d0b3258 | |||
| c2f7cdd72e | |||
| 01c37cb0ba | |||
| 0dd0af1b84 | |||
| 26aa684300 | |||
| 9669cdfb76 | |||
| 2c5e5e283e | |||
| 303921c6ea | |||
| 593291a60f | |||
| 3af8e395bd | |||
| 0b192cfa52 | |||
| fb8f20f31a | |||
| e53386cf72 | |||
| b0e9b9b307 | |||
| 789678a45d | |||
| 4ad01d3451 | |||
| 6a87daa0b3 | |||
| aebef87076 | |||
| 32ff58598f | |||
| 2d56cbe227 | |||
| fbef5f592b | |||
| 090af36f5e | |||
| 885f27fcf1 | |||
| 8dc4eaf77a | |||
| a55bef08a8 | |||
| 0dc3089739 | |||
| 789ef0d783 | |||
| 092db1106d | |||
| 5f4b5bc570 | |||
| 32581fcd5a | |||
| b14d28c406 | |||
| 1d1535dc55 | |||
| de942440e3 | |||
| b58fc39cf1 | |||
| 2e3af25dd4 | |||
| 1d541a30ae | |||
| f926714544 | |||
| f68849d3a5 | |||
| 2111232f42 | |||
| 088552bf56 | |||
| 3eabbc9770 | |||
| 8e3b8df497 | |||
| 06a5aaaa72 | |||
| 97c4f14094 | |||
| 1fa7f18235 | |||
| b309d2fd4a | |||
| 372b56bb1b | |||
| 3325a36cae | |||
| 38673bbff4 | |||
| fb51834153 | |||
| 3a932a1e8a | |||
| 53c61bcb17 | |||
| 6951b4b20d | |||
| 746f9f1a11 | |||
| 9a7344ccbe | |||
| ab2fb8747f | |||
| 9cd8cb3d54 | |||
| ba1c2845b6 | |||
| 065aa19e9e | |||
| 4c2a05c3a6 | |||
| fd45c0740c | |||
| e44bf920fa | |||
| 8a327b2dff | |||
| 6ece7e2596 | |||
| 2e2ce98be3 | |||
| fe4c2392db | |||
| 320a0971b4 | |||
| bfb70b6a05 | |||
| a68af62748 | |||
| 52bd5ce5cc | |||
| b93d486348 | |||
| d193c58e5f | |||
| 6ac2a61793 | |||
| a45c6f2197 | |||
| 71d639bf19 | |||
| 9a51472726 | |||
| 0670f298cd | |||
| aa1e98544b | |||
| fa4607e232 | |||
| cb46420c09 | |||
| d88014fa90 | |||
| 168b76cb0c | |||
| f5680c6d69 | |||
| 654a3cc7ed | |||
| 841cdc474f | |||
| 0adbea3a43 | |||
| e12bada052 | |||
| 68dbefc46f | |||
| 9d71e9b177 | |||
| 5b5801c2cf | |||
| df1cc2b8e9 | |||
| 18d399b3f7 | |||
| e9687fd182 | |||
| 79137a074c | |||
| dae55ca386 | |||
| b7f040d89a | |||
| dc69df9f4f | |||
| 6c1fbfa63b | |||
| e968a2195a | |||
| 000bcea181 | |||
| 8edf508453 | |||
| bc9cc50130 | |||
| 71091d88fc | |||
| 70c1d7e21f | |||
| c07920978e | |||
| 954b2919ac | |||
| c630f731ed | |||
| aaefa7f74e | |||
| 2ec6b471f1 | |||
| a5c5ab68d2 | |||
| fb045c501a | |||
| 6a6e411492 | |||
| 76aac330fc | |||
| 07bdf31f66 | |||
| fe14928af6 | |||
| ad0c1033a4 | |||
| 0c2448fb99 | |||
| cedda145a5 | |||
| ee73187f1a | |||
| 89f91d6800 | |||
| 6714827694 | |||
| aad73f7d19 | |||
| c5985de1c3 | |||
| 86a5b0879a | |||
| 414972d545 | |||
| 9a74ae5844 | |||
| 301980ab14 | |||
| 5dced82e5a | |||
| 9a1e4df408 | |||
| 5b08b81239 | |||
| 7fac538ba3 | |||
| ef6be74ec2 | |||
| 9f49587245 | |||
| b7b733f351 | |||
| 06bfc33e72 | |||
| fbcd55d6c5 | |||
| 2484b5f14b | |||
| 6982659658 | |||
| 9e006166a8 | |||
| 25a62e33a1 | |||
| d05ed0a56c | |||
| eaffb2755c | |||
| e0fcae2ae3 | |||
| af9ad61174 | |||
| 7c54ad54fc | |||
| aee9f1032c | |||
| f7d0605e0a | |||
| f40dcafb43 | |||
| d9cb54b285 | |||
| f738a162d3 | |||
| fda4cd6783 | |||
| ecd1604e25 | |||
| 0f061900af | |||
| c47f5ea85e | |||
| 306eb0e3c7 | |||
| e64025ded8 | |||
| c1fe2da636 | |||
| ff23f58a4f | |||
| fc2f5ffdf9 | |||
| 6dd9ed7fb0 | |||
| 2f362abb91 | |||
| 96807a64cf | |||
| 7df5f1c4c4 | |||
| cf1ede9cf7 | |||
| 729385588a | |||
| 668d5cf8f0 | |||
| 72b1b5b0f9 | |||
| fbf726c174 | |||
| c441eed847 | |||
| e8e83ed49c | |||
| cdc21b067c | |||
| 48e19f7914 | |||
| 89dd570b30 | |||
| 16474d4328 | |||
| 9db612bf03 | |||
| 7d92dbc5c0 | |||
| a9efca8687 | |||
| dbfea5d02b | |||
| a6b05c4a27 | |||
| 6d539d3404 | |||
| b2aff1efc9 | |||
| 8a20a1ef50 | |||
| 33cbfa9751 | |||
| b95a8d44d4 | |||
| 54df9d634a | |||
| 4eb9a696ff | |||
| 7b4fb4682b | |||
| da4275530d | |||
| 1c417e909a | |||
| fc53d69f82 | |||
| dda86cdb93 | |||
| 525a974e3a | |||
| b18c155e22 | |||
| 07e011092a | |||
| 6803ac0611 | |||
| af0dde5ae8 | |||
| ea6edaecc4 | |||
| eb2054bd5e | |||
| b277b3e3af | |||
| 9dc3a4e6ee | |||
| 6fbd2f1079 | |||
| 9edbc7f1d7 | |||
| 8aa75be0d3 | |||
| dc124fb15c | |||
| 9109d1ca3e | |||
| 02296f1d1c | |||
| 63e1082b97 | |||
| 285f228660 | |||
| c18cf069b1 | |||
| fc64f47589 | |||
| 562b940d91 | |||
| d658e07583 | |||
| 9121a6341c | |||
| 4bec027f11 | |||
| b9053e3057 | |||
| 0621138478 | |||
| ce42e89e25 | |||
| 46e1e4c043 | |||
| 621468a183 | |||
| d8876cf96a | |||
| 57d5bc6480 | |||
| f224918f33 | |||
| 7b290dc465 | |||
| b1412dda34 | |||
| 28e4ac8dcb | |||
| 79eeb6d703 | |||
| 0d0e735d0e | |||
| d994502d06 | |||
| dfbd7a65ae | |||
| f99f94c8d7 | |||
| 41c643496a | |||
| e5476f8a01 | |||
| 188fb188ce | |||
| c852592b34 | |||
| 3a1e0c5a63 | |||
| 6376972130 | |||
| c70c860a82 | |||
| 5a178ada74 | |||
| 230427e758 | |||
| abf1af41a3 | |||
| 61e2548bb7 | |||
| f739c54292 | |||
| 3ed84de320 | |||
| 621b4c0946 | |||
| 11be969101 | |||
| ea958cd8f7 | |||
| 56048dcdb0 | |||
| fb545947ec | |||
| df57070b70 | |||
| 9b27d7ee23 | |||
| a2d3fa6e1d | |||
| 94b670eb81 | |||
| d65ed6ced7 | |||
| db50eb7526 | |||
| d21b2018cb | |||
| 9110c07ed9 | |||
| 2298e71279 | |||
| 909bd76e08 | |||
| 50cd0c4e10 | |||
| 460fc235e3 | |||
| c38a3d9eba | |||
| b303291e94 | |||
| 7993da038e | |||
| 05bf4f5525 | |||
| db36896f92 | |||
| 16dbad8bdf | |||
| 8a4c717d24 | |||
| 442a290966 | |||
| 0785f4d0f5 | |||
| 21e325af9c | |||
| 3e9d29ea7f | |||
| 4324373e61 | |||
| 673053d291 | |||
| 5b3975f886 | |||
| 7ed8f43859 | |||
| dcbb1c0dd1 | |||
| 5d4d417f3e | |||
| 5943c6a2c6 | |||
| 6d33d72663 | |||
| 9d2b098837 | |||
| 17bc2d2331 | |||
| 1c192b8db6 | |||
| 76595233fc | |||
| 6531b80998 | |||
| 05707e29d7 | |||
| 616ed4637d | |||
| 912c340a01 | |||
| 583a2f0fad | |||
| 60015bc041 | |||
| 029f445d0a | |||
| 150416b578 | |||
| 6684576de1 | |||
| 289acc9296 | |||
| 2cf9a407e8 | |||
| 682c364647 | |||
| e70730e9a8 | |||
| 0ba6c88d69 | |||
| c56bdea1e2 | |||
| a449a01a24 | |||
| 849acfca3d | |||
| 3cd3cb0186 | |||
| feead100f2 | |||
| a9987e6ab0 | |||
| ef0a6f54b8 | |||
| c8865ad185 | |||
| 354968fba7 | |||
| f985ed2131 | |||
| be2628875f | |||
| 9430c8c580 | |||
| ea2cf5d4ff | |||
| 3b36974d84 | |||
| 41fea1d2a0 | |||
| d81fafc9f6 | |||
| 0a73177996 | |||
| c9423ef425 | |||
| 7086055ec3 | |||
| 553b35d218 | |||
| c910026308 | |||
| 35be9f14e4 | |||
| abcbec9c2a | |||
| ff6f5d7e89 | |||
| 56deea9fb3 | |||
| 1c9a139006 | |||
| 4d89c324b9 | |||
| a76ce03911 | |||
| 9ee3f46ff0 | |||
| 3343007cf8 | |||
| c42d314b76 | |||
| 8db6c2153e | |||
| 5baf54335b | |||
| d9019b8f46 | |||
| a31446557d | |||
| 321fbe22dd | |||
| 01ab912bd9 | |||
| 557bad60bc | |||
| f2dd67d87f | |||
| b8b92c8d69 | |||
| 74ff112e7a | |||
| 684bb1875c | |||
| f6fec2424c | |||
| 2889029b70 | |||
| b56b4fa813 | |||
| 00bc055d69 | |||
| 6fd291c7e3 | |||
| dbdb787076 | |||
| fc788a718d | |||
| e093fe6a06 | |||
| cdce368042 | |||
| 689847d864 | |||
| 3675580d87 | |||
| 92f494d0fe | |||
| 06d7a6d892 | |||
| e2754200af | |||
| cdb083ff48 | |||
| c3fb08d634 | |||
| 78a167aacf | |||
| 5a913fdfbb | |||
| f0a190e8d2 | |||
| a2715fb851 | |||
| 47e5b03f45 | |||
| 251141a5c3 | |||
| 6ac8f4c45d | |||
| 7ebefa7c42 | |||
| 9e4c90f220 | |||
| 50f988641b | |||
| e53b9d4790 | |||
| 027805c4d5 | |||
| c02496c4f0 | |||
| 2a83f290a5 | |||
| d4f9b0b1bc | |||
| 2452b03a49 | |||
| 2ce423b6cb | |||
| e9206158b8 | |||
| 8690e918dd | |||
| c1d702a51c | |||
| 0338ac3810 | |||
| 526fef85e4 | |||
| 49f2d8588a | |||
| 9a80992aec | |||
| 32d0890dba | |||
| b4d37f9ba2 | |||
| 5372ef8f0c | |||
| a11b654c3d | |||
| 1a9a0b3394 | |||
| 890920a57b | |||
| 7fe7de5fdf | |||
| b9b115d0ea | |||
| 08af195f11 | |||
| 71cde729fc | |||
| 077f0a03f6 | |||
| 812eb8001b | |||
| b59af683ac | |||
| 561d680e78 | |||
| 7c3eff2ba7 | |||
| 300c0a8f35 | |||
| 51bfdc0947 | |||
| a64566c0f3 | |||
| dbb9a80ea6 | |||
| e930c54246 | |||
| dfff047cbf | |||
| 44fb2b02bc | |||
| 6a7efafd9f | |||
| 241abc3956 | |||
| 1e82c879bf | |||
| a81d01d2e3 | |||
| 2230796504 | |||
| 458ca7c7cf | |||
| 3f91663ecf | |||
| 04a671382a | |||
| 945ec818e5 | |||
| ff7ac8a785 | |||
| 603105e2ea | |||
| 5475567b48 | |||
| 2aec0adb08 | |||
| 54fc3761bf | |||
| 99e1912bfe | |||
| ecc1cabafd | |||
| 1a5b847b23 | |||
| d3409e7133 | |||
| 4e553e3eb3 | |||
| 4577bbc572 | |||
| da8ca23496 | |||
| 988853be63 | |||
| cde5dc5bfa | |||
| b617250eff | |||
| 313da99536 | |||
| 442e245216 | |||
| 050ab17019 | |||
| c80f488a13 | |||
| cf73804c71 | |||
| a90e5d13ea | |||
| 891fb0b479 | |||
| 58a623d44d | |||
| 0e84b8a154 | |||
| a4dfcf80e4 | |||
| d8567eadb2 | |||
| 0b88207ad5 | |||
| 671466a737 | |||
| 84881a0d52 | |||
| a589049cc7 | |||
| 17877e0f17 | |||
| 1ed9bef2a1 | |||
| a6dddf311c | |||
| e8c2bad187 | |||
| 52bda2c080 | |||
| 607919f40f | |||
| d830638ee6 | |||
| 106bda2097 | |||
| 7debb27374 | |||
| 05b5a7f598 | |||
| 3bbda7ba54 | |||
| 9312f5fd14 | |||
| 399eb07e35 | |||
| eb197ebcee | |||
| 4c30d8ab05 | |||
| 3a67ddf0f6 | |||
| 6541c7b5b7 | |||
| 37f41ade43 | |||
| 007d20d417 | |||
| 00370a81fa | |||
| d4599c3331 | |||
| bce76bbcf3 | |||
| 847a5fe71b | |||
| e2fa003239 | |||
| 0c555e88d3 | |||
| bf7f1a04b3 | |||
| 623172af6d | |||
| 4fb689d9e4 | |||
| 6054c489c6 | |||
| 21719f4408 | |||
| f2a650ba02 | |||
| 871c28b1ea | |||
| d3aa32147a | |||
| 9a50f2e408 | |||
| dcde4947e8 | |||
| 5b61bdc3a8 | |||
| ec1d65f4c3 | |||
| a0081dec07 | |||
| 783787e514 | |||
| ac99dd55a2 | |||
| c56f984952 | |||
| 9269ca726e | |||
| eca3205dcf | |||
| 13f5486d0b | |||
| d4e71274f9 | |||
| 4cc96de806 | |||
| d27ef12039 | |||
| f3c2ee4c40 | |||
| 555f73b478 | |||
| 544bf2ea21 | |||
| 54bbb5e384 | |||
| b10062c73d | |||
| a027d6df1b | |||
| 926a53a4b0 | |||
| 406cb46170 | |||
| acc58dc892 | |||
| 55894c22a4 | |||
| 476b10b862 | |||
| 3cbbe446ab | |||
| 4cf7512ee0 | |||
| ee8ec460a1 | |||
| deecab3cca | |||
| d2f5c1a195 | |||
| dba77e26a3 | |||
| fa48bafbc6 | |||
| 73c48694c7 | |||
| 0ff89d039b | |||
| 7a7081ee13 | |||
| 874aaf4e93 | |||
| ebf076d9f6 | |||
| 073a041d4c | |||
| 96a9b4dabd | |||
| 8e4cdf2386 | |||
| ab4d925a5a | |||
| d9c6f52e21 | |||
| 0a748cd53b | |||
| 07314ef018 | |||
| 5eaebf678f | |||
| 80fbfa60de | |||
| fbbcc9e9b6 | |||
| f47dc6b9de | |||
| 5f8e74f017 | |||
| 8c1ca0ac7e | |||
| 9018de3c4c | |||
| e7cb88c757 | |||
| d6127d6811 | |||
| 67e09e2e1d | |||
| 8fbc24c751 | |||
| c0948209be | |||
| 7237161d52 | |||
| 94c2e21e2b | |||
| 65067e6e01 | |||
| 39490ce7ba | |||
| 2f3f47c745 | |||
| 2195c3df76 | |||
| 119b9db6b4 | |||
| fcbc598732 | |||
| e850049e8e | |||
| 907adea73f | |||
| 2ac5c1362c | |||
| 8b20e2b48f | |||
| c2a9820fc1 | |||
| a9e5bc0c95 | |||
| 0fa2834d25 | |||
| 23f0876c00 | |||
| 6d88d90659 | |||
| a3c366c360 | |||
| 3bef07eeab | |||
| d029e65b8e | |||
| f305ac6905 | |||
| 4d4a46d2a5 | |||
| 8218f2f830 | |||
| b1bf901eac | |||
| 06eff55210 | |||
| 71730fddad | |||
| f2d1c6e3cb | |||
| 7ae837ca3c | |||
| b10908df5e | |||
| 4dd4d38d5b | |||
| 447c286b56 | |||
| c71898ece9 | |||
| 9473e88ea9 | |||
| d7663ed56e | |||
| da7569e2f5 | |||
| d989940a4d | |||
| bd6a86b135 | |||
| b38eb11503 | |||
| 7aef32c13d | |||
| fab64b147c | |||
| cc5a63205c | |||
| 814166a884 | |||
| 55240d9e9b | |||
| 9dc598150f | |||
| 21c087e273 | |||
| bdf3a7014f | |||
| dfea6e9b1b | |||
| 50eef1190e | |||
| ed180121ff | |||
| bdb0ad89d4 | |||
| 7195a30d55 | |||
| 5b0426a94c | |||
| a6d012abd9 | |||
| 86f0b3f29f | |||
| 85e3aa34ac | |||
| 5bbc1dedef | |||
| 39b468ef06 | |||
| fe17176b31 | |||
| 84f701c4ab | |||
| 047f8c176f | |||
| d82e79b680 | |||
| 320d1ae9d8 | |||
| a8892143a2 | |||
| 50f4532406 | |||
| 844454053d | |||
| db5c5ed534 | |||
| a26b8ecca0 | |||
| 5a32ccfa7a | |||
| f51818b157 | |||
| 31a624db51 | |||
| f045b18762 | |||
| f5006cac7d | |||
| 152b193ad5 | |||
| a27af0b642 | |||
| 44ffed3f7c | |||
| fa035ad9be | |||
| 186ace4343 | |||
| 8fb1a0bb1f | |||
| 05513bf8b9 | |||
| 858784857e | |||
| 291a23949a | |||
| 454de23844 | |||
| 1176092fc6 | |||
| 0fc2c57395 | |||
| 0b292f8d74 | |||
| 4bbe51331f | |||
| c289786dfa | |||
| 9a9752a6a1 | |||
| c43b688f8e | |||
| a6272d5221 | |||
| b440a58d95 | |||
| ec1589aa2d | |||
| 8d48e56fa0 | |||
| df7938037e | |||
| c908ee2d49 | |||
| b714abddae | |||
| 63ca189907 | |||
| 6130fb9ba2 | |||
| f0edc676fc | |||
| b68fdb0772 | |||
| 76c7bdd604 | |||
| 205101568e | |||
| efff68c49b | |||
| f74f60bb1d | |||
| 63ea28a620 | |||
| 2c6d043277 | |||
| 9a226f7b64 | |||
| 3b73a0fd72 | |||
| 2478aa77cd | |||
| a41068dbc9 | |||
| 5e47b7ae6b | |||
| debf45a7d5 | |||
| e7041e8c8c | |||
| bd960992bc | |||
| 0c5f6b432c | |||
| 49232edbd5 | |||
| b02884f58d | |||
| 845b588426 | |||
| 3a97d7c8be | |||
| 2cb2ded2d9 | |||
| 14e02bee6c | |||
| 30f7cdc1ba | |||
| 420d14fc37 | |||
| 3d7953d977 | |||
| 35238b3da1 | |||
| 446f4283e0 | |||
| 8062dd3709 | |||
| 41c8fde8c5 | |||
| d90b986d19 | |||
| 64ea8416b2 | |||
| 100a4c9d35 | |||
| 4ef6dec89a | |||
| a14cdc48bd | |||
| 8a4ddbc6df | |||
| ee33acc561 | |||
| 5fe69becf3 | |||
| d460d3ccdf | |||
| 0ee74943e8 | |||
| 82837e38d2 | |||
| 91df90d760 | |||
| 1ee37da720 | |||
| 6f8fc5b69d | |||
| be1918c769 | |||
| 0057b35a0a | |||
| d12974702a | |||
| 921c41689d | |||
| 6389899507 | |||
| 92ede2a2b3 | |||
| 826a63ed71 | |||
| d1576a2a72 | |||
| 95f218d704 | |||
| 315d3a0ac0 | |||
| 954818cef2 | |||
| 5a95ca9b1b | |||
| c1e6f4c26e | |||
| 7c603258fb | |||
| fcbc582686 | |||
| 9c4906b90b | |||
| d35e31e02d | |||
| 6c4ca36c09 | |||
| 70355dc505 | |||
| 20aeaf2a05 | |||
| 1f13e1d08b | |||
| eb9d35c123 | |||
| 45808cd530 | |||
| fd715a3f92 | |||
| e3b32367a7 | |||
| bf9554a746 | |||
| b9f8ca1488 | |||
| e8c4159678 | |||
| e57e71629e | |||
| 2bfd9d24a4 | |||
| b18b8fe22f | |||
| b154ff2f9d | |||
| e9b764b63c | |||
| 7216b97d92 | |||
| 0e9d93b194 | |||
| 2cbee62f0a | |||
| 379e9da5fe | |||
| ae7caa4901 | |||
| cd8b4c9dd7 | |||
| 60cd61dfd2 | |||
| 5a6637d9fc | |||
| dca7ed23f5 | |||
| 8cb5791f3b | |||
| 9b67f2c58f | |||
| 3815810d4f | |||
| 819ceba17d | |||
| 0aa0d62e03 | |||
| b3e2a35880 | |||
| 15ec20c65d | |||
| d4d6d7e12f | |||
| 2e7a4f1421 | |||
| ab8a52faf3 | |||
| bd465559fb | |||
| 13ec45a95c | |||
| 13b034875b | |||
| bb701fb088 | |||
| b367414865 | |||
| 4b00eec608 | |||
| 5e11b51152 | |||
| 9fb43b996e | |||
| bc2072e81f | |||
| f36bc3f643 | |||
| f7901ad843 | |||
| 3771030ed6 | |||
| 57197e58b5 | |||
| ac601399ac | |||
| 6a0e221153 | |||
| 6a949fc851 | |||
| f1a077dc2f | |||
| f20962b02b | |||
| 77e057f244 | |||
| 2c5114c770 | |||
| a30895a199 | |||
| 6d46d4b3da | |||
| 006efbbb77 | |||
| 52334087ad | |||
| ea8fb2c70a | |||
| 531d148718 | |||
| d83ddea323 | |||
| b5dea34090 | |||
| 7d8e3202b5 | |||
| d956e0af4b | |||
| 9ad70990b5 | |||
| 82d711f077 | |||
| 5fc28ef711 | |||
| 1f3dc682e2 | |||
| cce7768246 | |||
| 01172b0664 | |||
| 1ca11fdd34 | |||
| 52a064ae45 | |||
| 6c62ddf927 | |||
| 47e04c08d0 | |||
| d1601874b4 | |||
| 6bc19af041 | |||
| 4e72a3886f | |||
| 8e6b219eea | |||
| bd638251e4 | |||
| 0173d5e4b3 | |||
| 4f364e134b | |||
| 94d2519717 |
@@ -0,0 +1,14 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
indent_size=4
|
||||||
|
insert_final_newline=true
|
||||||
|
ij_kotlin_allow_trailing_comma=true
|
||||||
|
ij_kotlin_allow_trailing_comma_on_call_site=true
|
||||||
|
ij_kotlin_name_count_to_use_star_import=2147483647
|
||||||
|
ij_kotlin_name_count_to_use_star_import_for_members=2147483647
|
||||||
|
|
||||||
|
ktlint_standard_discouraged-comment-location=disabled
|
||||||
|
ktlint_standard_if-else-wrapping=disabled
|
||||||
|
ktlint_standard_no-consecutive-comments=disabled
|
||||||
|
|
||||||
|
[**/generated/**]
|
||||||
|
ktlint=disabled
|
||||||
@@ -26,3 +26,6 @@
|
|||||||
*.swp binary
|
*.swp binary
|
||||||
*.pdf binary
|
*.pdf binary
|
||||||
*.exe binary
|
*.exe binary
|
||||||
|
*.avif binary
|
||||||
|
*.heif binary
|
||||||
|
*.jxl binary
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🐞 Bug report"
|
|
||||||
title: "[Bug] <short description>"
|
|
||||||
about: "Report a bug"
|
|
||||||
labels: "bug"
|
|
||||||
---
|
|
||||||
|
|
||||||
**PLEASE READ THIS**
|
|
||||||
|
|
||||||
I acknowledge that:
|
|
||||||
|
|
||||||
- I have updated to the latest version of the app.
|
|
||||||
- I have tried the troubleshooting guide described in `README.md`
|
|
||||||
- If this is a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
|
||||||
- If this is an issue with some extension not working properly, It does work inside Tachiyomi as intended.
|
|
||||||
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
|
|
||||||
- I will fill out the title and the information in this template
|
|
||||||
|
|
||||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Device information
|
|
||||||
- Tachidesk version: (Example: v0.2.3-r255-win32)
|
|
||||||
- Server Operating System: (Example: Ubuntu 20.04)
|
|
||||||
- Server Desktop Environment: N/A or (Example: Gnome 40)
|
|
||||||
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
|
|
||||||
- Client Operating System: <usually the same as above Server Operating System>
|
|
||||||
- Client Web Browser: (Example: Google Chrome 89.0.4389.82)
|
|
||||||
|
|
||||||
## Steps to reproduce
|
|
||||||
1. First Step
|
|
||||||
2. Second Step
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
Describe what should have happened. Remove this line after you are done.
|
|
||||||
|
|
||||||
### Actual behavior
|
|
||||||
Describe what happens instead. Remove this line after you are done.
|
|
||||||
|
|
||||||
## Other details
|
|
||||||
Describe additional details If necessary. Remove this line after you are done.
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Report a bug in Suwayomi-Server
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce-steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Provide an example of the issue.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
3. Issue here
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: Explain what you should expect to happen.
|
||||||
|
placeholder: |
|
||||||
|
Example: "This should happen..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual-behavior
|
||||||
|
attributes:
|
||||||
|
label: Actual behavior
|
||||||
|
description: Explain what actually happens.
|
||||||
|
placeholder: |
|
||||||
|
Example: "This happened instead..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: suwayomi-server-version
|
||||||
|
attributes:
|
||||||
|
label: Suwayomi-Server version
|
||||||
|
description: You can find your Suwayomi-Server version in **More → About**.
|
||||||
|
placeholder: |
|
||||||
|
Example: "v2.2.2100"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: database
|
||||||
|
attributes:
|
||||||
|
label: Used database
|
||||||
|
description: H2 is the default database
|
||||||
|
options:
|
||||||
|
- H2
|
||||||
|
- PostgreSQL
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: server-os
|
||||||
|
attributes:
|
||||||
|
label: Server operating system
|
||||||
|
description: The operating system on which Suwayomi-Server is running on
|
||||||
|
placeholder: |
|
||||||
|
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: server-desktop-environment
|
||||||
|
attributes:
|
||||||
|
label: Server Desktop Environment
|
||||||
|
description:
|
||||||
|
placeholder: |
|
||||||
|
Example: "Gnome 40"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: server-jvm-version
|
||||||
|
attributes:
|
||||||
|
label: Server JVM version
|
||||||
|
description: The java version used to run Suwayomi-Server
|
||||||
|
placeholder: |
|
||||||
|
Example: "openjdk 21.0.5 2024-10-15 LTS"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: client-name
|
||||||
|
attributes:
|
||||||
|
label: Used client name
|
||||||
|
description:
|
||||||
|
placeholder: |
|
||||||
|
Example: "Suwayomi-WebUI"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: client-version
|
||||||
|
attributes:
|
||||||
|
label: Client version
|
||||||
|
description:
|
||||||
|
placeholder: |
|
||||||
|
Example: "v1.2.3"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: client-browser
|
||||||
|
attributes:
|
||||||
|
label: Used web browser
|
||||||
|
description: The browser which is used to open Suwayomi-WebUI
|
||||||
|
placeholder: |
|
||||||
|
Example: "Chrome 134.0.6998.118 (64-Bit) | FireFox 136.0.2 (64-Bit) | Electron v35.0.2"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: client-os
|
||||||
|
attributes:
|
||||||
|
label: Client operating system
|
||||||
|
description: The system on which the Suwayomi-WebUI is running on
|
||||||
|
placeholder: |
|
||||||
|
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: other-details
|
||||||
|
attributes:
|
||||||
|
label: Other details
|
||||||
|
description: The more information that gets provided the better, especially via videos and images
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||||
|
required: true
|
||||||
|
- label: I have written a short but informative title (ideally less than ~100 characters).
|
||||||
|
required: true
|
||||||
|
- label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support)
|
||||||
|
required: true
|
||||||
|
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
|
||||||
|
required: true
|
||||||
|
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||||
|
required: true
|
||||||
|
- label: I understand that **Suwayomi does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
|
||||||
|
required: true
|
||||||
@@ -1 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: ☎️ Support
|
||||||
|
url: https://discord.gg/DDZdqZWaHA
|
||||||
|
about: Join our discord to get help for anything that is not a bug or a feature request
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🌟 Feature request"
|
|
||||||
title: "[Feature Request] <short description>"
|
|
||||||
about: "Suggest a feature to improve the project"
|
|
||||||
labels: "enhancement"
|
|
||||||
---
|
|
||||||
|
|
||||||
**PLEASE READ THIS**
|
|
||||||
|
|
||||||
I acknowledge that:
|
|
||||||
|
|
||||||
- I have updated to the latest version of the app.
|
|
||||||
- I have tried the troubleshooting guide described in `README.md`
|
|
||||||
- If this is a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
|
||||||
- If this is an issue with some extension not working properly, It does work in Tachiyomi application as intended.
|
|
||||||
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
|
|
||||||
- I will fill out the title and the information in this template
|
|
||||||
|
|
||||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What feature should be added to Tachidesk?
|
|
||||||
Explain What the feature is and how it should work in detail. Remove this line after you are done.
|
|
||||||
|
|
||||||
## Why/Project's Benefit/Existing Problem
|
|
||||||
Explain why this should be added. Remove this line after you are done.
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: 🌟 Feature request
|
||||||
|
description: Suggest a feature to improve Suwayomi-Server
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: Describe your suggested feature
|
||||||
|
description: How can Suwayomi-Server be improved?
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"It should work like this..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: other-details
|
||||||
|
attributes:
|
||||||
|
label: Other details
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||||
|
required: true
|
||||||
|
- label: I have written a short but informative title (ideally less than ~100 characters).
|
||||||
|
required: true
|
||||||
|
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
|
||||||
|
required: true
|
||||||
|
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!--
|
||||||
|
Pull Request Checklist:
|
||||||
|
- Mention what the pull request does and the reasons behind the changes
|
||||||
|
- Mention all issues the pull request is closing
|
||||||
|
- Make sure to update the CHANGELOG accordingly if necessary based on the LAST stable release
|
||||||
|
-->
|
||||||
@@ -3,6 +3,10 @@ name: CI Pull Request
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_wrapper:
|
check_wrapper:
|
||||||
name: Validate Gradle Wrapper
|
name: Validate Gradle Wrapper
|
||||||
@@ -10,34 +14,44 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build pull request
|
name: Build pull request
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous runs
|
|
||||||
uses: styfle/cancel-workflow-action@0.9.0
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Checkout pull request
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 25
|
||||||
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -45,17 +59,62 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Download android.jar
|
- name: Build And Run Jar
|
||||||
|
working-directory: master
|
||||||
run: |
|
run: |
|
||||||
cd master
|
./gradlew ktlintCheck :server:shadowJar --stacktrace
|
||||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so
|
||||||
|
export LD_PRELOAD="$(pwd)/scripts/resources/catch_abort.so"
|
||||||
|
JAR=$(ls ./server/build/*.jar| head -1)
|
||||||
|
set +e
|
||||||
|
timeout 30s java \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseType=POSTGRESQL \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseUrl=postgresql://localhost:5432/postgres \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databaseUsername=postgres \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.databasePassword=postgres \
|
||||||
|
-jar "$JAR"
|
||||||
|
|
||||||
|
ecode="$?"
|
||||||
|
# if exit code is not 124 or 125, then error
|
||||||
|
if ! [ "$ecode" -eq 124 -o "$ecode" -eq 125 ]; then
|
||||||
|
printf "exit code '%s' - exiting.\n" "$ecode"
|
||||||
|
exit "$ecode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
timeout 30s java \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
|
||||||
|
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
|
||||||
|
-jar "$JAR"
|
||||||
|
|
||||||
|
ecode="$?"
|
||||||
|
# if exit code is not 124 or 125, then error
|
||||||
|
if ! [ "$ecode" -eq 124 -o "$ecode" -eq 125 ]; then
|
||||||
|
printf "exit code '%s' - exiting.\n" "$ecode"
|
||||||
|
exit "$ecode"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
check_docs:
|
||||||
|
name: Validate that all options are documented
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Validate all options are documented
|
||||||
|
run: |
|
||||||
|
f="`cat ./server/server-config/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt |
|
||||||
|
awk -F' ' 'BEGIN{prev=""}{if ($3 ~ "Mutable" && !(prev ~ "@Deprecated")) print "server." substr($2, 1, length($2)-1); prev=$0}' |
|
||||||
|
while read -r setting; do
|
||||||
|
if ! grep "$setting" ./docs/Configuring-Suwayomi‐Server.md >/dev/null; then
|
||||||
|
echo "Setting $setting not documented" >&2
|
||||||
|
echo ":"
|
||||||
|
fi
|
||||||
|
done`"
|
||||||
|
if [ -n "$f" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build Jar
|
|
||||||
uses: eskatos/gradle-command-action@v1
|
|
||||||
with:
|
|
||||||
build-root-directory: master
|
|
||||||
wrapper-directory: master
|
|
||||||
arguments: :server:shadowJar --stacktrace
|
|
||||||
wrapper-cache-enabled: true
|
|
||||||
dependencies-cache-enabled: true
|
|
||||||
configuration-cache-enabled: true
|
|
||||||
@@ -5,41 +5,41 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_wrapper:
|
check_wrapper:
|
||||||
name: Validate Gradle Wrapper
|
name: Validate Gradle Wrapper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build artifacts and deploy preview
|
name: Build Jar
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous runs
|
|
||||||
uses: styfle/cancel-workflow-action@0.9.0
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Checkout master branch
|
- name: Checkout master branch
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 25
|
||||||
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -47,58 +47,181 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Download android.jar
|
|
||||||
run: |
|
|
||||||
cd master
|
|
||||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
|
||||||
|
|
||||||
- name: Build Jar
|
- name: Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
|
||||||
env:
|
env:
|
||||||
ProductBuildType: "Preview"
|
ProductBuildType: "Preview"
|
||||||
with:
|
working-directory: master
|
||||||
build-root-directory: master
|
run: ./gradlew :server:shadowJar --stacktrace
|
||||||
wrapper-directory: master
|
|
||||||
arguments: :server:shadowJar --stacktrace
|
|
||||||
wrapper-cache-enabled: true
|
|
||||||
dependencies-cache-enabled: true
|
|
||||||
configuration-cache-enabled: true
|
|
||||||
|
|
||||||
# - name: Mock Build and copy webUI, Build Jar
|
- name: Upload Jar
|
||||||
# run: |
|
uses: actions/upload-artifact@v7
|
||||||
# mkdir -p master/server/build
|
with:
|
||||||
# cd master/server/build
|
name: jar
|
||||||
# echo "test" > Tachidesk-v0.3.8-r583.jar
|
path: master/server/build/*.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload icons
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: icon
|
||||||
|
path: master/server/src/main/resources/icon
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Tar scripts dir to maintain file permissions
|
||||||
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
|
- name: Upload scripts.tar.gz
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: scripts
|
||||||
|
path: scripts.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
jlink:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
name: linux-x64
|
||||||
|
- os: windows-latest
|
||||||
|
name: windows-x64
|
||||||
|
- os: macos-15
|
||||||
|
name: macOS-arm64
|
||||||
|
- os: macos-15-intel
|
||||||
|
name: macOS-x64
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-15, macos-15-intel]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: 25
|
||||||
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Package JDK
|
||||||
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
|
- name: Upload JRE package
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.name }}-jre
|
||||||
|
path: suwa
|
||||||
|
|
||||||
|
bundle:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: debian-all
|
||||||
|
jre: linux-x64
|
||||||
|
- name: appimage
|
||||||
|
jre: linux-x64
|
||||||
|
- name: linux-assets
|
||||||
|
jre: linux-assets
|
||||||
|
- name: linux-x64
|
||||||
|
jre: linux-x64
|
||||||
|
- name: macOS-x64
|
||||||
|
jre: macOS-x64
|
||||||
|
- name: macOS-arm64
|
||||||
|
jre: macOS-arm64
|
||||||
|
- name: windows-x64
|
||||||
|
jre: windows-x64
|
||||||
|
name: [debian-all, appimage, linux-assets, linux-x64, macOS-x64, macOS-arm64, windows-x64]
|
||||||
|
|
||||||
|
name: Make ${{ matrix.name }} release
|
||||||
|
needs: [build, jlink]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Jar
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: server/build
|
||||||
|
|
||||||
|
- name: Download JRE
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.jre }}-jre
|
||||||
|
path: jre
|
||||||
|
|
||||||
|
- name: Download icons
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: icon
|
||||||
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
|
- name: Download scripts.tar.gz
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: scripts
|
||||||
|
|
||||||
|
- name: Make ${{ matrix.name }} release
|
||||||
|
run: |
|
||||||
|
mkdir upload
|
||||||
|
tar -xvpf scripts.tar.gz
|
||||||
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
|
- name: Upload ${{ matrix.name }} release
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
path: upload/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: bundle
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: debian-all
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: appimage
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: linux-assets
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: linux-x64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: macOS-x64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: macOS-arm64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: windows-x64
|
||||||
|
path: release
|
||||||
|
|
||||||
|
- name: Checkout Preview branch
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
|
ref: main
|
||||||
|
path: preview
|
||||||
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
|
|
||||||
- name: Generate Tag Name
|
- name: Generate Tag Name
|
||||||
id: GenTagName
|
id: GenTagName
|
||||||
run: |
|
run: |
|
||||||
cd master/server/build
|
cd release
|
||||||
genTag=$(ls *.jar | sed -e's/Tachidesk-Server-\|.jar//g')
|
genTag=$(ls *.jar | sed -e's/Suwayomi-Server-\|.jar//g')
|
||||||
echo "$genTag"
|
echo "$genTag"
|
||||||
echo "::set-output name=value::$genTag"
|
echo "value=$genTag" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: make bundle packages
|
|
||||||
run: |
|
|
||||||
cd master/scripts
|
|
||||||
./windows-bundler.sh win32
|
|
||||||
./windows-bundler.sh win64
|
|
||||||
./unix-bundler.sh linux-x64
|
|
||||||
./unix-bundler.sh macOS-x64
|
|
||||||
./unix-bundler.sh macOS-arm64
|
|
||||||
|
|
||||||
# - name: Mock make windows packages
|
|
||||||
# run: |
|
|
||||||
# cd master/server/build
|
|
||||||
# echo test > Tachidesk-v0.3.8-r580-win32.zip
|
|
||||||
|
|
||||||
- name: Checkout preview branch
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: 'Suwayomi/Tachidesk-Server-preview'
|
|
||||||
ref: main
|
|
||||||
path: preview
|
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create Tag
|
- name: Create Tag
|
||||||
run: |
|
run: |
|
||||||
@@ -107,7 +230,8 @@ jobs:
|
|||||||
cd preview
|
cd preview
|
||||||
echo "{ \"latest\": \"$TAG\" }" > index.json
|
echo "{ \"latest\": \"$TAG\" }" > index.json
|
||||||
git add index.json
|
git add index.json
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
git config --global user.email \
|
||||||
|
"github-actions[bot]@users.noreply.github.com"
|
||||||
git config --global user.name "github-actions[bot]"
|
git config --global user.name "github-actions[bot]"
|
||||||
git commit -m "Updated to $TAG"
|
git commit -m "Updated to $TAG"
|
||||||
git push origin main
|
git push origin main
|
||||||
@@ -116,10 +240,10 @@ jobs:
|
|||||||
git push origin $TAG
|
git push origin $TAG
|
||||||
|
|
||||||
- name: Upload Preview Release
|
- name: Upload Preview Release
|
||||||
uses: ncipollo/release-action@v1
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
artifacts: "master/server/build/*.jar,master/server/build/*.zip,master/server/build/*.tar.gz"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
owner: "Suwayomi"
|
tag_name: ${{ steps.GenTagName.outputs.value }}
|
||||||
repo: "Tachidesk-Server-preview"
|
files: release/*
|
||||||
tag: ${{ steps.GenTagName.outputs.value }}
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Issue closer
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
autoclose:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Autoclose issues
|
|
||||||
uses: arkon/issue-closer-action@v3.0
|
|
||||||
with:
|
|
||||||
repo-token: ${{ github.token }}
|
|
||||||
rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "title",
|
|
||||||
"regex": ".*<short description>*",
|
|
||||||
"message": "You did not fill out the description in the title"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
|
||||||
"message": "The acknowledgment section was not removed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": "(Tachidesk version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
|
|
||||||
"message": "The requested information was not filled out"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*Remove this line after you are done.*",
|
|
||||||
"message": "The lines requesting to be removed were not removed."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
+181
-43
@@ -1,83 +1,221 @@
|
|||||||
name: CI Publish
|
name: CI Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- "v*"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_wrapper:
|
check_wrapper:
|
||||||
name: Validate Gradle Wrapper
|
name: Validate Gradle Wrapper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@v5
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build artifacts and release
|
name: Build Jar
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous runs
|
|
||||||
uses: styfle/cancel-workflow-action@0.9.0
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Checkout ${{ github.ref }}
|
- name: Checkout ${{ github.ref }}
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
path: master
|
path: master
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 25
|
||||||
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
cd master
|
cd master
|
||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties \
|
||||||
|
~/.gradle/gradle.properties
|
||||||
- name: Download android.jar
|
|
||||||
run: |
|
|
||||||
cd master
|
|
||||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
|
||||||
|
|
||||||
- name: Build and copy webUI, Build Jar
|
- name: Build and copy webUI, Build Jar
|
||||||
uses: eskatos/gradle-command-action@v1
|
|
||||||
env:
|
env:
|
||||||
ProductBuildType: "Stable"
|
ProductBuildType: "Stable"
|
||||||
with:
|
working-directory: master
|
||||||
build-root-directory: master
|
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
||||||
wrapper-directory: master
|
|
||||||
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
|
|
||||||
wrapper-cache-enabled: true
|
|
||||||
dependencies-cache-enabled: true
|
|
||||||
configuration-cache-enabled: true
|
|
||||||
|
|
||||||
- name: make bundle packages
|
- name: Upload Jar
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: master/server/build/*.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload icons
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: icon
|
||||||
|
path: master/server/src/main/resources/icon
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Tar scripts dir to maintain file permissions
|
||||||
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
|
- name: Upload scripts.tar.gz
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: scripts
|
||||||
|
path: scripts.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
jlink:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
name: linux-x64
|
||||||
|
- os: windows-latest
|
||||||
|
name: windows-x64
|
||||||
|
- os: macos-15
|
||||||
|
name: macOS-arm64
|
||||||
|
- os: macos-15-intel
|
||||||
|
name: macOS-x64
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-15, macos-15-intel]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
java-version: 25
|
||||||
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Package JDK
|
||||||
|
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
|
||||||
|
|
||||||
|
- name: Upload JDK package
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.name }}-jre
|
||||||
|
path: suwa
|
||||||
|
|
||||||
|
bundle:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: debian-all
|
||||||
|
jre: linux-x64
|
||||||
|
- name: appimage
|
||||||
|
jre: linux-x64
|
||||||
|
- name: linux-assets
|
||||||
|
jre: linux-assets
|
||||||
|
- name: linux-x64
|
||||||
|
jre: linux-x64
|
||||||
|
- name: macOS-x64
|
||||||
|
jre: macOS-x64
|
||||||
|
- name: macOS-arm64
|
||||||
|
jre: macOS-arm64
|
||||||
|
- name: windows-x64
|
||||||
|
jre: windows-x64
|
||||||
|
name: [debian-all, appimage, linux-assets, linux-x64, macOS-x64, macOS-arm64, windows-x64]
|
||||||
|
|
||||||
|
name: Make ${{ matrix.name }} release
|
||||||
|
needs: [build, jlink]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Jar
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: server/build
|
||||||
|
|
||||||
|
- name: Download JRE
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.jre }}-jre
|
||||||
|
path: jre
|
||||||
|
|
||||||
|
- name: Download icons
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: icon
|
||||||
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
|
- name: Download scripts.tar.gz
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: scripts
|
||||||
|
|
||||||
|
- name: Make ${{ matrix.name }} release
|
||||||
run: |
|
run: |
|
||||||
cd master/scripts
|
mkdir upload/
|
||||||
./windows-bundler.sh win32
|
tar -xvpf scripts.tar.gz
|
||||||
./windows-bundler.sh win64
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
./unix-bundler.sh linux-x64
|
|
||||||
./unix-bundler.sh macOS-x64
|
|
||||||
./unix-bundler.sh macOS-arm64
|
|
||||||
|
|
||||||
- name: Upload Release
|
- name: Upload ${{ matrix.name }} files
|
||||||
uses: xresloader/upload-to-github-release@v1
|
uses: actions/upload-artifact@v7
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
file: "master/server/build/*.jar;master/server/build/*.zip;master/server/build/*.tar.gz"
|
name: ${{ matrix.name }}
|
||||||
tags: true
|
path: upload/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
needs: bundle
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: jar
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: debian-all
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: appimage
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: linux-assets
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: linux-x64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: macOS-x64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: macOS-arm64
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: windows-x64
|
||||||
|
path: release
|
||||||
|
|
||||||
|
- name: Generate checksums
|
||||||
|
run: cd release && sha256sum * > Checksums.sha256
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
verbose: true
|
files: release/*
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: GitHub Wiki upload
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths: [docs/**, .github/workflows/wiki.yml]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
wiki:
|
||||||
|
name: Publish to GitHub Wiki
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: ${{github.repository}}
|
||||||
|
path: ${{github.repository}}
|
||||||
|
|
||||||
|
- name: Checkout Wiki
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: ${{github.repository}}.wiki
|
||||||
|
path: ${{github.repository}}.wiki
|
||||||
|
|
||||||
|
- name: Push to wiki
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
cd $GITHUB_WORKSPACE/${{github.repository}}.wiki
|
||||||
|
cp -r $GITHUB_WORKSPACE/${{github.repository}}/docs/* .
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
git add .
|
||||||
|
git diff-index --quiet HEAD || git commit -m "action: wiki sync" && git push
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
name: Publish to WinGet
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Version
|
||||||
|
required: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest # action can only be run on windows
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: Suwayomi.Suwayomi-Server
|
||||||
|
installers-regex: '.*x64.msi$'
|
||||||
|
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
||||||
|
version: ${{ inputs.version || github.ref_name }}
|
||||||
|
release-tag: ${{ inputs.version || github.ref_name }}
|
||||||
+14
-8
@@ -1,17 +1,23 @@
|
|||||||
# Ignore Gradle project-specific cache directory
|
# Ignore project-specific local files and dirs
|
||||||
.gradle
|
.gradle
|
||||||
.idea
|
.idea
|
||||||
gradle.properties
|
gradle.properties
|
||||||
|
.fleet
|
||||||
|
# But we need these
|
||||||
|
!.idea/runConfigurations
|
||||||
|
.kotlin
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Ignore Gradle build output directory
|
||||||
build
|
build
|
||||||
|
server/out
|
||||||
|
AndroidCompat/out
|
||||||
|
|
||||||
|
# WebUI is either to be downloaded on-demand or is a dynamic build asset
|
||||||
server/src/main/resources/WebUI.zip
|
server/src/main/resources/WebUI.zip
|
||||||
server/tmp/
|
|
||||||
server/tachiserver-data/
|
|
||||||
|
|
||||||
# bundle asset downlaods
|
# bundling stage downlaoded assets
|
||||||
OpenJDK*.*
|
scripts/OpenJDK*
|
||||||
zulu*jre*
|
scripts/zulu*
|
||||||
electron-*.*
|
scripts/electron-*
|
||||||
rcedit-*
|
scripts/rcedit-*
|
||||||
|
scripts/resources/*.so
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
plugins {
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.jvm
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.serialization
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.ktlint
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Shared
|
||||||
|
implementation(libs.bundles.shared)
|
||||||
|
testImplementation(libs.bundles.sharedTest)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ package xyz.nulldev.ts.config
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import net.harawata.appdirs.AppDirsFactory
|
import ca.gosyer.appdirs.AppDirs
|
||||||
|
|
||||||
const val CONFIG_PREFIX = "suwayomi.tachidesk.config"
|
const val CONFIG_PREFIX = "suwayomi.tachidesk.config"
|
||||||
|
|
||||||
val ApplicationRootDir: String
|
val ApplicationRootDir: String
|
||||||
get(): String {
|
get(): String {
|
||||||
return System.getProperty(
|
return System.getProperty(
|
||||||
"$CONFIG_PREFIX.server.rootDir",
|
"$CONFIG_PREFIX.server.rootDir",
|
||||||
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
|
AppDirs { appName = "Tachidesk" }.getUserDataDir(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package xyz.nulldev.ts.config
|
|
||||||
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.bind
|
|
||||||
import org.kodein.di.singleton
|
|
||||||
|
|
||||||
class ConfigKodeinModule {
|
|
||||||
fun create() = DI.Module("ConfigManager") {
|
|
||||||
//Config module
|
|
||||||
bind<ConfigManager>() with singleton { GlobalConfigManager }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,22 +10,32 @@ package xyz.nulldev.ts.config
|
|||||||
import ch.qos.logback.classic.Level
|
import ch.qos.logback.classic.Level
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigObject
|
||||||
import mu.KotlinLogging
|
import com.typesafe.config.ConfigValue
|
||||||
|
import com.typesafe.config.parser.ConfigDocument
|
||||||
|
import com.typesafe.config.parser.ConfigDocumentFactory
|
||||||
|
import io.github.config4k.toConfig
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages app config.
|
* Manages app config.
|
||||||
*/
|
*/
|
||||||
open class ConfigManager {
|
open class ConfigManager {
|
||||||
|
val logger = KotlinLogging.logger {}
|
||||||
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
|
||||||
val config by lazy { loadConfigs() }
|
private val userConfigFile = File(ApplicationRootDir, "server.conf")
|
||||||
|
private var internalConfig = loadConfigs()
|
||||||
|
val config: Config
|
||||||
|
get() = internalConfig
|
||||||
|
|
||||||
//Public read-only view of modules
|
// Public read-only view of modules
|
||||||
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
|
||||||
get() = generatedModules
|
get() = generatedModules
|
||||||
|
|
||||||
val logger = KotlinLogging.logger {}
|
private val mutex = Mutex()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a config module
|
* Get a config module
|
||||||
@@ -38,28 +48,32 @@ open class ConfigManager {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
|
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
|
||||||
|
|
||||||
|
private fun getUserConfig(): Config =
|
||||||
|
userConfigFile.let {
|
||||||
|
ConfigFactory.parseFile(it)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configs
|
* Load configs
|
||||||
*/
|
*/
|
||||||
fun loadConfigs(): Config {
|
fun loadConfigs(): Config {
|
||||||
//Load reference configs
|
// Load reference configs
|
||||||
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
val compatConfig = ConfigFactory.parseResources("compat-reference.conf")
|
||||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
val baseConfig =
|
val baseConfig =
|
||||||
ConfigFactory.parseMap(
|
ConfigFactory.parseMap(
|
||||||
mapOf(
|
mapOf(
|
||||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
// override AndroidCompat's rootDir
|
||||||
)
|
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat",
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
//Load user config
|
// Load user config
|
||||||
val userConfig =
|
val userConfig = getUserConfig()
|
||||||
File(ApplicationRootDir, "server.conf").let {
|
|
||||||
ConfigFactory.parseFile(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val config =
|
||||||
val config = ConfigFactory.empty()
|
ConfigFactory
|
||||||
|
.empty()
|
||||||
.withFallback(baseConfig)
|
.withFallback(baseConfig)
|
||||||
.withFallback(userConfig)
|
.withFallback(userConfig)
|
||||||
.withFallback(compatConfig)
|
.withFallback(compatConfig)
|
||||||
@@ -68,11 +82,7 @@ open class ConfigManager {
|
|||||||
|
|
||||||
// set log level early
|
// set log level early
|
||||||
if (debugLogsEnabled(config)) {
|
if (debugLogsEnabled(config)) {
|
||||||
setLogLevel(Level.DEBUG)
|
setLogLevelFor(BASE_LOGGER_NAME, Level.DEBUG)
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug {
|
|
||||||
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
@@ -87,6 +97,104 @@ open class ConfigManager {
|
|||||||
registerModule(it)
|
registerModule(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateUserConfigFile(
|
||||||
|
path: String,
|
||||||
|
value: ConfigValue,
|
||||||
|
) {
|
||||||
|
val userConfigDoc = ConfigDocumentFactory.parseFile(userConfigFile)
|
||||||
|
val updatedConfigDoc = userConfigDoc.withValue(path, value)
|
||||||
|
val newFileContent = updatedConfigDoc.render()
|
||||||
|
userConfigFile.writeText(newFileContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateValue(
|
||||||
|
path: String,
|
||||||
|
value: Any,
|
||||||
|
) {
|
||||||
|
mutex.withLock {
|
||||||
|
val configValue = value.toConfig("internal").getValue("internal")
|
||||||
|
|
||||||
|
updateUserConfigFile(path, configValue)
|
||||||
|
internalConfig = internalConfig.withValue(path, configValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createConfigDocumentFromReference(): ConfigDocument {
|
||||||
|
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
|
||||||
|
return ConfigDocumentFactory.parseString(serverConfigFileContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetUserConfig(): ConfigDocument {
|
||||||
|
val serverConfigDoc = createConfigDocumentFromReference()
|
||||||
|
|
||||||
|
userConfigFile.writeText(serverConfigDoc.render())
|
||||||
|
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||||
|
|
||||||
|
return serverConfigDoc
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure the "UserConfig" is up-to-date.
|
||||||
|
*
|
||||||
|
* - Adds missing settings
|
||||||
|
* - Migrates deprecated settings
|
||||||
|
* - Removes outdated settings
|
||||||
|
*/
|
||||||
|
fun updateUserConfig(migrate: ConfigDocument.(Config) -> ConfigDocument) {
|
||||||
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
|
val userConfig = getUserConfig()
|
||||||
|
|
||||||
|
// NOTE: if more than 1 dot is included, that's a nested setting, which we need to filter out here
|
||||||
|
val refKeys =
|
||||||
|
serverConfig.root().entries.flatMap {
|
||||||
|
(it.value as? ConfigObject)?.entries?.map { e -> "${it.key}.${e.key}" }.orEmpty()
|
||||||
|
}
|
||||||
|
val hasMissingSettings = refKeys.any { !userConfig.hasPath(it) }
|
||||||
|
val hasOutdatedSettings = userConfig.entrySet().any { !refKeys.contains(it.key) && it.key.count { c -> c == '.' } <= 1 }
|
||||||
|
|
||||||
|
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
|
||||||
|
if (!isUserConfigOutdated) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug {
|
||||||
|
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var newUserConfigDoc: ConfigDocument = createConfigDocumentFromReference()
|
||||||
|
userConfig
|
||||||
|
.entrySet()
|
||||||
|
.filter {
|
||||||
|
serverConfig.hasPath(
|
||||||
|
it.key,
|
||||||
|
) ||
|
||||||
|
it.key.count { c -> c == '.' } > 1
|
||||||
|
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
|
||||||
|
|
||||||
|
newUserConfigDoc =
|
||||||
|
migrate(newUserConfigDoc, internalConfig)
|
||||||
|
|
||||||
|
userConfigFile.writeText(newUserConfigDoc.render())
|
||||||
|
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRedactedConfig(nonPrivacySafeKeys: List<String>): Config {
|
||||||
|
val entries =
|
||||||
|
config.entrySet().associate { entry ->
|
||||||
|
val key = entry.key
|
||||||
|
val value =
|
||||||
|
if (nonPrivacySafeKeys.any { key.split(".").getOrNull(1) == it }) {
|
||||||
|
"[REDACTED]"
|
||||||
|
} else {
|
||||||
|
entry.value.unwrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
key to value
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigFactory.parseMap(entries)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalConfigManager : ConfigManager()
|
object GlobalConfigManager : ConfigManager()
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package xyz.nulldev.ts.config
|
||||||
|
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
fun configManagerModule(): Module =
|
||||||
|
module {
|
||||||
|
single<ConfigManager> { GlobalConfigManager }
|
||||||
|
}
|
||||||
@@ -8,37 +8,69 @@ package xyz.nulldev.ts.config
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigException
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import io.github.config4k.ClassContainer
|
||||||
|
import io.github.config4k.TypeReference
|
||||||
import io.github.config4k.getValue
|
import io.github.config4k.getValue
|
||||||
|
import io.github.config4k.readers.SelectReader
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract config module.
|
* Abstract config module.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
abstract class ConfigModule(config: Config)
|
abstract class ConfigModule(
|
||||||
|
getConfig: () -> Config,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract jvm-commandline-argument-overridable config module.
|
* Abstract jvm-commandline-argument-overridable config module.
|
||||||
*/
|
*/
|
||||||
abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String): ConfigModule(config) {
|
abstract class SystemPropertyOverridableConfigModule(
|
||||||
val overridableConfig = SystemPropertyOverrideDelegate(config, moduleName)
|
getConfig: () -> Config,
|
||||||
|
val moduleName: String,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
|
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */
|
/** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */
|
||||||
class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String) {
|
class SystemPropertyOverrideDelegate(
|
||||||
inline operator fun <R, reified T> getValue(thisRef: R, property: KProperty<*>): T {
|
val getConfig: () -> Config,
|
||||||
val configValue: T = config.getValue(thisRef, property)
|
val moduleName: String,
|
||||||
|
) {
|
||||||
|
inline operator fun <R, reified T> getValue(
|
||||||
|
thisRef: R,
|
||||||
|
property: KProperty<*>,
|
||||||
|
): T {
|
||||||
|
val config = getConfig()
|
||||||
|
|
||||||
val combined = System.getProperty(
|
val systemProperty =
|
||||||
"$CONFIG_PREFIX.$moduleName.${property.name}",
|
System.getProperty("$CONFIG_PREFIX.$moduleName.${property.name}")
|
||||||
configValue.toString()
|
if (systemProperty == null) {
|
||||||
)
|
val configValue: T = config.getValue(thisRef, property)
|
||||||
|
return configValue
|
||||||
|
}
|
||||||
|
|
||||||
return when(T::class.simpleName) {
|
val systemPropertyConfig =
|
||||||
"Int" -> combined.toInt()
|
try {
|
||||||
"Boolean" -> combined.toBoolean()
|
ConfigFactory.parseString("internal=$systemProperty")
|
||||||
// add more types as needed
|
} catch (_: ConfigException) {
|
||||||
else -> combined // covers String
|
ConfigFactory.parseMap(mapOf("internal" to systemProperty))
|
||||||
} as T
|
}
|
||||||
|
|
||||||
|
val genericType = object : TypeReference<T>() {}.genericType()
|
||||||
|
val clazz = ClassContainer(T::class, genericType)
|
||||||
|
val reader = SelectReader.getReader(clazz)
|
||||||
|
val path = property.name
|
||||||
|
|
||||||
|
val result = reader(systemPropertyConfig, "internal")
|
||||||
|
return try {
|
||||||
|
result as T
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw result
|
||||||
|
?.let { e }
|
||||||
|
?: ConfigException.BadPath(path, "take a look at your config")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,132 @@ package xyz.nulldev.ts.config
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level
|
import ch.qos.logback.classic.Level
|
||||||
|
import ch.qos.logback.classic.LoggerContext
|
||||||
|
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||||
|
import ch.qos.logback.core.rolling.RollingFileAppender
|
||||||
|
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
|
||||||
|
import ch.qos.logback.core.util.FileSize
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import mu.KotlinLogging
|
import io.github.oshai.kotlinlogging.DelegatingKLogger
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
fun setLogLevel(level: Level) {
|
private fun fileSizeValueOfOrDefault(
|
||||||
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
|
fileSizeStr: String,
|
||||||
|
default: String,
|
||||||
|
): FileSize =
|
||||||
|
try {
|
||||||
|
FileSize.valueOf(fileSizeStr)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
FileSize.valueOf(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val FILE_APPENDER_NAME = "SuwayomiDefaultAppender"
|
||||||
|
|
||||||
|
private fun createRollingFileAppender(
|
||||||
|
logContext: LoggerContext,
|
||||||
|
logDirPath: String,
|
||||||
|
maxFiles: Int,
|
||||||
|
maxFileSize: String,
|
||||||
|
maxTotalSize: String,
|
||||||
|
): RollingFileAppender<ILoggingEvent> {
|
||||||
|
val logFilename = "application"
|
||||||
|
|
||||||
|
val logEncoder =
|
||||||
|
PatternLayoutEncoder().apply {
|
||||||
|
pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n"
|
||||||
|
context = logContext
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
val appender =
|
||||||
|
RollingFileAppender<ILoggingEvent>().apply {
|
||||||
|
name = FILE_APPENDER_NAME
|
||||||
|
context = logContext
|
||||||
|
encoder = logEncoder
|
||||||
|
file = "$logDirPath/$logFilename.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
val rollingPolicy =
|
||||||
|
SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
|
||||||
|
context = logContext
|
||||||
|
setParent(appender)
|
||||||
|
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
|
||||||
|
maxHistory = maxFiles.coerceAtLeast(0)
|
||||||
|
setMaxFileSize(fileSizeValueOfOrDefault(maxFileSize, "10mb"))
|
||||||
|
setTotalSizeCap(fileSizeValueOfOrDefault(maxTotalSize, "100mb"))
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
appender.rollingPolicy = rollingPolicy
|
||||||
|
appender.start()
|
||||||
|
|
||||||
|
return appender
|
||||||
}
|
}
|
||||||
|
|
||||||
fun debugLogsEnabled(config: Config)
|
private fun getBaseLogger(): ch.qos.logback.classic.Logger =
|
||||||
= System.getProperty("suwayomi.tachidesk.config.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
|
((KotlinLogging.logger(Logger.ROOT_LOGGER_NAME) as DelegatingKLogger<*>).underlyingLogger as ch.qos.logback.classic.Logger)
|
||||||
|
|
||||||
|
private fun getLogger(name: String): ch.qos.logback.classic.Logger {
|
||||||
|
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
||||||
|
return context.getLogger(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initLoggerConfig(
|
||||||
|
appRootPath: String,
|
||||||
|
maxFiles: Int,
|
||||||
|
maxFileSize: String,
|
||||||
|
maxTotalSize: String,
|
||||||
|
) {
|
||||||
|
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
||||||
|
val logger = getBaseLogger()
|
||||||
|
|
||||||
|
// logback logs to the console by default (at least when adding a console appender logs in the console are duplicated)
|
||||||
|
logger.addAppender(createRollingFileAppender(context, "$appRootPath/logs", maxFiles, maxFileSize, maxTotalSize))
|
||||||
|
|
||||||
|
// set "kotlin exposed" log level
|
||||||
|
setLogLevelFor("Exposed", Level.ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFileAppender(
|
||||||
|
maxFiles: Int,
|
||||||
|
maxFileSize: String,
|
||||||
|
maxTotalSize: String,
|
||||||
|
) {
|
||||||
|
val logger = getBaseLogger()
|
||||||
|
|
||||||
|
val appender = logger.getAppender(FILE_APPENDER_NAME) as RollingFileAppender<*>? ?: return
|
||||||
|
val rollingPolicy = appender.rollingPolicy as SizeAndTimeBasedRollingPolicy<*>
|
||||||
|
rollingPolicy.apply {
|
||||||
|
maxHistory = maxFiles
|
||||||
|
setMaxFileSize(FileSize.valueOf(maxFileSize))
|
||||||
|
setTotalSizeCap(FileSize.valueOf(maxTotalSize))
|
||||||
|
|
||||||
|
rollingPolicy.stop()
|
||||||
|
appender.stop()
|
||||||
|
|
||||||
|
rollingPolicy.start()
|
||||||
|
appender.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const val BASE_LOGGER_NAME = "_BaseLogger"
|
||||||
|
|
||||||
|
fun setLogLevelFor(
|
||||||
|
name: String,
|
||||||
|
level: Level,
|
||||||
|
) {
|
||||||
|
val logger =
|
||||||
|
if (name == BASE_LOGGER_NAME) {
|
||||||
|
getBaseLogger()
|
||||||
|
} else {
|
||||||
|
getLogger(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
fun debugLogsEnabled(config: Config) =
|
||||||
|
System.getProperty("suwayomi.tachidesk.config.server.debugLogsEnabled", config.getString("server.debugLogsEnabled")).toBoolean()
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ package xyz.nulldev.ts.config.util
|
|||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
operator fun Config.get(key: String) = getString(key)
|
operator fun Config.get(key: String) =
|
||||||
|
getString(key)
|
||||||
?: throw IllegalStateException("Could not find value for config entry: $key!")
|
?: throw IllegalStateException("Could not find value for config entry: $key!")
|
||||||
|
|||||||
@@ -1,68 +1,51 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
id(
|
||||||
kotlin("plugin.serialization")
|
libs.plugins.kotlin.jvm
|
||||||
}
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
repositories {
|
id(
|
||||||
mavenCentral()
|
libs.plugins.kotlin.serialization
|
||||||
maven {
|
.get()
|
||||||
url = uri("https://jitpack.io")
|
.pluginId,
|
||||||
}
|
)
|
||||||
|
id(
|
||||||
maven {
|
libs.plugins.ktlint
|
||||||
url = uri("https://maven.google.com")
|
.get()
|
||||||
}
|
.pluginId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Android stub library
|
// Shared
|
||||||
implementation(fileTree("lib/"))
|
implementation(libs.bundles.shared)
|
||||||
|
testImplementation(libs.bundles.sharedTest)
|
||||||
|
|
||||||
// JSON
|
// Android stub library
|
||||||
compileOnly("com.google.code.gson:gson:2.8.6")
|
implementation(libs.android.stubs)
|
||||||
|
|
||||||
// XML
|
// XML
|
||||||
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
compileOnly(libs.xmlpull)
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
implementation(project(":AndroidCompat:Config"))
|
implementation(projects.androidCompat.config)
|
||||||
|
|
||||||
// APK sig verifier
|
// APK sig verifier
|
||||||
compileOnly("com.android.tools.build:apksig:4.2.0-alpha13")
|
compileOnly(libs.apksig)
|
||||||
|
|
||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
|
compileOnly(libs.android.annotations)
|
||||||
|
|
||||||
// substitute for duktape-android
|
// substitute for duktape-android/quickjs
|
||||||
implementation("org.mozilla:rhino-runtime:1.7.13") // slimmer version of 'org.mozilla:rhino'
|
implementation(libs.bundles.polyglot)
|
||||||
implementation("org.mozilla:rhino-engine:1.7.13") // provides the same interface as 'javax.script' a.k.a Nashorn
|
|
||||||
|
|
||||||
// Kotlin wrapper around Java Preferences, makes certain things easier
|
// Kotlin wrapper around Java Preferences, makes certain things easier
|
||||||
val multiplatformSettingsVersion = "0.7.7"
|
implementation(libs.bundles.settings)
|
||||||
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
|
||||||
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
|
||||||
|
|
||||||
// Android version of SimpleDateFormat
|
// Android version of SimpleDateFormat
|
||||||
implementation("com.ibm.icu:icu4j:69.1")
|
implementation(libs.icu4j)
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
||||||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
implementation(libs.bundles.twelvemonkeys)
|
||||||
kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
|
implementation(libs.imageio.webp)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
|
||||||
//
|
|
||||||
//// Copy JVM core patches
|
|
||||||
//task copyJVMPatches(type: Copy) {
|
|
||||||
// from fatJarTask.outputs.files
|
|
||||||
// into 'src/main/resources/patches'
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//compileOnly(Java.dependsOn gradle.includedBuild('dex2jar').task(':dex-translator:assemble')
|
|
||||||
//compileOnly(Java.dependsOn copyJVMPatches
|
|
||||||
//copyJVMPatches.dependsOn fatJarTask
|
|
||||||
//
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
android.jar
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package android.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Denotes that the annotated element represents a packed color
|
||||||
|
* long. If applied to a long array, every element in the array
|
||||||
|
* represents a color long. For more information on how colors
|
||||||
|
* are packed in a long, please refer to the documentation of
|
||||||
|
* the {@link android.graphics.Color} class.</p>
|
||||||
|
*
|
||||||
|
* <p>Example:</p>
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public void setFillColor(@ColorLong long color);
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see android.graphics.Color
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
|
||||||
|
public @interface ColorLong {
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package android.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Denotes that the annotated element represents a half-precision floating point
|
||||||
|
* value. Such values are stored in short data types and can be manipulated with
|
||||||
|
* the {@link android.util.Half} class. If applied to an array of short, every
|
||||||
|
* element in the array represents a half-precision float.</p>
|
||||||
|
*
|
||||||
|
* <p>Example:</p>
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see android.util.Half
|
||||||
|
* @see android.util.Half#toHalf(float)
|
||||||
|
* @see android.util.Half#toFloat(short)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@Retention(SOURCE)
|
||||||
|
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
|
||||||
|
public @interface HalfFloat {
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import android.os.IBinder;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import kotlin.NotImplementedError;
|
import kotlin.NotImplementedError;
|
||||||
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -299,7 +299,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
*/
|
*/
|
||||||
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
|
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
|
||||||
|
|
||||||
private static final ServiceSupport serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class);
|
private static final ServiceSupport serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class);
|
||||||
|
|
||||||
private static final String TAG = "Service";
|
private static final String TAG = "Service";
|
||||||
/**
|
/**
|
||||||
@@ -328,7 +328,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
|
|||||||
public Service() {
|
public Service() {
|
||||||
//==================[THIS LINE MODIFIED FROM ANDROID SOURCE!]==================
|
//==================[THIS LINE MODIFIED FROM ANDROID SOURCE!]==================
|
||||||
//Service must be initialized with a base context!
|
//Service must be initialized with a base context!
|
||||||
super(KodeinGlobalHelper.instance(Context.class));
|
super(KoinGlobalHelper.instance(Context.class));
|
||||||
}
|
}
|
||||||
/** Return the application that owns this service. */
|
/** Return the application that owns this service. */
|
||||||
public final Application getApplication() {
|
public final Application getApplication() {
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
|
|
||||||
|
public final class Bitmap {
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
private final BufferedImage image;
|
||||||
|
|
||||||
|
public Bitmap(BufferedImage image) {
|
||||||
|
this.image = image;
|
||||||
|
this.width = image.getWidth();
|
||||||
|
this.height = image.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CompressFormat {
|
||||||
|
JPEG (0),
|
||||||
|
PNG (1),
|
||||||
|
WEBP (2),
|
||||||
|
WEBP_LOSSY (3),
|
||||||
|
WEBP_LOSSLESS (4);
|
||||||
|
|
||||||
|
CompressFormat(int nativeInt) {
|
||||||
|
this.nativeInt = nativeInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int nativeInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Config {
|
||||||
|
ALPHA_8(1),
|
||||||
|
RGB_565(3),
|
||||||
|
ARGB_4444(4),
|
||||||
|
ARGB_8888(5),
|
||||||
|
RGBA_F16(6),
|
||||||
|
HARDWARE(7),
|
||||||
|
RGBA_1010102(8),
|
||||||
|
|
||||||
|
_TYPE_3BYTE_BGR(BufferedImage.TYPE_3BYTE_BGR),
|
||||||
|
_TYPE_4BYTE_ABGR(BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
|
_TYPE_4BYTE_ABGR_PRE(BufferedImage.TYPE_4BYTE_ABGR_PRE),
|
||||||
|
_TYPE_BYTE_BINARY(BufferedImage.TYPE_BYTE_BINARY),
|
||||||
|
_TYPE_BYTE_GRAY(BufferedImage.TYPE_BYTE_GRAY),
|
||||||
|
_TYPE_BYTE_INDEXED(BufferedImage.TYPE_BYTE_INDEXED),
|
||||||
|
_TYPE_CUSTOM(BufferedImage.TYPE_CUSTOM),
|
||||||
|
_TYPE_INT_ARGB(BufferedImage.TYPE_INT_ARGB),
|
||||||
|
_TYPE_INT_ARGB_PRE(BufferedImage.TYPE_INT_ARGB_PRE),
|
||||||
|
_TYPE_INT_BGR(BufferedImage.TYPE_INT_BGR),
|
||||||
|
_TYPE_INT_RGB(BufferedImage.TYPE_INT_RGB),
|
||||||
|
_TYPE_USHORT_555_RGB(BufferedImage.TYPE_USHORT_555_RGB),
|
||||||
|
_TYPE_USHORT_565_RGB(BufferedImage.TYPE_USHORT_565_RGB),
|
||||||
|
_TYPE_USHORT_GRAY(BufferedImage.TYPE_USHORT_GRAY),
|
||||||
|
;
|
||||||
|
|
||||||
|
final int nativeInt;
|
||||||
|
|
||||||
|
private static final Config[] sConfigs = {
|
||||||
|
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
|
||||||
|
};
|
||||||
|
|
||||||
|
Config(int ni) {
|
||||||
|
this.nativeInt = ni;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config nativeToConfig(int ni) {
|
||||||
|
return sConfigs[ni];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int configToBufferedImageType(Config config) {
|
||||||
|
switch (config) {
|
||||||
|
case ALPHA_8:
|
||||||
|
return BufferedImage.TYPE_BYTE_GRAY;
|
||||||
|
case RGB_565:
|
||||||
|
return BufferedImage.TYPE_USHORT_565_RGB;
|
||||||
|
case ARGB_8888:
|
||||||
|
return BufferedImage.TYPE_INT_ARGB;
|
||||||
|
case _TYPE_3BYTE_BGR:
|
||||||
|
case _TYPE_4BYTE_ABGR:
|
||||||
|
case _TYPE_4BYTE_ABGR_PRE:
|
||||||
|
case _TYPE_BYTE_BINARY:
|
||||||
|
case _TYPE_BYTE_GRAY:
|
||||||
|
case _TYPE_BYTE_INDEXED:
|
||||||
|
case _TYPE_CUSTOM:
|
||||||
|
case _TYPE_INT_ARGB:
|
||||||
|
case _TYPE_INT_ARGB_PRE:
|
||||||
|
case _TYPE_INT_BGR:
|
||||||
|
case _TYPE_INT_RGB:
|
||||||
|
case _TYPE_USHORT_555_RGB:
|
||||||
|
case _TYPE_USHORT_565_RGB:
|
||||||
|
case _TYPE_USHORT_GRAY:
|
||||||
|
return config.ordinal();
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Config bufferedImageTypeToConfig(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case BufferedImage.TYPE_BYTE_GRAY:
|
||||||
|
return Config.ALPHA_8;
|
||||||
|
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||||
|
return Config.RGB_565;
|
||||||
|
case BufferedImage.TYPE_INT_ARGB:
|
||||||
|
return Config.ARGB_8888;
|
||||||
|
case BufferedImage.TYPE_3BYTE_BGR:
|
||||||
|
return Config._TYPE_3BYTE_BGR;
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||||
|
return Config._TYPE_4BYTE_ABGR;
|
||||||
|
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||||
|
return Config._TYPE_4BYTE_ABGR_PRE;
|
||||||
|
case BufferedImage.TYPE_BYTE_BINARY:
|
||||||
|
return Config._TYPE_BYTE_BINARY;
|
||||||
|
case BufferedImage.TYPE_BYTE_INDEXED:
|
||||||
|
return Config._TYPE_BYTE_INDEXED;
|
||||||
|
case BufferedImage.TYPE_CUSTOM:
|
||||||
|
return Config._TYPE_CUSTOM;
|
||||||
|
case BufferedImage.TYPE_INT_ARGB_PRE:
|
||||||
|
return Config._TYPE_INT_ARGB_PRE;
|
||||||
|
case BufferedImage.TYPE_INT_BGR:
|
||||||
|
return Config._TYPE_INT_BGR;
|
||||||
|
case BufferedImage.TYPE_INT_RGB:
|
||||||
|
return Config._TYPE_INT_RGB;
|
||||||
|
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||||
|
return Config._TYPE_USHORT_555_RGB;
|
||||||
|
case BufferedImage.TYPE_USHORT_GRAY:
|
||||||
|
return Config._TYPE_USHORT_GRAY;
|
||||||
|
default:
|
||||||
|
Log.w("Bitmap", "Encountered unsupported image type " + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common code for checking that x and y are >= 0
|
||||||
|
*
|
||||||
|
* @param x x coordinate to ensure is >= 0
|
||||||
|
* @param y y coordinate to ensure is >= 0
|
||||||
|
*/
|
||||||
|
private static void checkXYSign(int x, int y) {
|
||||||
|
if (x < 0) {
|
||||||
|
throw new IllegalArgumentException("x must be >= 0");
|
||||||
|
}
|
||||||
|
if (y < 0) {
|
||||||
|
throw new IllegalArgumentException("y must be >= 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common code for checking that width and height are > 0
|
||||||
|
*
|
||||||
|
* @param width width to ensure is > 0
|
||||||
|
* @param height height to ensure is > 0
|
||||||
|
*/
|
||||||
|
private static void checkWidthHeight(int width, int height) {
|
||||||
|
if (width <= 0) {
|
||||||
|
throw new IllegalArgumentException("width must be > 0");
|
||||||
|
}
|
||||||
|
if (height <= 0) {
|
||||||
|
throw new IllegalArgumentException("height must be > 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap createBitmap(int width, int height, Config config) {
|
||||||
|
BufferedImage image = new BufferedImage(width, height, configToBufferedImageType(config));
|
||||||
|
return new Bitmap(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
|
||||||
|
checkXYSign(x, y);
|
||||||
|
checkWidthHeight(width, height);
|
||||||
|
if (x + width > source.getWidth()) {
|
||||||
|
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
|
||||||
|
}
|
||||||
|
if (y + height > source.getHeight()) {
|
||||||
|
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android will make a copy when creating a sub image,
|
||||||
|
// so we do the same here
|
||||||
|
BufferedImage subImage = source.image.getSubimage(x, y, width, height);
|
||||||
|
BufferedImage newImage = new BufferedImage(subImage.getWidth(), subImage.getHeight(), subImage.getType());
|
||||||
|
newImage.setData(subImage.getData());
|
||||||
|
|
||||||
|
return new Bitmap(newImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
|
||||||
|
if (stream == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quality < 0 || quality > 100) {
|
||||||
|
throw new IllegalArgumentException("quality must be 0..100");
|
||||||
|
}
|
||||||
|
float qualityFloat = ((float) quality) / 100;
|
||||||
|
|
||||||
|
String formatString;
|
||||||
|
if (format == Bitmap.CompressFormat.PNG) {
|
||||||
|
formatString = "png";
|
||||||
|
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||||
|
formatString = "jpg";
|
||||||
|
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||||
|
formatString = "webp";
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||||
|
if (!writers.hasNext()) {
|
||||||
|
throw new IllegalStateException("no image writers found for this format!");
|
||||||
|
}
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
|
ImageOutputStream ios;
|
||||||
|
try {
|
||||||
|
ios = ImageIO.createImageOutputStream(stream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
writer.setOutput(ios);
|
||||||
|
|
||||||
|
BufferedImage img = image;
|
||||||
|
|
||||||
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
if ("jpg".equals(formatString)) {
|
||||||
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
param.setCompressionQuality(qualityFloat);
|
||||||
|
|
||||||
|
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
img.getGraphics().drawImage(image, 0, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
writer.write(null, new IIOImage(img, null, null), param);
|
||||||
|
ios.close();
|
||||||
|
writer.dispose();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap copy(Config config, boolean isMutable) {
|
||||||
|
Bitmap ret = createBitmap(width, height, config);
|
||||||
|
ret.image.getGraphics().drawImage(image, 0, 0, null);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared code to check for illegal arguments passed to getPixels()
|
||||||
|
* or setPixels()
|
||||||
|
*
|
||||||
|
* @param x left edge of the area of pixels to access
|
||||||
|
* @param y top edge of the area of pixels to access
|
||||||
|
* @param width width of the area of pixels to access
|
||||||
|
* @param height height of the area of pixels to access
|
||||||
|
* @param offset offset into pixels[] array
|
||||||
|
* @param stride number of elements in pixels[] between each logical row
|
||||||
|
* @param pixels array to hold the area of pixels being accessed
|
||||||
|
*/
|
||||||
|
private void checkPixelsAccess(int x, int y, int width, int height,
|
||||||
|
int offset, int stride, int[] pixels) {
|
||||||
|
checkXYSign(x, y);
|
||||||
|
if (width < 0) {
|
||||||
|
throw new IllegalArgumentException("width must be >= 0");
|
||||||
|
}
|
||||||
|
if (height < 0) {
|
||||||
|
throw new IllegalArgumentException("height must be >= 0");
|
||||||
|
}
|
||||||
|
if (x + width > getWidth()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"x + width must be <= bitmap.width()");
|
||||||
|
}
|
||||||
|
if (y + height > getHeight()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"y + height must be <= bitmap.height()");
|
||||||
|
}
|
||||||
|
if (Math.abs(stride) < width) {
|
||||||
|
throw new IllegalArgumentException("abs(stride) must be >= width");
|
||||||
|
}
|
||||||
|
int lastScanline = offset + (height - 1) * stride;
|
||||||
|
int length = pixels.length;
|
||||||
|
if (offset < 0 || (offset + width > length)
|
||||||
|
|| lastScanline < 0
|
||||||
|
|| (lastScanline + width > length)) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared code to check for illegal arguments passed to getPixel()
|
||||||
|
* or setPixel()
|
||||||
|
*
|
||||||
|
* @param x x coordinate of the pixel
|
||||||
|
* @param y y coordinate of the pixel
|
||||||
|
*/
|
||||||
|
private void checkPixelAccess(int x, int y) {
|
||||||
|
checkXYSign(x, y);
|
||||||
|
if (x >= getWidth()) {
|
||||||
|
throw new IllegalArgumentException("x must be < bitmap.width()");
|
||||||
|
}
|
||||||
|
if (y >= getHeight()) {
|
||||||
|
throw new IllegalArgumentException("y must be < bitmap.height()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
|
||||||
|
int x, int y, int width, int height) {
|
||||||
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
|
|
||||||
|
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public int getPixel(int x, int y) {
|
||||||
|
checkPixelAccess(x, y);
|
||||||
|
return image.getRGB(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Write the specified {@link Color} into the bitmap (assuming it is
|
||||||
|
* mutable) at the x,y coordinate. The color must be a
|
||||||
|
* non-premultiplied ARGB value in the {@link ColorSpace.Named#SRGB sRGB}
|
||||||
|
* color space.</p>
|
||||||
|
*
|
||||||
|
* @param x The x coordinate of the pixel to replace (0...width-1)
|
||||||
|
* @param y The y coordinate of the pixel to replace (0...height-1)
|
||||||
|
* @param color The ARGB color to write into the bitmap
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the bitmap is not mutable
|
||||||
|
* @throws IllegalArgumentException if x, y are outside of the bitmap's
|
||||||
|
* bounds.
|
||||||
|
*/
|
||||||
|
public void setPixel(int x, int y, @ColorInt int color) {
|
||||||
|
checkPixelAccess(x, y);
|
||||||
|
image.setRGB(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Replace pixels in the bitmap with the colors in the array. Each element
|
||||||
|
* in the array is a packed int representing a non-premultiplied ARGB
|
||||||
|
* {@link Color} in the {@link ColorSpace.Named#SRGB sRGB} color space.</p>
|
||||||
|
*
|
||||||
|
* @param pixels The colors to write to the bitmap
|
||||||
|
* @param offset The index of the first color to read from pixels[]
|
||||||
|
* @param stride The number of colors in pixels[] to skip between rows.
|
||||||
|
* Normally this value will be the same as the width of
|
||||||
|
* the bitmap, but it can be larger (or negative).
|
||||||
|
* @param x The x coordinate of the first pixel to write to in
|
||||||
|
* the bitmap.
|
||||||
|
* @param y The y coordinate of the first pixel to write to in
|
||||||
|
* the bitmap.
|
||||||
|
* @param width The number of colors to copy from pixels[] per row
|
||||||
|
* @param height The number of rows to write to the bitmap
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the bitmap is not mutable
|
||||||
|
* @throws IllegalArgumentException if x, y, width, height are outside of
|
||||||
|
* the bitmap's bounds.
|
||||||
|
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
|
||||||
|
* to receive the specified number of pixels.
|
||||||
|
*/
|
||||||
|
public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
|
||||||
|
int x, int y, int width, int height) {
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
|
image.setRGB(x, y, width, height, pixels, offset, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eraseColor(int c) {
|
||||||
|
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||||
|
Graphics2D graphics = image.createGraphics();
|
||||||
|
graphics.setColor(color);
|
||||||
|
graphics.fillRect(0, 0, width, height);
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recycle() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Config getConfig() {
|
||||||
|
int type = image.getType();
|
||||||
|
return bufferedImageTypeToConfig(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class BitmapFactory {
|
||||||
|
public static class Options {
|
||||||
|
/**
|
||||||
|
* Create a default Options object, which if left unchanged will give
|
||||||
|
* the same result from the decoder as if null were passed.
|
||||||
|
*/
|
||||||
|
public Options() {
|
||||||
|
inScaled = true;
|
||||||
|
inPremultiplied = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, decode methods that take the Options object will attempt to
|
||||||
|
* reuse this bitmap when loading content. If the decode operation
|
||||||
|
* cannot use this bitmap, the decode method will throw an
|
||||||
|
* {@link java.lang.IllegalArgumentException}. The
|
||||||
|
* current implementation necessitates that the reused bitmap be
|
||||||
|
* mutable, and the resulting reused bitmap will continue to remain
|
||||||
|
* mutable even when decoding a resource which would normally result in
|
||||||
|
* an immutable bitmap.</p>
|
||||||
|
*
|
||||||
|
* <p>You should still always use the returned Bitmap of the decode
|
||||||
|
* method and not assume that reusing the bitmap worked, due to the
|
||||||
|
* constraints outlined above and failure situations that can occur.
|
||||||
|
* Checking whether the return value matches the value of the inBitmap
|
||||||
|
* set in the Options structure will indicate if the bitmap was reused,
|
||||||
|
* but in all cases you should use the Bitmap returned by the decoding
|
||||||
|
* function to ensure that you are using the bitmap that was used as the
|
||||||
|
* decode destination.</p>
|
||||||
|
*
|
||||||
|
* <h3>Usage with BitmapFactory</h3>
|
||||||
|
*
|
||||||
|
* <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, any
|
||||||
|
* mutable bitmap can be reused by {@link BitmapFactory} to decode any
|
||||||
|
* other bitmaps as long as the resulting {@link Bitmap#getByteCount()
|
||||||
|
* byte count} of the decoded bitmap is less than or equal to the {@link
|
||||||
|
* Bitmap#getAllocationByteCount() allocated byte count} of the reused
|
||||||
|
* bitmap. This can be because the intrinsic size is smaller, or its
|
||||||
|
* size post scaling (for density / sample size) is smaller.</p>
|
||||||
|
*
|
||||||
|
* <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}
|
||||||
|
* additional constraints apply: The image being decoded (whether as a
|
||||||
|
* resource or as a stream) must be in jpeg or png format. Only equal
|
||||||
|
* sized bitmaps are supported, with {@link #inSampleSize} set to 1.
|
||||||
|
* Additionally, the {@link android.graphics.Bitmap.Config
|
||||||
|
* configuration} of the reused bitmap will override the setting of
|
||||||
|
* {@link #inPreferredConfig}, if set.</p>
|
||||||
|
*
|
||||||
|
* <h3>Usage with BitmapRegionDecoder</h3>
|
||||||
|
*
|
||||||
|
* <p>BitmapRegionDecoder will draw its requested content into the Bitmap
|
||||||
|
* provided, clipping if the output content size (post scaling) is larger
|
||||||
|
* than the provided Bitmap. The provided Bitmap's width, height, and
|
||||||
|
* {@link Bitmap.Config} will not be changed.
|
||||||
|
*
|
||||||
|
* <p class="note">BitmapRegionDecoder support for {@link #inBitmap} was
|
||||||
|
* introduced in {@link android.os.Build.VERSION_CODES#JELLY_BEAN}. All
|
||||||
|
* formats supported by BitmapRegionDecoder support Bitmap reuse via
|
||||||
|
* {@link #inBitmap}.</p>
|
||||||
|
*
|
||||||
|
* @see Bitmap#reconfigure(int,int, android.graphics.Bitmap.Config)
|
||||||
|
*/
|
||||||
|
public Bitmap inBitmap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, decode methods will always return a mutable Bitmap instead of
|
||||||
|
* an immutable one. This can be used for instance to programmatically apply
|
||||||
|
* effects to a Bitmap loaded through BitmapFactory.
|
||||||
|
* <p>Can not be set simultaneously with inPreferredConfig =
|
||||||
|
* {@link android.graphics.Bitmap.Config#HARDWARE},
|
||||||
|
* because hardware bitmaps are always immutable.
|
||||||
|
*/
|
||||||
|
public boolean inMutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true, the decoder will return null (no bitmap), but
|
||||||
|
* the <code>out...</code> fields will still be set, allowing the caller to
|
||||||
|
* query the bitmap without having to allocate the memory for its pixels.
|
||||||
|
*/
|
||||||
|
public boolean inJustDecodeBounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to a value > 1, requests the decoder to subsample the original
|
||||||
|
* image, returning a smaller image to save memory. The sample size is
|
||||||
|
* the number of pixels in either dimension that correspond to a single
|
||||||
|
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
|
||||||
|
* an image that is 1/4 the width/height of the original, and 1/16 the
|
||||||
|
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
|
||||||
|
* decoder uses a final value based on powers of 2, any other value will
|
||||||
|
* be rounded down to the nearest power of 2.
|
||||||
|
*/
|
||||||
|
public int inSampleSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is non-null, the decoder will try to decode into this
|
||||||
|
* internal configuration. If it is null, or the request cannot be met,
|
||||||
|
* the decoder will try to pick the best matching config based on the
|
||||||
|
* system's screen depth, and characteristics of the original image such
|
||||||
|
* as if it has per-pixel alpha (requiring a config that also does).
|
||||||
|
*
|
||||||
|
* Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
|
||||||
|
* default.
|
||||||
|
*/
|
||||||
|
public Bitmap.Config inPreferredConfig = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>If this is non-null, the decoder will try to decode into this
|
||||||
|
* color space. If it is null, or the request cannot be met,
|
||||||
|
* the decoder will pick either the color space embedded in the image
|
||||||
|
* or the color space best suited for the requested image configuration
|
||||||
|
* (for instance {@link ColorSpace.Named#SRGB sRGB} for
|
||||||
|
* {@link Bitmap.Config#ARGB_8888} configuration and
|
||||||
|
* {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for
|
||||||
|
* {@link Bitmap.Config#RGBA_F16}).</p>
|
||||||
|
*
|
||||||
|
* <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are
|
||||||
|
* currently supported. An <code>IllegalArgumentException</code> will
|
||||||
|
* be thrown by the decode methods when setting a non-RGB color space
|
||||||
|
* such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
|
||||||
|
*
|
||||||
|
* <p class="note">
|
||||||
|
* Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
|
||||||
|
* the specified color space's transfer function must be
|
||||||
|
* an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
|
||||||
|
* <code>IllegalArgumentException</code> will be thrown by the decode methods
|
||||||
|
* if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
|
||||||
|
* specified color space returns null.
|
||||||
|
*
|
||||||
|
* Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
|
||||||
|
* non ICC parametric curve transfer function is allowed.
|
||||||
|
* E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.</p>
|
||||||
|
*
|
||||||
|
* <p>After decode, the bitmap's color space is stored in
|
||||||
|
* {@link #outColorSpace}.</p>
|
||||||
|
*/
|
||||||
|
public ColorSpace inPreferredColorSpace = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true (which is the default), the resulting bitmap will have its
|
||||||
|
* color channels pre-multiplied by the alpha channel.
|
||||||
|
*
|
||||||
|
* <p>This should NOT be set to false for images to be directly drawn by
|
||||||
|
* the view system or through a {@link Canvas}. The view system and
|
||||||
|
* {@link Canvas} assume all drawn images are pre-multiplied to simplify
|
||||||
|
* draw-time blending, and will throw a RuntimeException when
|
||||||
|
* un-premultiplied are drawn.</p>
|
||||||
|
*
|
||||||
|
* <p>This is likely only useful if you want to manipulate raw encoded
|
||||||
|
* image data, e.g. with RenderScript or custom OpenGL.</p>
|
||||||
|
*
|
||||||
|
* <p>This does not affect bitmaps without an alpha channel.</p>
|
||||||
|
*
|
||||||
|
* <p>Setting this flag to false while setting {@link #inScaled} to true
|
||||||
|
* may result in incorrect colors.</p>
|
||||||
|
*
|
||||||
|
* @see Bitmap#hasAlpha()
|
||||||
|
* @see Bitmap#isPremultiplied()
|
||||||
|
* @see #inScaled
|
||||||
|
*/
|
||||||
|
public boolean inPremultiplied;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if dither is
|
||||||
|
* true, the decoder will attempt to dither the decoded image.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inDither;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density to use for the bitmap. This will always result
|
||||||
|
* in the returned bitmap having a density set for it (see
|
||||||
|
* {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}). In addition,
|
||||||
|
* if {@link #inScaled} is set (which it is by default} and this
|
||||||
|
* density does not match {@link #inTargetDensity}, then the bitmap
|
||||||
|
* will be scaled to the target density before being returned.
|
||||||
|
*
|
||||||
|
* <p>If this is 0,
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int)},
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
|
||||||
|
* and {@link BitmapFactory#decodeResourceStream}
|
||||||
|
* will fill in the density associated with the resource. The other
|
||||||
|
* functions will leave it as-is and no density will be applied.
|
||||||
|
*
|
||||||
|
* @see #inTargetDensity
|
||||||
|
* @see #inScreenDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see Bitmap#setDensity(int)
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density of the destination this bitmap will be drawn to.
|
||||||
|
* This is used in conjunction with {@link #inDensity} and
|
||||||
|
* {@link #inScaled} to determine if and how to scale the bitmap before
|
||||||
|
* returning it.
|
||||||
|
*
|
||||||
|
* <p>If this is 0,
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int)},
|
||||||
|
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
|
||||||
|
* and {@link BitmapFactory#decodeResourceStream}
|
||||||
|
* will fill in the density associated the Resources object's
|
||||||
|
* DisplayMetrics. The other
|
||||||
|
* functions will leave it as-is and no scaling for density will be
|
||||||
|
* performed.
|
||||||
|
*
|
||||||
|
* @see #inDensity
|
||||||
|
* @see #inScreenDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inTargetDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel density of the actual screen that is being used. This is
|
||||||
|
* purely for applications running in density compatibility code, where
|
||||||
|
* {@link #inTargetDensity} is actually the density the application
|
||||||
|
* sees rather than the real screen density.
|
||||||
|
*
|
||||||
|
* <p>By setting this, you
|
||||||
|
* allow the loading code to avoid scaling a bitmap that is currently
|
||||||
|
* in the screen density up/down to the compatibility density. Instead,
|
||||||
|
* if {@link #inDensity} is the same as {@link #inScreenDensity}, the
|
||||||
|
* bitmap will be left as-is. Anything using the resulting bitmap
|
||||||
|
* must also used {@link Bitmap#getScaledWidth(int)
|
||||||
|
* Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
|
||||||
|
* Bitmap.getScaledHeight} to account for any different between the
|
||||||
|
* bitmap's density and the target's density.
|
||||||
|
*
|
||||||
|
* <p>This is never set automatically for the caller by
|
||||||
|
* {@link BitmapFactory} itself. It must be explicitly set, since the
|
||||||
|
* caller must deal with the resulting bitmap in a density-aware way.
|
||||||
|
*
|
||||||
|
* @see #inDensity
|
||||||
|
* @see #inTargetDensity
|
||||||
|
* @see #inScaled
|
||||||
|
* @see android.util.DisplayMetrics#densityDpi
|
||||||
|
*/
|
||||||
|
public int inScreenDensity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this flag is set, if {@link #inDensity} and
|
||||||
|
* {@link #inTargetDensity} are not 0, the
|
||||||
|
* bitmap will be scaled to match {@link #inTargetDensity} when loaded,
|
||||||
|
* rather than relying on the graphics system scaling it each time it
|
||||||
|
* is drawn to a Canvas.
|
||||||
|
*
|
||||||
|
* <p>BitmapRegionDecoder ignores this flag, and will not scale output
|
||||||
|
* based on density. (though {@link #inSampleSize} is supported)</p>
|
||||||
|
*
|
||||||
|
* <p>This flag is turned on by default and should be turned off if you need
|
||||||
|
* a non-scaled version of the bitmap. Nine-patch bitmaps ignore this
|
||||||
|
* flag and are always scaled.
|
||||||
|
*
|
||||||
|
* <p>If {@link #inPremultiplied} is set to false, and the image has alpha,
|
||||||
|
* setting this flag to true may result in incorrect colors.
|
||||||
|
*/
|
||||||
|
public boolean inScaled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
|
||||||
|
* is set to true, then the resulting bitmap will allocate its
|
||||||
|
* pixels such that they can be purged if the system needs to reclaim
|
||||||
|
* memory. In that instance, when the pixels need to be accessed again
|
||||||
|
* (e.g. the bitmap is drawn, getPixels() is called), they will be
|
||||||
|
* automatically re-decoded.
|
||||||
|
*
|
||||||
|
* <p>For the re-decode to happen, the bitmap must have access to the
|
||||||
|
* encoded data, either by sharing a reference to the input
|
||||||
|
* or by making a copy of it. This distinction is controlled by
|
||||||
|
* inInputShareable. If this is true, then the bitmap may keep a shallow
|
||||||
|
* reference to the input. If this is false, then the bitmap will
|
||||||
|
* explicitly make a copy of the input data, and keep that. Even if
|
||||||
|
* sharing is allowed, the implementation may still decide to make a
|
||||||
|
* deep copy of the input data.</p>
|
||||||
|
*
|
||||||
|
* <p>While inPurgeable can help avoid big Dalvik heap allocations (from
|
||||||
|
* API level 11 onward), it sacrifices performance predictability since any
|
||||||
|
* image that the view system tries to draw may incur a decode delay which
|
||||||
|
* can lead to dropped frames. Therefore, most apps should avoid using
|
||||||
|
* inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
|
||||||
|
* allocations use the {@link #inBitmap} flag instead.</p>
|
||||||
|
*
|
||||||
|
* <p class="note"><strong>Note:</strong> This flag is ignored when used
|
||||||
|
* with {@link #decodeResource(Resources, int,
|
||||||
|
* android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
|
||||||
|
* android.graphics.BitmapFactory.Options)}.</p>
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inPurgeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
|
||||||
|
* ignored.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
|
||||||
|
* field works in conjunction with inPurgeable. If inPurgeable is false,
|
||||||
|
* then this field is ignored. If inPurgeable is true, then this field
|
||||||
|
* determines whether the bitmap can share a reference to the input
|
||||||
|
* data (inputstream, array, etc.) or if it must make a deep copy.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inInputShareable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this is
|
||||||
|
* ignored. The output will always be high quality.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if
|
||||||
|
* inPreferQualityOverSpeed is set to true, the decoder will try to
|
||||||
|
* decode the reconstructed image to a higher quality even at the
|
||||||
|
* expense of the decoding speed. Currently the field only affects JPEG
|
||||||
|
* decode, in the case of which a more accurate, but slightly slower,
|
||||||
|
* IDCT method will be used instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean inPreferQualityOverSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resulting width of the bitmap. If {@link #inJustDecodeBounds} is
|
||||||
|
* set to false, this will be width of the output bitmap after any
|
||||||
|
* scaling is applied. If true, it will be the width of the input image
|
||||||
|
* without any accounting for scaling.
|
||||||
|
*
|
||||||
|
* <p>outWidth will be set to -1 if there is an error trying to decode.</p>
|
||||||
|
*/
|
||||||
|
public int outWidth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resulting height of the bitmap. If {@link #inJustDecodeBounds} is
|
||||||
|
* set to false, this will be height of the output bitmap after any
|
||||||
|
* scaling is applied. If true, it will be the height of the input image
|
||||||
|
* without any accounting for scaling.
|
||||||
|
*
|
||||||
|
* <p>outHeight will be set to -1 if there is an error trying to decode.</p>
|
||||||
|
*/
|
||||||
|
public int outHeight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, this string is set to the mimetype of the decoded image.
|
||||||
|
* If not known, or there is an error, it is set to null.
|
||||||
|
*/
|
||||||
|
public String outMimeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, the config the decoded bitmap will have.
|
||||||
|
* If not known, or there is an error, it is set to null.
|
||||||
|
*/
|
||||||
|
public Bitmap.Config outConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If known, the color space the decoded bitmap will have. Note that the
|
||||||
|
* output color space is not guaranteed to be the color space the bitmap
|
||||||
|
* is encoded with. If not known (when the config is
|
||||||
|
* {@link Bitmap.Config#ALPHA_8} for instance), or there is an error,
|
||||||
|
* it is set to null.
|
||||||
|
*/
|
||||||
|
public ColorSpace outColorSpace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temp storage to use for decoding. Suggest 16K or so.
|
||||||
|
*/
|
||||||
|
public byte[] inTempStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, see
|
||||||
|
* comments on {@link #requestCancelDecode()}.
|
||||||
|
*
|
||||||
|
* Flag to indicate that cancel has been called on this object. This
|
||||||
|
* is useful if there's an intermediary that wants to first decode the
|
||||||
|
* bounds and then decode the image. In that case the intermediary
|
||||||
|
* can check, inbetween the bounds decode and the image decode, to see
|
||||||
|
* if the operation is canceled.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean mCancel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated As of {@link android.os.Build.VERSION_CODES#N}, this
|
||||||
|
* will not affect the decode, though it will still set mCancel.
|
||||||
|
*
|
||||||
|
* In {@link android.os.Build.VERSION_CODES#M} and below, if this can
|
||||||
|
* be called from another thread while this options object is inside
|
||||||
|
* a decode... call. Calling this will notify the decoder that it
|
||||||
|
* should cancel its operation. This is not guaranteed to cancel the
|
||||||
|
* decode, but if it does, the decoder... operation will return null,
|
||||||
|
* or if inJustDecodeBounds is true, will set outWidth/outHeight
|
||||||
|
* to -1
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void requestCancelDecode() {
|
||||||
|
mCancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate(Options opts) {
|
||||||
|
if (opts == null) return;
|
||||||
|
|
||||||
|
if (opts.inBitmap != null) {
|
||||||
|
/*
|
||||||
|
if (opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Bitmaps with Config.HARDWARE are always immutable");
|
||||||
|
}
|
||||||
|
if (opts.inBitmap.isRecycled()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot reuse a recycled Bitmap");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
|
||||||
|
throw new IllegalArgumentException("Bitmaps with Config.HARDWARE cannot be " +
|
||||||
|
"decoded into - they are immutable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inPreferredColorSpace != null) {
|
||||||
|
if (!(opts.inPreferredColorSpace instanceof ColorSpace.Rgb)) {
|
||||||
|
throw new IllegalArgumentException("The destination color space must use the " +
|
||||||
|
"RGB color model");
|
||||||
|
}
|
||||||
|
if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
|
||||||
|
&& !opts.inPreferredColorSpace.equals(
|
||||||
|
ColorSpace.get(ColorSpace.Named.BT2020_PQ))
|
||||||
|
&& ((ColorSpace.Rgb) opts.inPreferredColorSpace)
|
||||||
|
.getTransferParameters() == null) {
|
||||||
|
throw new IllegalArgumentException("The destination color space must use an " +
|
||||||
|
"ICC parametric transfer function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Bitmap decodeStream(InputStream inputStream) {
|
||||||
|
return decodeStream(inputStream, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
|
||||||
|
@Nullable Options opts) {
|
||||||
|
if (is == null) return null;
|
||||||
|
if (outPadding != null) throw new RuntimeException("OutPadding is not implemented");
|
||||||
|
Options.validate(opts);
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
// TODO: Support options with in parameters
|
||||||
|
|
||||||
|
try {
|
||||||
|
ImageInputStream imageInputStream = ImageIO.createImageInputStream(is);
|
||||||
|
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
||||||
|
|
||||||
|
if (!imageReaders.hasNext()) {
|
||||||
|
throw new IllegalArgumentException("no reader for image");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageReader imageReader = imageReaders.next();
|
||||||
|
imageReader.setInput(imageInputStream);
|
||||||
|
|
||||||
|
if (opts != null) {
|
||||||
|
opts.outHeight = imageReader.getHeight(0);
|
||||||
|
opts.outWidth = imageReader.getWidth(0);
|
||||||
|
opts.outMimeType = imageReader.getOriginatingProvider().getMIMETypes()[0];
|
||||||
|
opts.outColorSpace = null; // TODO: support? see imageReader.getImageTypeSpecifier().getColorSpace()
|
||||||
|
opts.outConfig = null; // TODO: support?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts == null || !opts.inJustDecodeBounds) {
|
||||||
|
BufferedImage image = imageReader.read(0, imageReader.getDefaultReadParam());
|
||||||
|
bitmap = new Bitmap(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
imageReader.dispose();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
|
||||||
|
return decodeByteArray(data, offset, length, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
|
||||||
|
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data, offset, length);
|
||||||
|
return decodeStream(byteArrayStream, null, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.ColorLong;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.font.GlyphVector;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Ellipse2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class Canvas {
|
||||||
|
private BufferedImage canvasImage;
|
||||||
|
private Graphics2D canvas;
|
||||||
|
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||||
|
|
||||||
|
private static final String TAG = "Canvas";
|
||||||
|
|
||||||
|
public Canvas(Bitmap bitmap) {
|
||||||
|
canvasImage = bitmap.getImage();
|
||||||
|
canvas = canvasImage.createGraphics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||||
|
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||||
|
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
||||||
|
canvas.drawImage(sourceImageCropped, dst.left, dst.top, dst.getWidth(), dst.getHeight(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawBitmap(Bitmap sourceBitmap, float left, float top, Paint paint) {
|
||||||
|
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||||
|
canvas.drawImage(sourceImage, null, (int) left, (int) top);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
|
||||||
|
@NonNull Paint paint) {
|
||||||
|
drawText(new String(text, index, count), x, y, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawText(@NonNull String str, float x, float y, @NonNull Paint paint) {
|
||||||
|
applyPaint(paint);
|
||||||
|
AttributedString text = paint.getTypeface().createWithFallback(str);
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||||
|
// TODO: fix with fallback fonts
|
||||||
|
GlyphVector glyphVector = paint.getTypeface().getFont().createGlyphVector(canvas.getFontRenderContext(), text.getIterator());
|
||||||
|
Shape textShape = glyphVector.getOutline();
|
||||||
|
switch (paint.getStyle()) {
|
||||||
|
case Paint.Style.FILL:
|
||||||
|
canvas.drawString(text.getIterator(), x, y);
|
||||||
|
break;
|
||||||
|
case Paint.Style.STROKE:
|
||||||
|
save();
|
||||||
|
translate(x, y);
|
||||||
|
canvas.draw(textShape);
|
||||||
|
restore();
|
||||||
|
break;
|
||||||
|
case Paint.Style.FILL_AND_STROKE:
|
||||||
|
save();
|
||||||
|
translate(x, y);
|
||||||
|
canvas.draw(textShape);
|
||||||
|
canvas.fill(textShape);
|
||||||
|
restore();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawText(@NonNull String text, int start, int end, float x, float y,
|
||||||
|
@NonNull Paint paint) {
|
||||||
|
drawText(text.substring(start, end), x, y, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
|
||||||
|
@NonNull Paint paint) {
|
||||||
|
String str = text.subSequence(start, end).toString();
|
||||||
|
drawText(str, x, y, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
|
||||||
|
@NonNull Paint paint) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void translate(float dx, float dy) {
|
||||||
|
if (dx == 0.0f && dy == 0.0f) return;
|
||||||
|
// TODO: check this, should translations stack?
|
||||||
|
canvas.translate(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scale(float sx, float sy) {
|
||||||
|
if (sx == 1.0f && sy == 1.0f) return;
|
||||||
|
canvas.scale(sx, sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void scale(float sx, float sy, float px, float py) {
|
||||||
|
if (sx == 1.0f && sy == 1.0f) return;
|
||||||
|
translate(px, py);
|
||||||
|
scale(sx, sy);
|
||||||
|
translate(-px, -py);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate(float degrees) {
|
||||||
|
if (degrees == 0.0f) return;
|
||||||
|
canvas.rotate(degrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void rotate(float degrees, float px, float py) {
|
||||||
|
if (degrees == 0.0f) return;
|
||||||
|
canvas.rotate(degrees, px, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSaveCount() {
|
||||||
|
return transformStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int save() {
|
||||||
|
transformStack.add(canvas.getTransform());
|
||||||
|
return getSaveCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreToCount(int saveCount) {
|
||||||
|
if (saveCount < 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Underflow in restoreToCount - more restores than saves");
|
||||||
|
}
|
||||||
|
if (saveCount > getSaveCount()) {
|
||||||
|
throw new IllegalArgumentException("Overflow in restoreToCount");
|
||||||
|
|
||||||
|
}
|
||||||
|
AffineTransform ts = transformStack.get(saveCount - 1);
|
||||||
|
canvas.setTransform(ts);
|
||||||
|
while (transformStack.size() >= saveCount) {
|
||||||
|
transformStack.remove(transformStack.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restore() {
|
||||||
|
restoreToCount(getSaveCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getClipBounds(@NonNull Rect bounds) {
|
||||||
|
Rectangle r = canvas.getClipBounds();
|
||||||
|
if (r == null) {
|
||||||
|
bounds.left = 0;
|
||||||
|
bounds.top = 0;
|
||||||
|
bounds.right = canvasImage.getWidth();
|
||||||
|
bounds.bottom = canvasImage.getHeight();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bounds.left = r.x;
|
||||||
|
bounds.top = r.y;
|
||||||
|
bounds.right = r.x + r.width;
|
||||||
|
bounds.bottom = r.y + r.height;
|
||||||
|
return r.width != 0 && r.height != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawColor(@ColorInt int colorInt) {
|
||||||
|
java.awt.Color color = Color.valueOf(colorInt).toJavaColor();
|
||||||
|
canvas.setColor(color);
|
||||||
|
canvas.fillRect(0, 0, canvasImage.getWidth(), canvasImage.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawColor(@ColorLong long colorLong) {
|
||||||
|
java.awt.Color color = Color.valueOf(colorLong).toJavaColor();
|
||||||
|
canvas.setColor(color);
|
||||||
|
canvas.fillRect(0, 0, canvasImage.getWidth(), canvasImage.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPoint(float x, float y, Paint paint) {
|
||||||
|
applyPaint(paint);
|
||||||
|
Shape shape = paintToShape(paint, x, y);
|
||||||
|
if (paint.getStyle() == Paint.Style.FILL) {
|
||||||
|
canvas.fill(shape);
|
||||||
|
} else {
|
||||||
|
canvas.draw(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPaint(Paint paint) {
|
||||||
|
canvas.setFont(paint.getTypeface().getFont());
|
||||||
|
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||||
|
canvas.setColor(color);
|
||||||
|
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), paintToStrokeCap(paint), BasicStroke.JOIN_ROUND));
|
||||||
|
if (paint.isAntiAlias()) {
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
} else {
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||||
|
}
|
||||||
|
if (paint.isDither()) {
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||||
|
} else {
|
||||||
|
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||||
|
}
|
||||||
|
// TODO: use more from paint?
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int paintToStrokeCap(Paint paint) {
|
||||||
|
switch (paint.getStrokeCap()) {
|
||||||
|
case BUTT:
|
||||||
|
return BasicStroke.CAP_BUTT;
|
||||||
|
case SQUARE:
|
||||||
|
return BasicStroke.CAP_SQUARE;
|
||||||
|
case ROUND:
|
||||||
|
return BasicStroke.CAP_ROUND;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Stroke cap " + paint.getStrokeCap() + " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Shape paintToShape(Paint paint, float x, float y) {
|
||||||
|
int width = (int)(paint.getStrokeWidth() * 2);
|
||||||
|
if (width <= 0) width = 1;
|
||||||
|
int upLeftX = (int) (x - (float) width / 2);
|
||||||
|
int upLeftY = (int) (y - (float) width / 2);
|
||||||
|
switch (paint.getStrokeCap()) {
|
||||||
|
case BUTT:
|
||||||
|
return new Rectangle((int)x, (int)y, 1, 1);
|
||||||
|
case SQUARE:
|
||||||
|
return new Rectangle(upLeftX, upLeftY, width, width);
|
||||||
|
case ROUND:
|
||||||
|
return new Ellipse2D.Float(upLeftX, upLeftY, width, width);
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Stroke cap " + paint.getStrokeCap() + " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,578 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.ColorLong;
|
||||||
|
import android.annotation.HalfFloat;
|
||||||
|
import android.annotation.IntRange;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.Size;
|
||||||
|
import android.util.Half;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
public class Color {
|
||||||
|
@ColorInt public static final int BLACK = 0xFF000000;
|
||||||
|
@ColorInt public static final int DKGRAY = 0xFF444444;
|
||||||
|
@ColorInt public static final int GRAY = 0xFF888888;
|
||||||
|
@ColorInt public static final int LTGRAY = 0xFFCCCCCC;
|
||||||
|
@ColorInt public static final int WHITE = 0xFFFFFFFF;
|
||||||
|
@ColorInt public static final int RED = 0xFFFF0000;
|
||||||
|
@ColorInt public static final int GREEN = 0xFF00FF00;
|
||||||
|
@ColorInt public static final int BLUE = 0xFF0000FF;
|
||||||
|
@ColorInt public static final int YELLOW = 0xFFFFFF00;
|
||||||
|
@ColorInt public static final int CYAN = 0xFF00FFFF;
|
||||||
|
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
|
||||||
|
@ColorInt public static final int TRANSPARENT = 0;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Size(min = 4, max = 5)
|
||||||
|
private final float[] mComponents;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final ColorSpace mColorSpace;
|
||||||
|
|
||||||
|
public Color() {
|
||||||
|
// This constructor is required for compatibility with previous APIs
|
||||||
|
mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||||
|
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color(float r, float g, float b, float a) {
|
||||||
|
this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||||
|
mComponents = new float[] { r, g, b, a };
|
||||||
|
mColorSpace = colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
|
||||||
|
mComponents = components;
|
||||||
|
mColorSpace = colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.awt.Color toJavaColor() {
|
||||||
|
return new java.awt.Color(red(), green(), blue(), alpha());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ColorSpace getColorSpace() {
|
||||||
|
return mColorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorSpace.Model getModel() {
|
||||||
|
return mColorSpace.getModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWideGamut() {
|
||||||
|
return getColorSpace().isWideGamut();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSrgb() {
|
||||||
|
return getColorSpace().isSrgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntRange(from = 4, to = 5)
|
||||||
|
public int getComponentCount() {
|
||||||
|
return mColorSpace.getComponentCount() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public long pack() {
|
||||||
|
return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Color convert(@NonNull ColorSpace colorSpace) {
|
||||||
|
ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
|
||||||
|
float[] color = new float[] {
|
||||||
|
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||||
|
};
|
||||||
|
connector.transform(color);
|
||||||
|
return new Color(color, colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public int toArgb() {
|
||||||
|
if (mColorSpace.isSrgb()) {
|
||||||
|
return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
|
||||||
|
((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (mComponents[2] * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] color = new float[] {
|
||||||
|
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||||
|
};
|
||||||
|
// The transformation saturates the output
|
||||||
|
ColorSpace.connect(mColorSpace).transform(color);
|
||||||
|
|
||||||
|
return ((int) (color[3] * 255.0f + 0.5f) << 24) |
|
||||||
|
((int) (color[0] * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (color[1] * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (color[2] * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float red() {
|
||||||
|
return mComponents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public float green() {
|
||||||
|
return mComponents[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public float blue() {
|
||||||
|
return mComponents[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public float alpha() {
|
||||||
|
return mComponents[mComponents.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Size(min = 4, max = 5)
|
||||||
|
public float[] getComponents() {
|
||||||
|
return Arrays.copyOf(mComponents, mComponents.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Size(min = 4)
|
||||||
|
public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
|
||||||
|
if (components == null) {
|
||||||
|
return Arrays.copyOf(mComponents, mComponents.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.length < mComponents.length) {
|
||||||
|
throw new IllegalArgumentException("The specified array's length must be at "
|
||||||
|
+ "least " + mComponents.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(mComponents, 0, components, 0, mComponents.length);
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getComponent(@IntRange(from = 0, to = 4) int component) {
|
||||||
|
return mComponents[component];
|
||||||
|
}
|
||||||
|
|
||||||
|
public float luminance() {
|
||||||
|
if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||||
|
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||||
|
"color space. The supplied color space is " + mColorSpace.getModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
|
||||||
|
double r = eotf.applyAsDouble(mComponents[0]);
|
||||||
|
double g = eotf.applyAsDouble(mComponents[1]);
|
||||||
|
double b = eotf.applyAsDouble(mComponents[2]);
|
||||||
|
|
||||||
|
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Color color = (Color) o;
|
||||||
|
|
||||||
|
//noinspection SimplifiableIfStatement
|
||||||
|
if (!Arrays.equals(mComponents, color.mComponents)) return false;
|
||||||
|
return mColorSpace.equals(color.mColorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = Arrays.hashCode(mComponents);
|
||||||
|
result = 31 * result + mColorSpace.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder b = new StringBuilder("Color(");
|
||||||
|
for (float c : mComponents) {
|
||||||
|
b.append(c).append(", ");
|
||||||
|
}
|
||||||
|
b.append(mColorSpace.getName());
|
||||||
|
b.append(')');
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static ColorSpace colorSpace(@ColorLong long color) {
|
||||||
|
return ColorSpace.get((int) (color & 0x3fL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float red(@ColorLong long color) {
|
||||||
|
if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
|
||||||
|
return Half.toFloat((short) ((color >> 48) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float green(@ColorLong long color) {
|
||||||
|
if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
|
||||||
|
return Half.toFloat((short) ((color >> 32) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float blue(@ColorLong long color) {
|
||||||
|
if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
|
||||||
|
return Half.toFloat((short) ((color >> 16) & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float alpha(@ColorLong long color) {
|
||||||
|
if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
|
||||||
|
return ((color >> 6) & 0x3ff) / 1023.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSrgb(@ColorLong long color) {
|
||||||
|
return colorSpace(color).isSrgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isWideGamut(@ColorLong long color) {
|
||||||
|
return colorSpace(color).isWideGamut();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||||
|
return (int) (color & 0x3fL) == colorSpace.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int toArgb(@ColorLong long color) {
|
||||||
|
if ((color & 0x3fL) == 0L) return (int) (color >> 32);
|
||||||
|
|
||||||
|
float r = red(color);
|
||||||
|
float g = green(color);
|
||||||
|
float b = blue(color);
|
||||||
|
float a = alpha(color);
|
||||||
|
|
||||||
|
// The transformation saturates the output
|
||||||
|
float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
|
||||||
|
|
||||||
|
return ((int) (a * 255.0f + 0.5f) << 24) |
|
||||||
|
((int) (c[0] * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (c[1] * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (c[2] * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(@ColorInt int color) {
|
||||||
|
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||||
|
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||||
|
float b = ((color ) & 0xff) / 255.0f;
|
||||||
|
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||||
|
return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(@ColorLong long color) {
|
||||||
|
return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(float r, float g, float b) {
|
||||||
|
return new Color(r, g, b, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(float r, float g, float b, float a) {
|
||||||
|
return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||||
|
if (colorSpace.getComponentCount() > 3) {
|
||||||
|
throw new IllegalArgumentException("The specified color space must use a color model " +
|
||||||
|
"with at most 3 color components");
|
||||||
|
}
|
||||||
|
return new Color(r, g, b, a, colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
|
||||||
|
@NonNull ColorSpace colorSpace) {
|
||||||
|
if (components.length < colorSpace.getComponentCount() + 1) {
|
||||||
|
throw new IllegalArgumentException("Received a component array of length " +
|
||||||
|
components.length + " but the color model requires " +
|
||||||
|
(colorSpace.getComponentCount() + 1) + " (including alpha)");
|
||||||
|
}
|
||||||
|
return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long pack(@ColorInt int color) {
|
||||||
|
return (color & 0xffffffffL) << 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long pack(float red, float green, float blue) {
|
||||||
|
return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long pack(float red, float green, float blue, float alpha) {
|
||||||
|
return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long pack(float red, float green, float blue, float alpha,
|
||||||
|
@NonNull ColorSpace colorSpace) {
|
||||||
|
if (colorSpace.isSrgb()) {
|
||||||
|
int argb =
|
||||||
|
((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||||
|
((int) (red * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (green * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (blue * 255.0f + 0.5f);
|
||||||
|
return (argb & 0xffffffffL) << 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = colorSpace.getId();
|
||||||
|
if (id == ColorSpace.MIN_ID) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown color space, please use a color space returned by ColorSpace.get()");
|
||||||
|
}
|
||||||
|
if (colorSpace.getComponentCount() > 3) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"The color space must use a color model with at most 3 components");
|
||||||
|
}
|
||||||
|
|
||||||
|
@HalfFloat short r = Half.toHalf(red);
|
||||||
|
@HalfFloat short g = Half.toHalf(green);
|
||||||
|
@HalfFloat short b = Half.toHalf(blue);
|
||||||
|
|
||||||
|
int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
|
||||||
|
|
||||||
|
// Suppress sign extension
|
||||||
|
return (r & 0xffffL) << 48 |
|
||||||
|
(g & 0xffffL) << 32 |
|
||||||
|
(b & 0xffffL) << 16 |
|
||||||
|
(a & 0x3ffL ) << 6 |
|
||||||
|
id & 0x3fL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
|
||||||
|
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||||
|
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||||
|
float b = ((color ) & 0xff) / 255.0f;
|
||||||
|
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||||
|
ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||||
|
return convert(r, g, b, a, source, colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||||
|
float r = red(color);
|
||||||
|
float g = green(color);
|
||||||
|
float b = blue(color);
|
||||||
|
float a = alpha(color);
|
||||||
|
ColorSpace source = colorSpace(color);
|
||||||
|
return convert(r, g, b, a, source, colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long convert(float r, float g, float b, float a,
|
||||||
|
@NonNull ColorSpace source, @NonNull ColorSpace destination) {
|
||||||
|
float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
|
||||||
|
return pack(c[0], c[1], c[2], a, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
|
||||||
|
float r = red(color);
|
||||||
|
float g = green(color);
|
||||||
|
float b = blue(color);
|
||||||
|
float a = alpha(color);
|
||||||
|
return convert(r, g, b, a, connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorLong
|
||||||
|
public static long convert(float r, float g, float b, float a,
|
||||||
|
@NonNull ColorSpace.Connector connector) {
|
||||||
|
float[] c = connector.transform(r, g, b);
|
||||||
|
return pack(c[0], c[1], c[2], a, connector.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float luminance(@ColorLong long color) {
|
||||||
|
ColorSpace colorSpace = colorSpace(color);
|
||||||
|
if (colorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||||
|
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||||
|
"color space. The supplied color space is " + colorSpace.getModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
|
||||||
|
double r = eotf.applyAsDouble(red(color));
|
||||||
|
double g = eotf.applyAsDouble(green(color));
|
||||||
|
double b = eotf.applyAsDouble(blue(color));
|
||||||
|
|
||||||
|
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float saturate(float v) {
|
||||||
|
return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntRange(from = 0, to = 255)
|
||||||
|
public static int alpha(int color) {
|
||||||
|
return color >>> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntRange(from = 0, to = 255)
|
||||||
|
public static int red(int color) {
|
||||||
|
return (color >> 16) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntRange(from = 0, to = 255)
|
||||||
|
public static int green(int color) {
|
||||||
|
return (color >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntRange(from = 0, to = 255)
|
||||||
|
public static int blue(int color) {
|
||||||
|
return color & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int rgb(
|
||||||
|
@IntRange(from = 0, to = 255) int red,
|
||||||
|
@IntRange(from = 0, to = 255) int green,
|
||||||
|
@IntRange(from = 0, to = 255) int blue) {
|
||||||
|
return 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int rgb(float red, float green, float blue) {
|
||||||
|
return 0xff000000 |
|
||||||
|
((int) (red * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (green * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (blue * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int argb(
|
||||||
|
@IntRange(from = 0, to = 255) int alpha,
|
||||||
|
@IntRange(from = 0, to = 255) int red,
|
||||||
|
@IntRange(from = 0, to = 255) int green,
|
||||||
|
@IntRange(from = 0, to = 255) int blue) {
|
||||||
|
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int argb(float alpha, float red, float green, float blue) {
|
||||||
|
return ((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||||
|
((int) (red * 255.0f + 0.5f) << 16) |
|
||||||
|
((int) (green * 255.0f + 0.5f) << 8) |
|
||||||
|
(int) (blue * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float luminance(@ColorInt int color) {
|
||||||
|
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
|
||||||
|
DoubleUnaryOperator eotf = cs.getEotf();
|
||||||
|
|
||||||
|
double r = eotf.applyAsDouble(red(color) / 255.0);
|
||||||
|
double g = eotf.applyAsDouble(green(color) / 255.0);
|
||||||
|
double b = eotf.applyAsDouble(blue(color) / 255.0);
|
||||||
|
|
||||||
|
return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int parseColor(@Size(min=1) String colorString) {
|
||||||
|
if (colorString.charAt(0) == '#') {
|
||||||
|
// Use a long to avoid rollovers on #ffXXXXXX
|
||||||
|
long color = Long.parseLong(colorString.substring(1), 16);
|
||||||
|
if (colorString.length() == 7) {
|
||||||
|
// Set the alpha value
|
||||||
|
color |= 0x00000000ff000000;
|
||||||
|
} else if (colorString.length() != 9) {
|
||||||
|
throw new IllegalArgumentException("Unknown color");
|
||||||
|
}
|
||||||
|
return (int)color;
|
||||||
|
} else {
|
||||||
|
Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
|
||||||
|
if (color != null) {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown color");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RGBToHSV(
|
||||||
|
@IntRange(from = 0, to = 255) int red,
|
||||||
|
@IntRange(from = 0, to = 255) int green,
|
||||||
|
@IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
|
||||||
|
if (hsv.length < 3) {
|
||||||
|
throw new RuntimeException("3 components required for hsv");
|
||||||
|
}
|
||||||
|
nativeRGBToHSV(red, green, blue, hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) {
|
||||||
|
RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int HSVToColor(@Size(3) float hsv[]) {
|
||||||
|
return HSVToColor(0xFF, hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
|
||||||
|
if (hsv.length < 3) {
|
||||||
|
throw new RuntimeException("3 components required for hsv");
|
||||||
|
}
|
||||||
|
return nativeHSVToColor(alpha, hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]);
|
||||||
|
private static native int nativeHSVToColor(int alpha, float hsv[]);
|
||||||
|
|
||||||
|
private static final HashMap<String, Integer> sColorNameMap;
|
||||||
|
static {
|
||||||
|
sColorNameMap = new HashMap<>();
|
||||||
|
sColorNameMap.put("black", BLACK);
|
||||||
|
sColorNameMap.put("darkgray", DKGRAY);
|
||||||
|
sColorNameMap.put("gray", GRAY);
|
||||||
|
sColorNameMap.put("lightgray", LTGRAY);
|
||||||
|
sColorNameMap.put("white", WHITE);
|
||||||
|
sColorNameMap.put("red", RED);
|
||||||
|
sColorNameMap.put("green", GREEN);
|
||||||
|
sColorNameMap.put("blue", BLUE);
|
||||||
|
sColorNameMap.put("yellow", YELLOW);
|
||||||
|
sColorNameMap.put("cyan", CYAN);
|
||||||
|
sColorNameMap.put("magenta", MAGENTA);
|
||||||
|
sColorNameMap.put("aqua", 0xFF00FFFF);
|
||||||
|
sColorNameMap.put("fuchsia", 0xFFFF00FF);
|
||||||
|
sColorNameMap.put("darkgrey", DKGRAY);
|
||||||
|
sColorNameMap.put("grey", GRAY);
|
||||||
|
sColorNameMap.put("lightgrey", LTGRAY);
|
||||||
|
sColorNameMap.put("lime", 0xFF00FF00);
|
||||||
|
sColorNameMap.put("maroon", 0xFF800000);
|
||||||
|
sColorNameMap.put("navy", 0xFF000080);
|
||||||
|
sColorNameMap.put("olive", 0xFF808000);
|
||||||
|
sColorNameMap.put("purple", 0xFF800080);
|
||||||
|
sColorNameMap.put("silver", 0xFFC0C0C0);
|
||||||
|
sColorNameMap.put("teal", 0xFF008080);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
|||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
public final class Rect {
|
||||||
|
public int left;
|
||||||
|
public int top;
|
||||||
|
public int right;
|
||||||
|
public int bottom;
|
||||||
|
|
||||||
|
private static final class UnflattenHelper {
|
||||||
|
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
||||||
|
"(-?\\d+) (-?\\d+) (-?\\d+) (-?\\d+)");
|
||||||
|
|
||||||
|
static Matcher getMatcher(String str) {
|
||||||
|
return FLATTENED_PATTERN.matcher(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect(int left, int top, int right, int bottom) {
|
||||||
|
this.left = left;
|
||||||
|
this.top = top;
|
||||||
|
this.right = right;
|
||||||
|
this.bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect(Rect r) {
|
||||||
|
if (r == null) {
|
||||||
|
this.left = 0;
|
||||||
|
this.top = 0;
|
||||||
|
this.right = 0;
|
||||||
|
this.bottom = 0;
|
||||||
|
} else {
|
||||||
|
this.left = r.left;
|
||||||
|
this.top = r.top;
|
||||||
|
this.right = r.right;
|
||||||
|
this.bottom = r.bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int left, int top, int right, int bottom) {
|
||||||
|
this.left = left;
|
||||||
|
this.top = top;
|
||||||
|
this.right = right;
|
||||||
|
this.bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Rect r) {
|
||||||
|
this.left = r.left;
|
||||||
|
this.top = r.top;
|
||||||
|
this.right = r.right;
|
||||||
|
this.bottom = r.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getWidth() {
|
||||||
|
return right - left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getHeight() {
|
||||||
|
return bottom - top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Rect unflattenFromString(String str) {
|
||||||
|
if (str.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = UnflattenHelper.getMatcher(str);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Rect(Integer.parseInt(matcher.group(1)),
|
||||||
|
Integer.parseInt(matcher.group(2)),
|
||||||
|
Integer.parseInt(matcher.group(3)),
|
||||||
|
Integer.parseInt(matcher.group(4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toShortString() {
|
||||||
|
return toShortString(new StringBuilder(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toShortString(StringBuilder sb) {
|
||||||
|
sb.setLength(0);
|
||||||
|
sb.append('['); sb.append(left); sb.append(',');
|
||||||
|
sb.append(top); sb.append("]["); sb.append(right);
|
||||||
|
sb.append(','); sb.append(bottom); sb.append(']');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String flattenToString() {
|
||||||
|
StringBuilder sb = new StringBuilder(32);
|
||||||
|
sb.append(left);
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(top);
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(right);
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(bottom);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
|
out.writeInt(left);
|
||||||
|
out.writeInt(top);
|
||||||
|
out.writeInt(right);
|
||||||
|
out.writeInt(bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
|
||||||
|
@Override
|
||||||
|
public Rect createFromParcel(Parcel in) {
|
||||||
|
Rect r = new Rect();
|
||||||
|
r.readFromParcel(in);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rect[] newArray(int size) {
|
||||||
|
return new Rect[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void readFromParcel(Parcel in) {
|
||||||
|
left = in.readInt();
|
||||||
|
top = in.readInt();
|
||||||
|
right = in.readInt();
|
||||||
|
bottom = in.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class TemporaryBuffer {
|
||||||
|
public static char[] obtain(int len) {
|
||||||
|
char[] buf;
|
||||||
|
|
||||||
|
synchronized (TemporaryBuffer.class) {
|
||||||
|
buf = sTemp;
|
||||||
|
sTemp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf == null || buf.length < len) {
|
||||||
|
buf = ArrayUtils.newUnpaddedCharArray(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void recycle(char[] temp) {
|
||||||
|
if (temp.length > 1000) return;
|
||||||
|
|
||||||
|
synchronized (TemporaryBuffer.class) {
|
||||||
|
sTemp = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] sTemp = null;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.graphics;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.TestApi;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.android.internal.util.Preconditions;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
|
||||||
|
public class Typeface {
|
||||||
|
|
||||||
|
private static String TAG = "Typeface";
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
|
||||||
|
|
||||||
|
/** The default NORMAL typeface object */
|
||||||
|
public static final Typeface DEFAULT;
|
||||||
|
public static final Typeface DEFAULT_BOLD;
|
||||||
|
/** The NORMAL style of the default sans serif typeface. */
|
||||||
|
public static final Typeface SANS_SERIF;
|
||||||
|
/** The NORMAL style of the default serif typeface. */
|
||||||
|
public static final Typeface SERIF;
|
||||||
|
/** The NORMAL style of the default monospace typeface. */
|
||||||
|
public static final Typeface MONOSPACE;
|
||||||
|
|
||||||
|
public @interface Style {}
|
||||||
|
|
||||||
|
// Style
|
||||||
|
public static final int NORMAL = 0;
|
||||||
|
public static final int BOLD = 1;
|
||||||
|
public static final int ITALIC = 2;
|
||||||
|
public static final int BOLD_ITALIC = 3;
|
||||||
|
/** @hide */ public static final int STYLE_MASK = 0x03;
|
||||||
|
|
||||||
|
public static final String DEFAULT_FAMILY = "sans-serif";
|
||||||
|
|
||||||
|
private final Font mFont;
|
||||||
|
private final List<Font> mFallbackFonts;
|
||||||
|
|
||||||
|
/** Returns the typeface's weight value */
|
||||||
|
public int getWeight() {
|
||||||
|
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||||
|
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||||
|
if (weight instanceof Float) {
|
||||||
|
float w = ((Float) weight).floatValue();
|
||||||
|
// undo the transformation
|
||||||
|
return (int) ((w - TextAttribute.WEIGHT_REGULAR) / (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) * (Builder.BOLD_WEIGHT - Builder.NORMAL_WEIGHT) + Builder.NORMAL_WEIGHT);
|
||||||
|
}
|
||||||
|
return Builder.NORMAL_WEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getJavaWeight() {
|
||||||
|
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||||
|
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||||
|
if (weight instanceof Float) {
|
||||||
|
return ((Float) weight).floatValue();
|
||||||
|
}
|
||||||
|
return TextAttribute.WEIGHT_REGULAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the typeface's intrinsic style attributes */
|
||||||
|
public @Style int getStyle() {
|
||||||
|
if (isBold() && isItalic()) return BOLD_ITALIC;
|
||||||
|
if (isBold()) return BOLD;
|
||||||
|
if (isItalic()) return ITALIC;
|
||||||
|
return NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if getStyle() has the BOLD bit set. */
|
||||||
|
public final boolean isBold() {
|
||||||
|
return mFont.isBold();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if getStyle() has the ITALIC bit set. */
|
||||||
|
public final boolean isItalic() {
|
||||||
|
return mFont.isItalic();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final @Nullable String getSystemFontFamilyName() {
|
||||||
|
return mFont.getFamily();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface findFromCache(AssetManager mgr, String path) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
/** @hide */
|
||||||
|
public static final int NORMAL_WEIGHT = 400;
|
||||||
|
/** @hide */
|
||||||
|
public static final int BOLD_WEIGHT = 700;
|
||||||
|
|
||||||
|
private final String mPath;
|
||||||
|
|
||||||
|
private Font mFont;
|
||||||
|
private int mStyle;
|
||||||
|
private Map<TextAttribute, Object> mAttributes;
|
||||||
|
|
||||||
|
private String mFallbackFamilyName;
|
||||||
|
|
||||||
|
public Builder(@NonNull File path) {
|
||||||
|
mFont = loadFont(path);
|
||||||
|
Log.v(TAG, "Font loaded from " + path.toURI());
|
||||||
|
mPath = null;
|
||||||
|
mStyle = 0;
|
||||||
|
mAttributes = new HashMap<TextAttribute, Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(@NonNull FileDescriptor fd) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(@NonNull String path) {
|
||||||
|
mFont = loadFont(new File(path));
|
||||||
|
mPath = path;
|
||||||
|
mStyle = 0;
|
||||||
|
mAttributes = new HashMap<TextAttribute, Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
|
||||||
|
int cookie) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setWeight(int weight) {
|
||||||
|
// java font weight does not follow typical weight distribution
|
||||||
|
// In Java, regular weight is at 1.0 and bold at 2.0, compared to 400 and 700 in TTF
|
||||||
|
// Typical range is 0 to 1000
|
||||||
|
|
||||||
|
float jWeight = (weight - NORMAL_WEIGHT) / (BOLD_WEIGHT - NORMAL_WEIGHT) * (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) + TextAttribute.WEIGHT_REGULAR;
|
||||||
|
mAttributes.put(TextAttribute.WEIGHT, jWeight);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setItalic(boolean italic) {
|
||||||
|
if (italic) {
|
||||||
|
mStyle |= Font.ITALIC;
|
||||||
|
} else {
|
||||||
|
mStyle &= ~Font.ITALIC;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setTtcIndex(int ttcIndex) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFontVariationSettings(@Nullable String variationSettings) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFallback(@Nullable String familyName) {
|
||||||
|
mFallbackFamilyName = familyName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Typeface resolveFallbackTypeface() {
|
||||||
|
if (mFallbackFamilyName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Typeface(new Font(mFallbackFamilyName, mStyle, 12).deriveFont(mAttributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface build() {
|
||||||
|
if (mFont == null) return resolveFallbackTypeface();
|
||||||
|
return new Typeface(mFont.deriveFont(mStyle).deriveFont(mAttributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Font loadFont(File fontFile) {
|
||||||
|
try {
|
||||||
|
return Font.createFont(Font.TRUETYPE_FONT, fontFile);
|
||||||
|
} catch (FontFormatException ex) {
|
||||||
|
Log.v(TAG, "Failed to create font as TTF", ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Log.v(TAG, "Failed to create font as TTF", ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Font.createFont(Font.TYPE1_FONT, fontFile);
|
||||||
|
} catch (FontFormatException ex) {
|
||||||
|
Log.v(TAG, "Failed to create font as T1", ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Log.v(TAG, "Failed to create font as T1", ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface create(String familyName, @Style int style) {
|
||||||
|
return create(getSystemDefaultTypeface(familyName), style);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface create(Typeface family, @Style int style) {
|
||||||
|
if ((style & ~STYLE_MASK) != 0) {
|
||||||
|
style = NORMAL;
|
||||||
|
}
|
||||||
|
if (family == null) {
|
||||||
|
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Typeface create(@Nullable Typeface family,
|
||||||
|
int weight, boolean italic) {
|
||||||
|
Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
|
||||||
|
if (family == null) {
|
||||||
|
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
|
||||||
|
}
|
||||||
|
return createWeightStyle(family, weight, italic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
|
||||||
|
int weight, boolean italic) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface defaultFromStyle(@Style int style) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface createFromAsset(AssetManager mgr, String path) {
|
||||||
|
Preconditions.checkNotNull(path); // for backward compatibility
|
||||||
|
Preconditions.checkNotNull(mgr);
|
||||||
|
|
||||||
|
Typeface typeface = new Builder(mgr, path).build();
|
||||||
|
if (typeface != null) return typeface;
|
||||||
|
// check if the file exists, and throw an exception for backward compatibility
|
||||||
|
try (InputStream inputStream = mgr.open(path)) {
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Font asset not found " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Typeface.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface(Font fnt) {
|
||||||
|
mFont = fnt;
|
||||||
|
mFallbackFonts = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface(Font fnt, List<Font> fallbackFonts) {
|
||||||
|
mFont = fnt;
|
||||||
|
mFallbackFonts = fallbackFonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<TextAttribute, Object> getAttributes() {
|
||||||
|
return (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(Map<TextAttribute, Object> attributes) {
|
||||||
|
Font mainFont = mFont.deriveFont(attributes);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(attributes))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(float size) {
|
||||||
|
Font mainFont = mFont.deriveFont(size);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(size))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Typeface deriveFont(int style, float size) {
|
||||||
|
Font mainFont = mFont.deriveFont(style, size);
|
||||||
|
List<Font> fallbacks = mFallbackFonts.stream().map(font -> font.deriveFont(style, size))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Typeface(mainFont, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedString createWithFallback(String text) {
|
||||||
|
AttributedString result = new AttributedString(text);
|
||||||
|
|
||||||
|
int textLength = text.length();
|
||||||
|
result.addAttribute(TextAttribute.FONT, mFont, 0, textLength);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (true) {
|
||||||
|
int until = mFont.canDisplayUpTo(result.getIterator(), i, textLength);
|
||||||
|
if (until == -1) break;
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
// find a fallback font from `until`
|
||||||
|
for (int j = 0; j < mFallbackFonts.size(); ++j) {
|
||||||
|
int fallbackUntil = until;
|
||||||
|
for (; fallbackUntil < textLength; ++fallbackUntil) {
|
||||||
|
if (mFont.canDisplay(text.charAt(fallbackUntil)) || !mFallbackFonts.get(j).canDisplay(text.charAt(fallbackUntil)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fallbackUntil > until) {
|
||||||
|
// use this and advance
|
||||||
|
int end = fallbackUntil >= 0 ? fallbackUntil : textLength;
|
||||||
|
result.addAttribute(TextAttribute.FONT, mFallbackFonts.get(j), until, end);
|
||||||
|
Log.v(TAG, String.format("Fallback: from %d to %d using %s", until, end, mFallbackFonts.get(j).getName()));
|
||||||
|
i = end;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) continue;
|
||||||
|
|
||||||
|
Log.w(TAG, String.format("No fallback font found at %d, skipping", until));
|
||||||
|
i = until + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface createFromFile(@Nullable File file) {
|
||||||
|
// For the compatibility reasons, leaving possible NPE here.
|
||||||
|
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
|
||||||
|
|
||||||
|
Typeface typeface = new Builder(file).build();
|
||||||
|
if (typeface != null) return typeface;
|
||||||
|
|
||||||
|
// check if the file exists, and throw an exception for backward compatibility
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Typeface.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Typeface createFromFile(@Nullable String path) {
|
||||||
|
Preconditions.checkNotNull(path); // for backward compatibility
|
||||||
|
return createFromFile(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
|
||||||
|
return new Typeface(new Font(familyName, 0, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Font getFont() {
|
||||||
|
return mFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Typeface typeface = (Typeface) o;
|
||||||
|
|
||||||
|
return typeface.mFont.equals(mFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mFont.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Font loadFontAsset(String font) {
|
||||||
|
try (InputStream defaultNormalStream = ClassLoader.getSystemClassLoader().getResourceAsStream("font/" + font)) {
|
||||||
|
return Font.createFont(Font.TRUETYPE_FONT, defaultNormalStream).deriveFont(12.0f);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e(TAG, "Failed to load " + font, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Typeface withFallback(Font baseFallback, String mainFont, String... fonts) {
|
||||||
|
Font main = loadFontAsset(mainFont);
|
||||||
|
if (main == null) main = new Font(null, 0, 12);
|
||||||
|
List<Font> fallbacks = Stream.concat(Arrays.stream(fonts).map(Typeface::loadFontAsset).filter(f -> f != null), Stream.of(baseFallback))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Log.v(TAG, String.format("Loaded font %s with %d fallback fonts", main.getName(), fallbacks.size()));
|
||||||
|
return new Typeface(main, fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEFAULT = withFallback(new Font(null, 0, 12), "NotoSans/NotoSans-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
|
DEFAULT_BOLD = DEFAULT.deriveFont(Font.BOLD);
|
||||||
|
SANS_SERIF = DEFAULT;
|
||||||
|
SERIF = withFallback(new Font(Font.SERIF, 0, 12), "NotoSans/NotoSerif-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
|
MONOSPACE = withFallback(new Font(Font.MONOSPACED, 0, 12), "NotoSans/NotoSansMono-VariableFont_wdth,wght.ttf", "NotoSans/NotoSansSymbols2-Regular.ttf", "NotoSans/NotoEmoji-VariableFont_wght.ttf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package android.os;
|
package android.os;
|
||||||
|
|
||||||
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ import java.io.File;
|
|||||||
* Android compatibility layer for files
|
* Android compatibility layer for files
|
||||||
*/
|
*/
|
||||||
public class Environment {
|
public class Environment {
|
||||||
private static AndroidFiles androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class);
|
private static AndroidFiles androidFiles = KoinGlobalHelper.instance(AndroidFiles.class);
|
||||||
|
|
||||||
public static String DIRECTORY_ALARMS = getHomeDirectory("Alarms").getAbsolutePath();
|
public static String DIRECTORY_ALARMS = getHomeDirectory("Alarms").getAbsolutePath();
|
||||||
public static String DIRECTORY_DCIM = getHomeDirectory("DCIM").getAbsolutePath();
|
public static String DIRECTORY_DCIM = getHomeDirectory("DCIM").getAbsolutePath();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
/* //device/java/android/android/app/IActivityPendingResult.aidl
|
||||||
|
**
|
||||||
|
** Copyright 2007, The Android Open Source Project
|
||||||
|
**
|
||||||
|
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
** you may not use this file except in compliance with the License.
|
||||||
|
** You may obtain a copy of the License at
|
||||||
|
**
|
||||||
|
** http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
**
|
||||||
|
** Unless required by applicable law or agreed to in writing, software
|
||||||
|
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
** See the License for the specific language governing permissions and
|
||||||
|
** limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
/* oneway */ interface IMessenger {
|
||||||
|
void send(/* in */ Message msg);
|
||||||
|
}
|
||||||
@@ -0,0 +1,575 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Printer;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.util.proto.ProtoOutputStream;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to run a message loop for a thread. Threads by default do
|
||||||
|
* not have a message loop associated with them; to create one, call
|
||||||
|
* {@link #prepare} in the thread that is to run the loop, and then
|
||||||
|
* {@link #loop} to have it process messages until the loop is stopped.
|
||||||
|
*
|
||||||
|
* <p>Most interaction with a message loop is through the
|
||||||
|
* {@link Handler} class.
|
||||||
|
*
|
||||||
|
* <p>This is a typical example of the implementation of a Looper thread,
|
||||||
|
* using the separation of {@link #prepare} and {@link #loop} to create an
|
||||||
|
* initial Handler to communicate with the Looper.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* class LooperThread extends Thread {
|
||||||
|
* public Handler mHandler;
|
||||||
|
*
|
||||||
|
* public void run() {
|
||||||
|
* Looper.prepare();
|
||||||
|
*
|
||||||
|
* mHandler = new Handler(Looper.myLooper()) {
|
||||||
|
* public void handleMessage(Message msg) {
|
||||||
|
* // process incoming messages here
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* Looper.loop();
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public final class Looper {
|
||||||
|
/*
|
||||||
|
* API Implementation Note:
|
||||||
|
*
|
||||||
|
* This class contains the code required to set up and manage an event loop
|
||||||
|
* based on MessageQueue. APIs that affect the state of the queue should be
|
||||||
|
* defined on MessageQueue or Handler rather than on Looper itself. For example,
|
||||||
|
* idle handlers and sync barriers are defined on the queue whereas preparing the
|
||||||
|
* thread, looping, and quitting are defined on the looper.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final String TAG = "Looper";
|
||||||
|
|
||||||
|
private static class NoImagePreloadHolder {
|
||||||
|
// Enable/Disable verbose logging with a system prop. e.g.
|
||||||
|
// adb shell 'setprop log.looper.slow.verbose false && stop && start'
|
||||||
|
private static final boolean sVerboseLogging =
|
||||||
|
SystemProperties.getBoolean("log.looper.slow.verbose", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sThreadLocal.get() will return null unless you've called prepare().
|
||||||
|
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
|
||||||
|
private static Looper sMainLooper; // guarded by Looper.class
|
||||||
|
private static Observer sObserver;
|
||||||
|
|
||||||
|
final MessageQueue mQueue;
|
||||||
|
final Thread mThread;
|
||||||
|
private boolean mInLoop;
|
||||||
|
|
||||||
|
private Printer mLogging;
|
||||||
|
private long mTraceTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the looper will show a warning log if a message dispatch takes longer than this.
|
||||||
|
*/
|
||||||
|
private long mSlowDispatchThresholdMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the looper will show a warning log if a message delivery (actual delivery time -
|
||||||
|
* post time) takes longer than this.
|
||||||
|
*/
|
||||||
|
private long mSlowDeliveryThresholdMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if a message delivery takes longer than {@link #mSlowDeliveryThresholdMs}.
|
||||||
|
*/
|
||||||
|
private boolean mSlowDeliveryDetected;
|
||||||
|
|
||||||
|
/** Initialize the current thread as a looper.
|
||||||
|
* This gives you a chance to create handlers that then reference
|
||||||
|
* this looper, before actually starting the loop. Be sure to call
|
||||||
|
* {@link #loop()} after calling this method, and end it by calling
|
||||||
|
* {@link #quit()}.
|
||||||
|
*/
|
||||||
|
public static void prepare() {
|
||||||
|
prepare(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void prepare(boolean quitAllowed) {
|
||||||
|
if (sThreadLocal.get() != null) {
|
||||||
|
throw new RuntimeException("Only one Looper may be created per thread");
|
||||||
|
}
|
||||||
|
sThreadLocal.set(new Looper(quitAllowed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the current thread as a looper, marking it as an
|
||||||
|
* application's main looper. See also: {@link #prepare()}
|
||||||
|
*
|
||||||
|
* @deprecated The main looper for your application is created by the Android environment,
|
||||||
|
* so you should never need to call this function yourself.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static void prepareMainLooper() {
|
||||||
|
prepare(false);
|
||||||
|
synchronized (Looper.class) {
|
||||||
|
if (sMainLooper != null) {
|
||||||
|
throw new IllegalStateException("The main Looper has already been prepared.");
|
||||||
|
}
|
||||||
|
sMainLooper = myLooper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the application's main looper, which lives in the main thread of the application.
|
||||||
|
*/
|
||||||
|
public static Looper getMainLooper() {
|
||||||
|
synchronized (Looper.class) {
|
||||||
|
return sMainLooper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the application's main looper to the given value. The main looper is typically
|
||||||
|
* configured automatically by the OS, so this capability is only intended to enable testing.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void setMainLooperForTest(@NonNull Looper looper) {
|
||||||
|
synchronized (Looper.class) {
|
||||||
|
sMainLooper = Objects.requireNonNull(looper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the application's main looper to be undefined. The main looper is typically
|
||||||
|
* configured automatically by the OS, so this capability is only intended to enable testing.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void clearMainLooperForTest() {
|
||||||
|
synchronized (Looper.class) {
|
||||||
|
sMainLooper = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the transaction observer for all Loopers in this process.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void setObserver(@Nullable Observer observer) {
|
||||||
|
sObserver = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll and deliver single message, return true if the outer loop should continue.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
|
||||||
|
"ClearIdentityCallNotFollowedByTryFinally"})
|
||||||
|
private static boolean loopOnce(final Looper me,
|
||||||
|
final long ident, final int thresholdOverride) {
|
||||||
|
Message msg = me.mQueue.next(); // might block
|
||||||
|
if (msg == null) {
|
||||||
|
// No message indicates that the message queue is quitting.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This must be in a local variable, in case a UI event sets the logger
|
||||||
|
final Printer logging = me.mLogging;
|
||||||
|
if (logging != null) {
|
||||||
|
logging.println(">>>>> Dispatching to " + msg.target + " "
|
||||||
|
+ msg.callback + ": " + msg.what);
|
||||||
|
}
|
||||||
|
// Make sure the observer won't change while processing a transaction.
|
||||||
|
final Observer observer = sObserver;
|
||||||
|
|
||||||
|
final long traceTag = me.mTraceTag;
|
||||||
|
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
|
||||||
|
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
|
||||||
|
|
||||||
|
final boolean hasOverride = thresholdOverride >= 0;
|
||||||
|
if (hasOverride) {
|
||||||
|
slowDispatchThresholdMs = thresholdOverride;
|
||||||
|
slowDeliveryThresholdMs = thresholdOverride;
|
||||||
|
}
|
||||||
|
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
|
||||||
|
&& (msg.when > 0);
|
||||||
|
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);
|
||||||
|
|
||||||
|
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
|
||||||
|
final boolean needEndTime = logSlowDispatch;
|
||||||
|
|
||||||
|
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
|
||||||
|
final long dispatchEnd;
|
||||||
|
Object token = null;
|
||||||
|
if (observer != null) {
|
||||||
|
token = observer.messageDispatchStarting();
|
||||||
|
}
|
||||||
|
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
|
||||||
|
try {
|
||||||
|
msg.target.dispatchMessage(msg);
|
||||||
|
if (observer != null) {
|
||||||
|
observer.messageDispatched(token, msg);
|
||||||
|
}
|
||||||
|
} catch (Exception exception) {
|
||||||
|
if (observer != null) {
|
||||||
|
observer.dispatchingThrewException(token, msg, exception);
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Loop handler threw", exception);
|
||||||
|
// throw exception;
|
||||||
|
} finally {
|
||||||
|
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
|
||||||
|
ThreadLocalWorkSource.restore(origWorkSource);
|
||||||
|
}
|
||||||
|
if (logSlowDelivery) {
|
||||||
|
boolean slow = false;
|
||||||
|
|
||||||
|
if (!me.mSlowDeliveryDetected || NoImagePreloadHolder.sVerboseLogging) {
|
||||||
|
slow = showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart,
|
||||||
|
"delivery", msg);
|
||||||
|
}
|
||||||
|
if (me.mSlowDeliveryDetected) {
|
||||||
|
if (!slow && (dispatchStart - msg.when) <= 10) {
|
||||||
|
Slog.w(TAG, "Drained");
|
||||||
|
me.mSlowDeliveryDetected = false;
|
||||||
|
}
|
||||||
|
} else if (slow) {
|
||||||
|
// A slow delivery is detected, suppressing further logs unless verbose logging
|
||||||
|
// is enabled.
|
||||||
|
me.mSlowDeliveryDetected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logSlowDispatch) {
|
||||||
|
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logging != null) {
|
||||||
|
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that during the course of dispatching the
|
||||||
|
// identity of the thread wasn't corrupted.
|
||||||
|
// final long newIdent = Binder.clearCallingIdentity();
|
||||||
|
// if (ident != newIdent) {
|
||||||
|
// Log.wtf(TAG, "Thread identity changed from 0x"
|
||||||
|
// + Long.toHexString(ident) + " to 0x"
|
||||||
|
// + Long.toHexString(newIdent) + " while dispatching to "
|
||||||
|
// + msg.target.getClass().getName() + " "
|
||||||
|
// + msg.callback + " what=" + msg.what);
|
||||||
|
// }
|
||||||
|
|
||||||
|
msg.recycleUnchecked();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the message queue in this thread. Be sure to call
|
||||||
|
* {@link #quit()} to end the loop.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
|
||||||
|
"ClearIdentityCallNotFollowedByTryFinally",
|
||||||
|
"ResultOfClearIdentityCallNotStoredInVariable"})
|
||||||
|
public static void loop() {
|
||||||
|
final Looper me = myLooper();
|
||||||
|
if (me == null) {
|
||||||
|
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
|
||||||
|
}
|
||||||
|
if (me.mInLoop) {
|
||||||
|
Slog.w(TAG, "Loop again would have the queued messages be executed"
|
||||||
|
+ " before this one completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
me.mInLoop = true;
|
||||||
|
|
||||||
|
// Make sure the identity of this thread is that of the local process,
|
||||||
|
// and keep track of what that identity token actually is.
|
||||||
|
// Binder.clearCallingIdentity();
|
||||||
|
// final long ident = Binder.clearCallingIdentity();
|
||||||
|
final long ident = 0;
|
||||||
|
|
||||||
|
// Allow overriding a threshold with a system prop. e.g.
|
||||||
|
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
|
||||||
|
final int thresholdOverride = getThresholdOverride();
|
||||||
|
|
||||||
|
me.mSlowDeliveryDetected = false;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (!loopOnce(me, ident, thresholdOverride)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getThresholdOverride() {
|
||||||
|
return -1;
|
||||||
|
// // Allow overriding the threshold for all processes' main looper with a system prop.
|
||||||
|
// // e.g. adb shell 'setprop log.looper.any.main.slow 1 && stop && start'
|
||||||
|
// if (myLooper() == getMainLooper()) {
|
||||||
|
// final int globalOverride = SystemProperties.getInt("log.looper.any.main.slow", -1);
|
||||||
|
// if (globalOverride >= 0) {
|
||||||
|
// return globalOverride;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Allow overriding the threshold for all threads within a process with a system prop.
|
||||||
|
// // e.g. adb shell 'setprop log.looper.1000.any.slow 1 && stop && start'
|
||||||
|
// final int processOverride = SystemProperties.getInt("log.looper."
|
||||||
|
// + Process.myUid() + ".any.slow", -1);
|
||||||
|
// if (processOverride >= 0) {
|
||||||
|
// return processOverride;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return SystemProperties.getInt("log.looper."
|
||||||
|
// + Process.myUid() + "."
|
||||||
|
// + Thread.currentThread().getName()
|
||||||
|
// + ".slow", -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getThresholdOverride$ravenwood() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getThreadGroup() {
|
||||||
|
int threadGroup = Process.THREAD_GROUP_DEFAULT;
|
||||||
|
|
||||||
|
if (!Process.isIsolated()) {
|
||||||
|
threadGroup = Process.getProcessGroup(Process.myTid());
|
||||||
|
}
|
||||||
|
return threadGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String threadGroupToString(int threadGroup) {
|
||||||
|
switch (threadGroup) {
|
||||||
|
case Process.THREAD_GROUP_SYSTEM:
|
||||||
|
return "SYSTEM";
|
||||||
|
case Process.THREAD_GROUP_AUDIO_APP:
|
||||||
|
return "AUDIO_APP";
|
||||||
|
case Process.THREAD_GROUP_AUDIO_SYS:
|
||||||
|
return "AUDIO_SYS";
|
||||||
|
case Process.THREAD_GROUP_TOP_APP:
|
||||||
|
return "TOP_APP";
|
||||||
|
case Process.THREAD_GROUP_RT_APP:
|
||||||
|
return "RT_APP";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
|
||||||
|
String what, Message msg) {
|
||||||
|
final long actualTime = measureEnd - measureStart;
|
||||||
|
if (actualTime < threshold) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = /* Process.myProcessName() */ "Stub!";
|
||||||
|
String threadGroup = threadGroupToString(getThreadGroup());
|
||||||
|
boolean isMain = myLooper() == getMainLooper();
|
||||||
|
|
||||||
|
// For slow delivery, the current message isn't really important, but log it anyway.
|
||||||
|
Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
|
||||||
|
+ Thread.currentThread().getName() + " app=" + name
|
||||||
|
+ " main=" + isMain + " group=" + threadGroup
|
||||||
|
+ " h=" + msg.target.getClass().getName() + " c=" + msg.callback
|
||||||
|
+ " m=" + msg.what);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Looper object associated with the current thread. Returns
|
||||||
|
* null if the calling thread is not associated with a Looper.
|
||||||
|
*/
|
||||||
|
public static @Nullable Looper myLooper() {
|
||||||
|
return sThreadLocal.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link MessageQueue} object associated with the current
|
||||||
|
* thread. This must be called from a thread running a Looper, or a
|
||||||
|
* NullPointerException will be thrown.
|
||||||
|
*/
|
||||||
|
public static @NonNull MessageQueue myQueue() {
|
||||||
|
return myLooper().mQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Looper(boolean quitAllowed) {
|
||||||
|
mQueue = new MessageQueue(quitAllowed);
|
||||||
|
mThread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the current thread is this looper's thread.
|
||||||
|
*/
|
||||||
|
public boolean isCurrentThread() {
|
||||||
|
return Thread.currentThread() == mThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control logging of messages as they are processed by this Looper. If
|
||||||
|
* enabled, a log message will be written to <var>printer</var>
|
||||||
|
* at the beginning and ending of each message dispatch, identifying the
|
||||||
|
* target Handler and message contents.
|
||||||
|
*
|
||||||
|
* @param printer A Printer object that will receive log messages, or
|
||||||
|
* null to disable message logging.
|
||||||
|
*/
|
||||||
|
public void setMessageLogging(@Nullable Printer printer) {
|
||||||
|
mLogging = printer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
public void setTraceTag(long traceTag) {
|
||||||
|
mTraceTag = traceTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a thresholds for slow dispatch/delivery log.
|
||||||
|
* {@hide}
|
||||||
|
*/
|
||||||
|
public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) {
|
||||||
|
mSlowDispatchThresholdMs = slowDispatchThresholdMs;
|
||||||
|
mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quits the looper.
|
||||||
|
* <p>
|
||||||
|
* Causes the {@link #loop} method to terminate without processing any
|
||||||
|
* more messages in the message queue.
|
||||||
|
* </p><p>
|
||||||
|
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
|
||||||
|
* For example, the {@link Handler#sendMessage(Message)} method will return false.
|
||||||
|
* </p><p class="note">
|
||||||
|
* Using this method may be unsafe because some messages may not be delivered
|
||||||
|
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
|
||||||
|
* that all pending work is completed in an orderly manner.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see #quitSafely
|
||||||
|
*/
|
||||||
|
public void quit() {
|
||||||
|
mQueue.quit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quits the looper safely.
|
||||||
|
* <p>
|
||||||
|
* Causes the {@link #loop} method to terminate as soon as all remaining messages
|
||||||
|
* in the message queue that are already due to be delivered have been handled.
|
||||||
|
* However pending delayed messages with due times in the future will not be
|
||||||
|
* delivered before the loop terminates.
|
||||||
|
* </p><p>
|
||||||
|
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
|
||||||
|
* For example, the {@link Handler#sendMessage(Message)} method will return false.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public void quitSafely() {
|
||||||
|
mQueue.quit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Thread associated with this Looper.
|
||||||
|
*
|
||||||
|
* @return The looper's thread.
|
||||||
|
*/
|
||||||
|
public @NonNull Thread getThread() {
|
||||||
|
return mThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets this looper's message queue.
|
||||||
|
*
|
||||||
|
* @return The looper's message queue.
|
||||||
|
*/
|
||||||
|
public @NonNull MessageQueue getQueue() {
|
||||||
|
return mQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the state of the looper for debugging purposes.
|
||||||
|
*
|
||||||
|
* @param pw A printer to receive the contents of the dump.
|
||||||
|
* @param prefix A prefix to prepend to each line which is printed.
|
||||||
|
*/
|
||||||
|
public void dump(@NonNull Printer pw, @NonNull String prefix) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the state of the looper for debugging purposes.
|
||||||
|
*
|
||||||
|
* @param pw A printer to receive the contents of the dump.
|
||||||
|
* @param prefix A prefix to prepend to each line which is printed.
|
||||||
|
* @param handler Only dump messages for this Handler.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
|
||||||
|
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
public interface Observer {
|
||||||
|
/**
|
||||||
|
* Called right before a message is dispatched.
|
||||||
|
*
|
||||||
|
* <p> The token type is not specified to allow the implementation to specify its own type.
|
||||||
|
*
|
||||||
|
* @return a token used for collecting telemetry when dispatching a single message.
|
||||||
|
* The token token must be passed back exactly once to either
|
||||||
|
* {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
|
||||||
|
* and must not be reused again.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Object messageDispatchStarting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a message was processed by a Handler.
|
||||||
|
*
|
||||||
|
* @param token Token obtained by previously calling
|
||||||
|
* {@link Observer#messageDispatchStarting} on the same Observer instance.
|
||||||
|
* @param msg The message that was dispatched.
|
||||||
|
*/
|
||||||
|
void messageDispatched(Object token, Message msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an exception was thrown while processing a message.
|
||||||
|
*
|
||||||
|
* @param token Token obtained by previously calling
|
||||||
|
* {@link Observer#messageDispatchStarting} on the same Observer instance.
|
||||||
|
* @param msg The message that was dispatched and caused an exception.
|
||||||
|
* @param exception The exception that was thrown.
|
||||||
|
*/
|
||||||
|
void dispatchingThrewException(Object token, Message msg, Exception exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,630 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.util.TimeUtils;
|
||||||
|
import android.util.proto.ProtoOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Defines a message containing a description and arbitrary data object that can be
|
||||||
|
* sent to a {@link Handler}. This object contains two extra int fields and an
|
||||||
|
* extra object field that allow you to not do allocations in many cases.
|
||||||
|
*
|
||||||
|
* <p class="note">While the constructor of Message is public, the best way to get
|
||||||
|
* one of these is to call {@link #obtain Message.obtain()} or one of the
|
||||||
|
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
|
||||||
|
* them from a pool of recycled objects.</p>
|
||||||
|
*/
|
||||||
|
public final class Message implements Parcelable {
|
||||||
|
/**
|
||||||
|
* User-defined message code so that the recipient can identify
|
||||||
|
* what this message is about. Each {@link Handler} has its own name-space
|
||||||
|
* for message codes, so you do not need to worry about yours conflicting
|
||||||
|
* with other handlers.
|
||||||
|
*
|
||||||
|
* If not specified, this value is 0.
|
||||||
|
* Use values other than 0 to indicate custom message codes.
|
||||||
|
*/
|
||||||
|
public int what;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* arg1 and arg2 are lower-cost alternatives to using
|
||||||
|
* {@link #setData(Bundle) setData()} if you only need to store a
|
||||||
|
* few integer values.
|
||||||
|
*/
|
||||||
|
public int arg1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* arg1 and arg2 are lower-cost alternatives to using
|
||||||
|
* {@link #setData(Bundle) setData()} if you only need to store a
|
||||||
|
* few integer values.
|
||||||
|
*/
|
||||||
|
public int arg2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrary object to send to the recipient. When using
|
||||||
|
* {@link Messenger} to send the message across processes this can only
|
||||||
|
* be non-null if it contains a Parcelable of a framework class (not one
|
||||||
|
* implemented by the application). For other data transfer use
|
||||||
|
* {@link #setData}.
|
||||||
|
*
|
||||||
|
* <p>Note that Parcelable objects here are not supported prior to
|
||||||
|
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
|
||||||
|
*/
|
||||||
|
public Object obj;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional Messenger where replies to this message can be sent. The
|
||||||
|
* semantics of exactly how this is used are up to the sender and
|
||||||
|
* receiver.
|
||||||
|
*/
|
||||||
|
public Messenger replyTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the uid is not set;
|
||||||
|
*
|
||||||
|
* @hide Only for use within the system server.
|
||||||
|
*/
|
||||||
|
public static final int UID_NONE = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional field indicating the uid that sent the message. This is
|
||||||
|
* only valid for messages posted by a {@link Messenger}; otherwise,
|
||||||
|
* it will be -1.
|
||||||
|
*/
|
||||||
|
public int sendingUid = UID_NONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional field indicating the uid that caused this message to be enqueued.
|
||||||
|
*
|
||||||
|
* @hide Only for use within the system server.
|
||||||
|
*/
|
||||||
|
public int workSourceUid = UID_NONE;
|
||||||
|
|
||||||
|
/** If set message is in use.
|
||||||
|
* This flag is set when the message is enqueued and remains set while it
|
||||||
|
* is delivered and afterwards when it is recycled. The flag is only cleared
|
||||||
|
* when a new message is created or obtained since that is the only time that
|
||||||
|
* applications are allowed to modify the contents of the message.
|
||||||
|
*
|
||||||
|
* It is an error to attempt to enqueue or recycle a message that is already in use.
|
||||||
|
*/
|
||||||
|
/*package*/ static final int FLAG_IN_USE = 1 << 0;
|
||||||
|
|
||||||
|
/** If set message is asynchronous */
|
||||||
|
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
|
||||||
|
|
||||||
|
/** Flags to clear in the copyFrom method */
|
||||||
|
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
|
||||||
|
|
||||||
|
/*package*/ int flags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The targeted delivery time of this message. The time-base is
|
||||||
|
* {@link SystemClock#uptimeMillis}.
|
||||||
|
* @hide Only for use within the tests.
|
||||||
|
*/
|
||||||
|
public long when;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public long mInsertSeq;
|
||||||
|
|
||||||
|
/*package*/ Bundle data;
|
||||||
|
|
||||||
|
/*package*/ Handler target;
|
||||||
|
|
||||||
|
/*package*/ Runnable callback;
|
||||||
|
|
||||||
|
// sometimes we store linked lists of these things
|
||||||
|
/*package*/ Message next;
|
||||||
|
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final Object sPoolSync = new Object();
|
||||||
|
private static Message sPool;
|
||||||
|
private static int sPoolSize = 0;
|
||||||
|
|
||||||
|
private static final int MAX_POOL_SIZE = 50;
|
||||||
|
|
||||||
|
private static boolean gCheckRecycle = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new Message instance from the global pool. Allows us to
|
||||||
|
* avoid allocating new objects in many cases.
|
||||||
|
*/
|
||||||
|
public static Message obtain() {
|
||||||
|
synchronized (sPoolSync) {
|
||||||
|
if (sPool != null) {
|
||||||
|
Message m = sPool;
|
||||||
|
sPool = m.next;
|
||||||
|
m.next = null;
|
||||||
|
m.flags = 0; // clear in-use flag
|
||||||
|
sPoolSize--;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Message();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but copies the values of an existing
|
||||||
|
* message (including its target) into the new one.
|
||||||
|
* @param orig Original message to copy.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Message orig) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.what = orig.what;
|
||||||
|
m.arg1 = orig.arg1;
|
||||||
|
m.arg2 = orig.arg2;
|
||||||
|
m.obj = orig.obj;
|
||||||
|
m.replyTo = orig.replyTo;
|
||||||
|
m.sendingUid = orig.sendingUid;
|
||||||
|
m.workSourceUid = orig.workSourceUid;
|
||||||
|
if (orig.data != null) {
|
||||||
|
m.data = new Bundle(orig.data);
|
||||||
|
}
|
||||||
|
m.target = orig.target;
|
||||||
|
m.callback = orig.callback;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
|
||||||
|
* @param h Handler to assign to the returned Message object's <em>target</em> member.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
|
||||||
|
* the Message that is returned.
|
||||||
|
* @param h Handler to assign to the returned Message object's <em>target</em> member.
|
||||||
|
* @param callback Runnable that will execute when the message is handled.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h, Runnable callback) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
m.callback = callback;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but sets the values for both <em>target</em> and
|
||||||
|
* <em>what</em> members on the Message.
|
||||||
|
* @param h Value to assign to the <em>target</em> member.
|
||||||
|
* @param what Value to assign to the <em>what</em> member.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h, int what) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
m.what = what;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
|
||||||
|
* members.
|
||||||
|
* @param h The <em>target</em> value to set.
|
||||||
|
* @param what The <em>what</em> value to set.
|
||||||
|
* @param obj The <em>object</em> method to set.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h, int what, Object obj) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
m.what = what;
|
||||||
|
m.obj = obj;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
|
||||||
|
* <em>arg1</em>, and <em>arg2</em> members.
|
||||||
|
*
|
||||||
|
* @param h The <em>target</em> value to set.
|
||||||
|
* @param what The <em>what</em> value to set.
|
||||||
|
* @param arg1 The <em>arg1</em> value to set.
|
||||||
|
* @param arg2 The <em>arg2</em> value to set.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h, int what, int arg1, int arg2) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
m.what = what;
|
||||||
|
m.arg1 = arg1;
|
||||||
|
m.arg2 = arg2;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
|
||||||
|
* <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
|
||||||
|
*
|
||||||
|
* @param h The <em>target</em> value to set.
|
||||||
|
* @param what The <em>what</em> value to set.
|
||||||
|
* @param arg1 The <em>arg1</em> value to set.
|
||||||
|
* @param arg2 The <em>arg2</em> value to set.
|
||||||
|
* @param obj The <em>obj</em> value to set.
|
||||||
|
* @return A Message object from the global pool.
|
||||||
|
*/
|
||||||
|
public static Message obtain(Handler h, int what,
|
||||||
|
int arg1, int arg2, Object obj) {
|
||||||
|
Message m = obtain();
|
||||||
|
m.target = h;
|
||||||
|
m.what = what;
|
||||||
|
m.arg1 = arg1;
|
||||||
|
m.arg2 = arg2;
|
||||||
|
m.obj = obj;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static void updateCheckRecycle(int targetSdkVersion) {
|
||||||
|
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
gCheckRecycle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Message instance to the global pool.
|
||||||
|
* <p>
|
||||||
|
* You MUST NOT touch the Message after calling this function because it has
|
||||||
|
* effectively been freed. It is an error to recycle a message that is currently
|
||||||
|
* enqueued or that is in the process of being delivered to a Handler.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public void recycle() {
|
||||||
|
if (isInUse()) {
|
||||||
|
if (gCheckRecycle) {
|
||||||
|
throw new IllegalStateException("This message cannot be recycled because it "
|
||||||
|
+ "is still in use.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recycleUnchecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recycles a Message that may be in-use.
|
||||||
|
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
|
||||||
|
*/
|
||||||
|
void recycleUnchecked() {
|
||||||
|
// Mark the message as in use while it remains in the recycled object pool.
|
||||||
|
// Clear out all other details.
|
||||||
|
flags = FLAG_IN_USE;
|
||||||
|
what = 0;
|
||||||
|
arg1 = 0;
|
||||||
|
arg2 = 0;
|
||||||
|
obj = null;
|
||||||
|
replyTo = null;
|
||||||
|
sendingUid = UID_NONE;
|
||||||
|
workSourceUid = UID_NONE;
|
||||||
|
when = 0;
|
||||||
|
target = null;
|
||||||
|
callback = null;
|
||||||
|
data = null;
|
||||||
|
|
||||||
|
synchronized (sPoolSync) {
|
||||||
|
if (sPoolSize < MAX_POOL_SIZE) {
|
||||||
|
next = sPool;
|
||||||
|
sPool = this;
|
||||||
|
sPoolSize++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make this message like o. Performs a shallow copy of the data field.
|
||||||
|
* Does not copy the linked list fields, nor the timestamp or
|
||||||
|
* target/callback of the original message.
|
||||||
|
*/
|
||||||
|
public void copyFrom(Message o) {
|
||||||
|
this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
|
||||||
|
this.what = o.what;
|
||||||
|
this.arg1 = o.arg1;
|
||||||
|
this.arg2 = o.arg2;
|
||||||
|
this.obj = o.obj;
|
||||||
|
this.replyTo = o.replyTo;
|
||||||
|
this.sendingUid = o.sendingUid;
|
||||||
|
this.workSourceUid = o.workSourceUid;
|
||||||
|
|
||||||
|
if (o.data != null) {
|
||||||
|
this.data = (Bundle) o.data.clone();
|
||||||
|
} else {
|
||||||
|
this.data = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the targeted delivery time of this message, in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getWhen() {
|
||||||
|
return when;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget(Handler target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the {@link android.os.Handler Handler} implementation that
|
||||||
|
* will receive this message. The object must implement
|
||||||
|
* {@link android.os.Handler#handleMessage(android.os.Message)
|
||||||
|
* Handler.handleMessage()}. Each Handler has its own name-space for
|
||||||
|
* message codes, so you do not need to
|
||||||
|
* worry about yours conflicting with other handlers.
|
||||||
|
*/
|
||||||
|
public Handler getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve callback object that will execute when this message is handled.
|
||||||
|
* This object must implement Runnable. This is called by
|
||||||
|
* the <em>target</em> {@link Handler} that is receiving this Message to
|
||||||
|
* dispatch it. If
|
||||||
|
* not set, the message will be dispatched to the receiving Handler's
|
||||||
|
* {@link Handler#handleMessage(Message)}.
|
||||||
|
*/
|
||||||
|
public Runnable getCallback() {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public Message setCallback(Runnable r) {
|
||||||
|
callback = r;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a Bundle of arbitrary data associated with this
|
||||||
|
* event, lazily creating it if necessary. Set this value by calling
|
||||||
|
* {@link #setData(Bundle)}. Note that when transferring data across
|
||||||
|
* processes via {@link Messenger}, you will need to set your ClassLoader
|
||||||
|
* on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
|
||||||
|
* Bundle.setClassLoader()} so that it can instantiate your objects when
|
||||||
|
* you retrieve them.
|
||||||
|
* @see #peekData()
|
||||||
|
* @see #setData(Bundle)
|
||||||
|
*/
|
||||||
|
public Bundle getData() {
|
||||||
|
if (data == null) {
|
||||||
|
data = new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like getData(), but does not lazily create the Bundle. A null
|
||||||
|
* is returned if the Bundle does not already exist. See
|
||||||
|
* {@link #getData} for further information on this.
|
||||||
|
* @see #getData()
|
||||||
|
* @see #setData(Bundle)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Bundle peekData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a Bundle of arbitrary data values. Use arg1 and arg2 members
|
||||||
|
* as a lower cost way to send a few simple integer values, if you can.
|
||||||
|
* @see #getData()
|
||||||
|
* @see #peekData()
|
||||||
|
*/
|
||||||
|
public void setData(Bundle data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chainable setter for {@link #what}
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public Message setWhat(int what) {
|
||||||
|
this.what = what;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends this Message to the Handler specified by {@link #getTarget}.
|
||||||
|
* Throws a null pointer exception if this field has not been set.
|
||||||
|
*/
|
||||||
|
public void sendToTarget() {
|
||||||
|
target.sendMessage(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the message is asynchronous, meaning that it is not
|
||||||
|
* subject to {@link Looper} synchronization barriers.
|
||||||
|
*
|
||||||
|
* @return True if the message is asynchronous.
|
||||||
|
*
|
||||||
|
* @see #setAsynchronous(boolean)
|
||||||
|
*/
|
||||||
|
public boolean isAsynchronous() {
|
||||||
|
return (flags & FLAG_ASYNCHRONOUS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the message is asynchronous, meaning that it is not
|
||||||
|
* subject to {@link Looper} synchronization barriers.
|
||||||
|
* <p>
|
||||||
|
* Certain operations, such as view invalidation, may introduce synchronization
|
||||||
|
* barriers into the {@link Looper}'s message queue to prevent subsequent messages
|
||||||
|
* from being delivered until some condition is met. In the case of view invalidation,
|
||||||
|
* messages which are posted after a call to {@link android.view.View#invalidate}
|
||||||
|
* are suspended by means of a synchronization barrier until the next frame is
|
||||||
|
* ready to be drawn. The synchronization barrier ensures that the invalidation
|
||||||
|
* request is completely handled before resuming.
|
||||||
|
* </p><p>
|
||||||
|
* Asynchronous messages are exempt from synchronization barriers. They typically
|
||||||
|
* represent interrupts, input events, and other signals that must be handled independently
|
||||||
|
* even while other work has been suspended.
|
||||||
|
* </p><p>
|
||||||
|
* Note that asynchronous messages may be delivered out of order with respect to
|
||||||
|
* synchronous messages although they are always delivered in order among themselves.
|
||||||
|
* If the relative order of these messages matters then they probably should not be
|
||||||
|
* asynchronous in the first place. Use with caution.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param async True if the message is asynchronous.
|
||||||
|
*
|
||||||
|
* @see #isAsynchronous()
|
||||||
|
*/
|
||||||
|
public void setAsynchronous(boolean async) {
|
||||||
|
if (async) {
|
||||||
|
flags |= FLAG_ASYNCHRONOUS;
|
||||||
|
} else {
|
||||||
|
flags &= ~FLAG_ASYNCHRONOUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ boolean isInUse() {
|
||||||
|
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*package*/ void markInUse() {
|
||||||
|
flags |= FLAG_IN_USE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
|
||||||
|
*/
|
||||||
|
public Message() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toString(SystemClock.uptimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
String toString(long now) {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
b.append("{ when=");
|
||||||
|
TimeUtils.formatDuration(when - now, b);
|
||||||
|
|
||||||
|
if (target != null) {
|
||||||
|
if (callback != null) {
|
||||||
|
b.append(" callback=");
|
||||||
|
b.append(callback.getClass().getName());
|
||||||
|
} else {
|
||||||
|
b.append(" what=");
|
||||||
|
b.append(what);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg1 != 0) {
|
||||||
|
b.append(" arg1=");
|
||||||
|
b.append(arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg2 != 0) {
|
||||||
|
b.append(" arg2=");
|
||||||
|
b.append(arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj != null) {
|
||||||
|
b.append(" obj=");
|
||||||
|
b.append(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.append(" target=");
|
||||||
|
b.append(target.getClass().getName());
|
||||||
|
} else {
|
||||||
|
b.append(" barrier=");
|
||||||
|
b.append(arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.append(" }");
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
|
||||||
|
= new Parcelable.Creator<Message>() {
|
||||||
|
public Message createFromParcel(Parcel source) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.readFromParcel(source);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message[] newArray(int size) {
|
||||||
|
return new Message[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
if (callback != null) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Can't marshal callbacks across processes.");
|
||||||
|
}
|
||||||
|
dest.writeInt(what);
|
||||||
|
dest.writeInt(arg1);
|
||||||
|
dest.writeInt(arg2);
|
||||||
|
if (obj != null) {
|
||||||
|
try {
|
||||||
|
Parcelable p = (Parcelable)obj;
|
||||||
|
dest.writeInt(1);
|
||||||
|
dest.writeParcelable(p, flags);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Can't marshal non-Parcelable objects across processes.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.writeInt(0);
|
||||||
|
}
|
||||||
|
dest.writeLong(when);
|
||||||
|
dest.writeBundle(data);
|
||||||
|
Messenger.writeMessengerOrNullToParcel(replyTo, dest);
|
||||||
|
dest.writeInt(sendingUid);
|
||||||
|
dest.writeInt(workSourceUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readFromParcel(Parcel source) {
|
||||||
|
what = source.readInt();
|
||||||
|
arg1 = source.readInt();
|
||||||
|
arg2 = source.readInt();
|
||||||
|
if (source.readInt() != 0) {
|
||||||
|
obj = source.readParcelable(getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
when = source.readLong();
|
||||||
|
data = source.readBundle();
|
||||||
|
replyTo = Messenger.readMessengerOrNullFromParcel(source);
|
||||||
|
sendingUid = source.readInt();
|
||||||
|
workSourceUid = source.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -42,19 +42,19 @@ public class SystemProperties {
|
|||||||
return native_get(key);
|
return native_get(key);
|
||||||
}
|
}
|
||||||
private static int native_get_int(String key, int def) {
|
private static int native_get_int(String key, int def) {
|
||||||
if(configModule.hasProperty(key))
|
if(!configModule.hasProperty(key))
|
||||||
return def;
|
return def;
|
||||||
else
|
else
|
||||||
return configModule.getIntProperty(key);
|
return configModule.getIntProperty(key);
|
||||||
}
|
}
|
||||||
private static long native_get_long(String key, long def) {
|
private static long native_get_long(String key, long def) {
|
||||||
if(configModule.hasProperty(key))
|
if(!configModule.hasProperty(key))
|
||||||
return def;
|
return def;
|
||||||
else
|
else
|
||||||
return configModule.getLongProperty(key);
|
return configModule.getLongProperty(key);
|
||||||
}
|
}
|
||||||
private static boolean native_get_boolean(String key, boolean def) {
|
private static boolean native_get_boolean(String key, boolean def) {
|
||||||
if(configModule.hasProperty(key))
|
if(!configModule.hasProperty(key))
|
||||||
return def;
|
return def;
|
||||||
else
|
else
|
||||||
return configModule.getBooleanProperty(key);
|
return configModule.getBooleanProperty(key);
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks who triggered the work currently executed on this thread.
|
||||||
|
*
|
||||||
|
* <p>ThreadLocalWorkSource is automatically updated inside system server for incoming/outgoing
|
||||||
|
* binder calls and messages posted to handler threads.
|
||||||
|
*
|
||||||
|
* <p>ThreadLocalWorkSource can also be set manually if needed to refine the WorkSource.
|
||||||
|
*
|
||||||
|
* <p>Example:
|
||||||
|
* <ul>
|
||||||
|
* <li>Bluetooth process calls {@link PowerManager#isInteractive()} API on behalf of app foo.
|
||||||
|
* <li>ThreadLocalWorkSource will be automatically set to the UID of foo.
|
||||||
|
* <li>Any code on the thread handling {@link PowerManagerService#isInteractive()} can call
|
||||||
|
* {@link ThreadLocalWorkSource#getUid()} to blame any resource used to handle this call.
|
||||||
|
* <li>If a message is posted from the binder thread, the code handling the message can also call
|
||||||
|
* {@link ThreadLocalWorkSource#getUid()} and it will return the UID of foo since the work source is
|
||||||
|
* automatically propagated.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @hide Only for use within system server.
|
||||||
|
*/
|
||||||
|
public final class ThreadLocalWorkSource {
|
||||||
|
public static final int UID_NONE = Message.UID_NONE;
|
||||||
|
private static final ThreadLocal<int []> sWorkSourceUid =
|
||||||
|
ThreadLocal.withInitial(() -> new int[] {UID_NONE});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the UID to blame for the code currently executed on this thread.
|
||||||
|
*
|
||||||
|
* <p>This UID is set automatically by common frameworks (e.g. Binder and Handler frameworks)
|
||||||
|
* and automatically propagated inside system server.
|
||||||
|
* <p>It can also be set manually using {@link #setUid(int)}.
|
||||||
|
*/
|
||||||
|
public static int getUid() {
|
||||||
|
return sWorkSourceUid.get()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the UID to blame for the code currently executed on this thread.
|
||||||
|
*
|
||||||
|
* <p>Inside system server, this UID will be automatically propagated.
|
||||||
|
* <p>It will be used to attribute future resources used on this thread (e.g. binder
|
||||||
|
* transactions or processing handler messages) and on any other threads the UID is propagated
|
||||||
|
* to.
|
||||||
|
*
|
||||||
|
* @return a token that can be used to restore the state.
|
||||||
|
*/
|
||||||
|
public static long setUid(int uid) {
|
||||||
|
final long token = getToken();
|
||||||
|
sWorkSourceUid.get()[0] = uid;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the state using the provided token.
|
||||||
|
*/
|
||||||
|
public static void restore(long token) {
|
||||||
|
sWorkSourceUid.get()[0] = parseUidFromToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the stored work source uid.
|
||||||
|
*
|
||||||
|
* <p>This method should be used when we do not know who to blame. If the UID to blame is the
|
||||||
|
* UID of the current process, it is better to attribute the work to the current process
|
||||||
|
* explicitly instead of clearing the work source:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ThreadLocalWorkSource.setUid(Process.myUid());
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return a token that can be used to restore the state.
|
||||||
|
*/
|
||||||
|
public static long clear() {
|
||||||
|
return setUid(UID_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseUidFromToken(long token) {
|
||||||
|
return (int) token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getToken() {
|
||||||
|
return sWorkSourceUid.get()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadLocalWorkSource() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package android.os.shadows;
|
||||||
|
// package org.robolectric.res.android;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique id per object registry. Used to emulate android platform behavior of storing a long
|
||||||
|
* which represents a pointer to an object.
|
||||||
|
*/
|
||||||
|
public class NativeObjRegistry<T> {
|
||||||
|
|
||||||
|
private static final int INITIAL_ID = 1;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final boolean debug;
|
||||||
|
private final HashMap<Long, T> nativeObjToIdMap = new HashMap<Long, T>();
|
||||||
|
private final Map<Long, DebugInfo> idToDebugInfoMap;
|
||||||
|
|
||||||
|
private long nextId = INITIAL_ID;
|
||||||
|
|
||||||
|
public NativeObjRegistry(Class<T> theClass) {
|
||||||
|
this(theClass, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeObjRegistry(Class<T> theClass, boolean debug) {
|
||||||
|
this(theClass.getSimpleName(), debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeObjRegistry(String name) {
|
||||||
|
this(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeObjRegistry(String name, boolean debug) {
|
||||||
|
this.name = name;
|
||||||
|
this.debug = debug;
|
||||||
|
this.idToDebugInfoMap = debug ? new HashMap<>() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getNativeObjectId(T o) {
|
||||||
|
for(Map.Entry<Long, T> entry : nativeObjToIdMap.entrySet()) {
|
||||||
|
if (o == entry.getValue())
|
||||||
|
return entry.getKey();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register and assign a new unique native id for given object (representing a C memory pointer).
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the object was previously registered
|
||||||
|
*/
|
||||||
|
public synchronized long register(T o) {
|
||||||
|
if (o == null)
|
||||||
|
throw new IllegalStateException("Object must not be null");
|
||||||
|
Long nativeId = getNativeObjectId(o);
|
||||||
|
if (nativeId != null) {
|
||||||
|
if (debug) {
|
||||||
|
DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
|
||||||
|
if (debugInfo != null) {
|
||||||
|
System.out.printf(
|
||||||
|
"NativeObjRegistry %s: register %d -> %s already registered:%n", name, nativeId, o);
|
||||||
|
debugInfo.registrationTrace.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Object was previously registered with id " + nativeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeId = nextId;
|
||||||
|
if (debug) {
|
||||||
|
System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o);
|
||||||
|
idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace()));
|
||||||
|
}
|
||||||
|
nativeObjToIdMap.put(nativeId, o);
|
||||||
|
nextId++;
|
||||||
|
return nativeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister an object previously registered with {@link #register(Object)}.
|
||||||
|
*
|
||||||
|
* @param nativeId the unique id (representing a C memory pointer) of the object to unregister.
|
||||||
|
* @throws IllegalStateException if the object was never registered, or was previously
|
||||||
|
* unregistered.
|
||||||
|
*/
|
||||||
|
public synchronized T unregister(long nativeId) {
|
||||||
|
T o = nativeObjToIdMap.remove(nativeId);
|
||||||
|
if (debug) {
|
||||||
|
System.out.printf("NativeObjRegistry %s: unregister %d -> %s%n", name, nativeId, o);
|
||||||
|
new RuntimeException("unregister debug").printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
if (o == null) {
|
||||||
|
if (debug) {
|
||||||
|
DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
|
||||||
|
debugInfo.unregistrationTraces.add(new Trace());
|
||||||
|
if (debugInfo.unregistrationTraces.size() > 1) {
|
||||||
|
System.out.format("NativeObjRegistry %s: Too many unregistrations:%n", name);
|
||||||
|
for (Trace unregistration : debugInfo.unregistrationTraces) {
|
||||||
|
unregistration.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(
|
||||||
|
nativeId + " has already been removed (or was never registered)");
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieve the native object for given id. Throws if object with that id cannot be found */
|
||||||
|
public synchronized T getNativeObject(long nativeId) {
|
||||||
|
T object = nativeObjToIdMap.get(nativeId);
|
||||||
|
if (object != null) {
|
||||||
|
return object;
|
||||||
|
} else {
|
||||||
|
throw new NullPointerException(
|
||||||
|
String.format(
|
||||||
|
"Could not find object with nativeId: %d. Currently registered ids: %s",
|
||||||
|
nativeId, nativeObjToIdMap.keySet()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the native object for the given id.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if no object was registered with the given id before
|
||||||
|
*/
|
||||||
|
public synchronized void update(long nativeId, T o) {
|
||||||
|
T previous = nativeObjToIdMap.get(nativeId);
|
||||||
|
if (previous == null) {
|
||||||
|
throw new IllegalStateException("Native id " + nativeId + " was never registered");
|
||||||
|
}
|
||||||
|
if (debug) {
|
||||||
|
System.out.printf("NativeObjRegistry %s: update %d -> %s%n", name, nativeId, o);
|
||||||
|
idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace()));
|
||||||
|
}
|
||||||
|
nativeObjToIdMap.put(nativeId, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to {@link #getNativeObject(long)} but returns null if object with given id cannot be
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
public synchronized T peekNativeObject(long nativeId) {
|
||||||
|
return nativeObjToIdMap.get(nativeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WARNING -- dangerous! Call {@link #unregister(long)} instead! */
|
||||||
|
public synchronized void clear() {
|
||||||
|
nextId = INITIAL_ID;
|
||||||
|
nativeObjToIdMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DebugInfo {
|
||||||
|
final Trace registrationTrace;
|
||||||
|
final List<Trace> unregistrationTraces = new ArrayList<>();
|
||||||
|
|
||||||
|
public DebugInfo(Trace trace) {
|
||||||
|
registrationTrace = trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Trace extends Throwable {
|
||||||
|
|
||||||
|
private Trace() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package android.os.shadows;
|
||||||
|
// package org.robolectric.shadows;
|
||||||
|
// and badly gutted
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.MessageQueue;
|
||||||
|
import android.os.MessageQueue.IdleHandler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shadow {@link} MessageQueue} for {@link LooperMode.Mode.PAUSED}
|
||||||
|
*
|
||||||
|
* <p>This class should not be referenced directly. Use {@link ShadowMessageQueue} instead.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SynchronizeOnNonFinalField")
|
||||||
|
public class ShadowPausedMessageQueue {
|
||||||
|
|
||||||
|
// just use this class as the native object
|
||||||
|
private static NativeObjRegistry<ShadowPausedMessageQueue> nativeQueueRegistry =
|
||||||
|
new NativeObjRegistry<ShadowPausedMessageQueue>(ShadowPausedMessageQueue.class);
|
||||||
|
private boolean isPolling = false;
|
||||||
|
private Exception uncaughtException = null;
|
||||||
|
|
||||||
|
// shadow constructor instead of nativeInit because nativeInit signature has changed across SDK
|
||||||
|
// versions
|
||||||
|
public static long nativeInit() {
|
||||||
|
return nativeQueueRegistry.register(new ShadowPausedMessageQueue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nativeDestroy(long ptr) {
|
||||||
|
nativeQueueRegistry.unregister(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nativePollOnce(long ptr, int timeoutMillis) {
|
||||||
|
ShadowPausedMessageQueue obj = nativeQueueRegistry.getNativeObject(ptr);
|
||||||
|
obj.nativePollOnce(timeoutMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nativePollOnce(int timeoutMillis) {
|
||||||
|
if (timeoutMillis == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
isPolling = true;
|
||||||
|
try {
|
||||||
|
if (timeoutMillis < 0) {
|
||||||
|
this.wait();
|
||||||
|
} else {
|
||||||
|
this.wait(timeoutMillis);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
isPolling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nativeWake(long ptr) {
|
||||||
|
ShadowPausedMessageQueue obj = nativeQueueRegistry.getNativeObject(ptr);
|
||||||
|
synchronized (obj) {
|
||||||
|
obj.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean nativeIsPolling(long ptr) {
|
||||||
|
return nativeQueueRegistry.getNativeObject(ptr).isPolling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,8 +9,10 @@ import android.content.Context
|
|||||||
class PreferenceManager {
|
class PreferenceManager {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDefaultSharedPreferences(context: Context)
|
fun getDefaultSharedPreferences(context: Context) =
|
||||||
= context.getSharedPreferences(context.applicationInfo.packageName,
|
context.getSharedPreferences(
|
||||||
Context.MODE_PRIVATE)!!
|
context.applicationInfo.packageName,
|
||||||
|
Context.MODE_PRIVATE,
|
||||||
|
)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ package android.text;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.safety.Whitelist;
|
import org.jsoup.safety.Safelist;
|
||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +18,7 @@ import org.xml.sax.XMLReader;
|
|||||||
public class Html {
|
public class Html {
|
||||||
|
|
||||||
public static Spanned fromHtml(String source) {
|
public static Spanned fromHtml(String source) {
|
||||||
return new FakeSpanned(Jsoup.clean(source, Whitelist.none()));
|
return new FakeSpanned(Jsoup.clean(source, Safelist.none()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
|
public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,630 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.text;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.annotation.IntRange;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.font.LineBreakMeasurer;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.font.TextLayout;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
import com.android.internal.util.GrowingArrayUtils;
|
||||||
|
|
||||||
|
|
||||||
|
public class StaticLayout extends Layout {
|
||||||
|
/*
|
||||||
|
* The break iteration is done in native code. The protocol for using the native code is as
|
||||||
|
* follows.
|
||||||
|
*
|
||||||
|
* First, call nInit to setup native line breaker object. Then, for each paragraph, do the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
|
||||||
|
* native.
|
||||||
|
* - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
|
||||||
|
*
|
||||||
|
* After all paragraphs, call finish() to release expensive buffers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static final String TAG = "StaticLayout";
|
||||||
|
|
||||||
|
public final static class Builder {
|
||||||
|
private Builder() {}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
|
||||||
|
@IntRange(from = 0) int end, @NonNull TextPaint paint,
|
||||||
|
@IntRange(from = 0) int width) {
|
||||||
|
Builder b = new Builder();
|
||||||
|
|
||||||
|
// set default initial values
|
||||||
|
b.mText = source;
|
||||||
|
b.mStart = start;
|
||||||
|
b.mEnd = end;
|
||||||
|
b.mPaint = paint;
|
||||||
|
b.mWidth = width;
|
||||||
|
b.mAlignment = Alignment.ALIGN_NORMAL;
|
||||||
|
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||||
|
b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
|
||||||
|
b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
|
||||||
|
b.mIncludePad = true;
|
||||||
|
b.mFallbackLineSpacing = false;
|
||||||
|
b.mEllipsizedWidth = width;
|
||||||
|
b.mEllipsize = null;
|
||||||
|
b.mMaxLines = Integer.MAX_VALUE;
|
||||||
|
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||||
|
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||||
|
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
|
||||||
|
b.mMinimumFontMetrics = null;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// release any expensive state
|
||||||
|
/* package */ void finish() {
|
||||||
|
mText = null;
|
||||||
|
mPaint = null;
|
||||||
|
mLeftIndents = null;
|
||||||
|
mRightIndents = null;
|
||||||
|
mMinimumFontMetrics = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setText(CharSequence source) {
|
||||||
|
return setText(source, 0, source.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setText(@NonNull CharSequence source, int start, int end) {
|
||||||
|
mText = source;
|
||||||
|
mStart = start;
|
||||||
|
mEnd = end;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setPaint(@NonNull TextPaint paint) {
|
||||||
|
mPaint = paint;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setWidth(@IntRange(from = 0) int width) {
|
||||||
|
mWidth = width;
|
||||||
|
if (mEllipsize == null) {
|
||||||
|
mEllipsizedWidth = width;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setAlignment(@NonNull Alignment alignment) {
|
||||||
|
mAlignment = alignment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||||
|
mTextDir = textDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setLineSpacing(float spacingAdd, float spacingMult) {
|
||||||
|
mSpacingAdd = spacingAdd;
|
||||||
|
mSpacingMult = spacingMult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setIncludePad(boolean includePad) {
|
||||||
|
mIncludePad = includePad;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
|
||||||
|
mFallbackLineSpacing = useLineSpacingFromFallbacks;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
|
||||||
|
mEllipsizedWidth = ellipsizedWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
|
||||||
|
mEllipsize = ellipsize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
|
||||||
|
mMaxLines = maxLines;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
|
||||||
|
mBreakStrategy = breakStrategy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
|
||||||
|
mHyphenationFrequency = hyphenationFrequency;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
|
||||||
|
mLeftIndents = leftIndents;
|
||||||
|
mRightIndents = rightIndents;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setJustificationMode(@JustificationMode int justificationMode) {
|
||||||
|
mJustificationMode = justificationMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
/* package */ Builder setAddLastLineLineSpacing(boolean value) {
|
||||||
|
mAddLastLineLineSpacing = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
|
||||||
|
@NonNull
|
||||||
|
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
|
||||||
|
mUseBoundsForWidth = useBoundsForWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
// The corresponding getter is getShiftDrawingOffsetForStartOverhang()
|
||||||
|
@SuppressLint("MissingGetterMatchingBuilder")
|
||||||
|
public Builder setShiftDrawingOffsetForStartOverhang(
|
||||||
|
boolean shiftDrawingOffsetForStartOverhang) {
|
||||||
|
mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCalculateBounds(boolean value) {
|
||||||
|
mCalculateBounds = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
|
||||||
|
mMinimumFontMetrics = minimumFontMetrics;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public StaticLayout build() {
|
||||||
|
StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
|
||||||
|
? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence mText;
|
||||||
|
private int mStart;
|
||||||
|
private int mEnd;
|
||||||
|
private TextPaint mPaint;
|
||||||
|
private int mWidth;
|
||||||
|
private Alignment mAlignment;
|
||||||
|
private TextDirectionHeuristic mTextDir;
|
||||||
|
private float mSpacingMult;
|
||||||
|
private float mSpacingAdd;
|
||||||
|
private boolean mIncludePad;
|
||||||
|
private boolean mFallbackLineSpacing;
|
||||||
|
private int mEllipsizedWidth;
|
||||||
|
private TextUtils.TruncateAt mEllipsize;
|
||||||
|
private int mMaxLines;
|
||||||
|
private int mBreakStrategy;
|
||||||
|
private int mHyphenationFrequency;
|
||||||
|
@Nullable private int[] mLeftIndents;
|
||||||
|
@Nullable private int[] mRightIndents;
|
||||||
|
private int mJustificationMode;
|
||||||
|
private boolean mAddLastLineLineSpacing;
|
||||||
|
private boolean mUseBoundsForWidth;
|
||||||
|
private boolean mShiftDrawingOffsetForStartOverhang;
|
||||||
|
private boolean mCalculateBounds;
|
||||||
|
@Nullable private Paint.FontMetrics mMinimumFontMetrics;
|
||||||
|
|
||||||
|
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticLayout() {
|
||||||
|
super(
|
||||||
|
null, // text
|
||||||
|
null, // paint
|
||||||
|
0, // width
|
||||||
|
null, // alignment
|
||||||
|
null, // textDir
|
||||||
|
1, // spacing multiplier
|
||||||
|
0, // spacing amount
|
||||||
|
false, // include font padding
|
||||||
|
false, // fallback line spacing
|
||||||
|
0, // ellipsized width
|
||||||
|
null, // ellipsize
|
||||||
|
1, // maxLines
|
||||||
|
BREAK_STRATEGY_SIMPLE,
|
||||||
|
HYPHENATION_FREQUENCY_NONE,
|
||||||
|
null, // leftIndents
|
||||||
|
null, // rightIndents
|
||||||
|
JUSTIFICATION_MODE_NONE,
|
||||||
|
false, // useBoundsForWidth
|
||||||
|
false, // shiftDrawingOffsetForStartOverhang
|
||||||
|
null // minimumFontMetrics
|
||||||
|
);
|
||||||
|
|
||||||
|
mColumns = COLUMNS_ELLIPSIZE;
|
||||||
|
mLineDirections = new Directions[2];
|
||||||
|
mLines = new int[2 * mColumns];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, TextPaint paint,
|
||||||
|
int width,
|
||||||
|
Alignment align, float spacingmult, float spacingadd,
|
||||||
|
boolean includepad) {
|
||||||
|
this(source, 0, source.length(), paint, width, align,
|
||||||
|
spacingmult, spacingadd, includepad);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad) {
|
||||||
|
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||||
|
spacingmult, spacingadd, includepad, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad,
|
||||||
|
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
|
||||||
|
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||||
|
TextDirectionHeuristics.FIRSTSTRONG_LTR,
|
||||||
|
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||||
|
TextPaint paint, int outerwidth,
|
||||||
|
Alignment align, TextDirectionHeuristic textDir,
|
||||||
|
float spacingmult, float spacingadd,
|
||||||
|
boolean includepad,
|
||||||
|
TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
|
||||||
|
this(Builder.obtain(source, bufstart, bufend, paint, outerwidth)
|
||||||
|
.setAlignment(align)
|
||||||
|
.setTextDirection(textDir)
|
||||||
|
.setLineSpacing(spacingadd, spacingmult)
|
||||||
|
.setIncludePad(includepad)
|
||||||
|
.setEllipsize(ellipsize)
|
||||||
|
.setEllipsizedWidth(ellipsizedWidth)
|
||||||
|
.setMaxLines(maxLines), includepad,
|
||||||
|
ellipsize != null ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticLayout(Builder b, boolean trackPadding, int columnSize) {
|
||||||
|
super((b.mEllipsize == null) ? b.mText : (b.mText instanceof Spanned)
|
||||||
|
? new SpannedEllipsizer(b.mText) : new Ellipsizer(b.mText),
|
||||||
|
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
|
||||||
|
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
|
||||||
|
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
|
||||||
|
b.mRightIndents, b.mJustificationMode, b.mUseBoundsForWidth,
|
||||||
|
b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics);
|
||||||
|
|
||||||
|
mColumns = columnSize;
|
||||||
|
if (b.mEllipsize != null) {
|
||||||
|
Ellipsizer e = (Ellipsizer) getText();
|
||||||
|
|
||||||
|
e.mLayout = this;
|
||||||
|
e.mWidth = b.mEllipsizedWidth;
|
||||||
|
e.mMethod = b.mEllipsize;
|
||||||
|
throw new UnsupportedOperationException("Ellipsis not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
mLineDirections = new Directions[2];
|
||||||
|
mLines = new int[2 * mColumns];
|
||||||
|
mMaximumVisibleLineCount = b.mMaxLines;
|
||||||
|
|
||||||
|
mLeftIndents = b.mLeftIndents;
|
||||||
|
mRightIndents = b.mRightIndents;
|
||||||
|
|
||||||
|
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||||
|
AttributedString text = getPaint().getTypeface().createWithFallback(str);
|
||||||
|
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
|
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||||
|
// TODO: directions
|
||||||
|
|
||||||
|
float y = 0;
|
||||||
|
while (measurer.getPosition() < str.length()) {
|
||||||
|
int off = mLineCount * mColumns;
|
||||||
|
int want = off + mColumns + TOP;
|
||||||
|
if (want >= mLines.length) {
|
||||||
|
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
|
||||||
|
System.arraycopy(mLines, 0, grow, 0, mLines.length);
|
||||||
|
mLines = grow;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = measurer.getPosition();
|
||||||
|
TextLayout l = measurer.nextLayout(getWidth());
|
||||||
|
mLines[off + START] = pos;
|
||||||
|
mLines[off + TOP] = (int) y;
|
||||||
|
mLines[off + DESCENT] = (int) (l.getDescent() + l.getLeading());
|
||||||
|
mLines[off + EXTRA] = (int) l.getLeading();
|
||||||
|
mLines[off + DIR] |= Layout.DIR_LEFT_TO_RIGHT << DIR_SHIFT;
|
||||||
|
|
||||||
|
y += l.getAscent();
|
||||||
|
y += l.getDescent() + l.getLeading();
|
||||||
|
|
||||||
|
mLines[off + mColumns + START] = measurer.getPosition();
|
||||||
|
mLines[off + mColumns + TOP] = (int) y;
|
||||||
|
|
||||||
|
mLineCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override the base class so we can directly access our members,
|
||||||
|
// rather than relying on member functions.
|
||||||
|
// The logic mirrors that of Layout.getLineForVertical
|
||||||
|
// FIXME: It may be faster to do a linear search for layouts without many lines.
|
||||||
|
@Override
|
||||||
|
public int getLineForVertical(int vertical) {
|
||||||
|
int high = mLineCount;
|
||||||
|
int low = -1;
|
||||||
|
int guess;
|
||||||
|
int[] lines = mLines;
|
||||||
|
while (high - low > 1) {
|
||||||
|
guess = (high + low) >> 1;
|
||||||
|
if (lines[mColumns * guess + TOP] > vertical){
|
||||||
|
high = guess;
|
||||||
|
} else {
|
||||||
|
low = guess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (low < 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineCount() {
|
||||||
|
return mLineCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineTop(int line) {
|
||||||
|
return mLines[mColumns * line + TOP];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineExtra(int line) {
|
||||||
|
return mLines[mColumns * line + EXTRA];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineDescent(int line) {
|
||||||
|
return mLines[mColumns * line + DESCENT];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLineStart(int line) {
|
||||||
|
return mLines[mColumns * line + START] & START_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getParagraphDirection(int line) {
|
||||||
|
return mLines[mColumns * line + DIR] >> DIR_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getLineContainsTab(int line) {
|
||||||
|
return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Directions getLineDirections(int line) {
|
||||||
|
if (line > getLineCount()) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
return new Directions(null);
|
||||||
|
// return mLineDirections[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTopPadding() {
|
||||||
|
return mTopPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBottomPadding() {
|
||||||
|
return mBottomPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To store into single int field, pack the pair of start and end hyphen edit.
|
||||||
|
static int packHyphenEdit(
|
||||||
|
@Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
|
||||||
|
return start << START_HYPHEN_BITS_SHIFT | end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpackStartHyphenEdit(int packedHyphenEdit) {
|
||||||
|
return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpackEndHyphenEdit(int packedHyphenEdit) {
|
||||||
|
return packedHyphenEdit & END_HYPHEN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
|
||||||
|
return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
|
||||||
|
return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndentAdjust(int line, Alignment align) {
|
||||||
|
if (align == Alignment.ALIGN_LEFT) {
|
||||||
|
if (mLeftIndents == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||||
|
}
|
||||||
|
} else if (align == Alignment.ALIGN_RIGHT) {
|
||||||
|
if (mRightIndents == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||||
|
}
|
||||||
|
} else if (align == Alignment.ALIGN_CENTER) {
|
||||||
|
int left = 0;
|
||||||
|
if (mLeftIndents != null) {
|
||||||
|
left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||||
|
}
|
||||||
|
int right = 0;
|
||||||
|
if (mRightIndents != null) {
|
||||||
|
right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||||
|
}
|
||||||
|
return (left - right) >> 1;
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("unhandled alignment " + align);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEllipsisCount(int line) {
|
||||||
|
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLines[mColumns * line + ELLIPSIS_COUNT];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEllipsisStart(int line) {
|
||||||
|
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLines[mColumns * line + ELLIPSIS_START];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public RectF computeDrawingBoundingBox() {
|
||||||
|
// Cache the drawing bounds result because it does not change after created.
|
||||||
|
if (mDrawingBounds == null) {
|
||||||
|
mDrawingBounds = super.computeDrawingBoundingBox();
|
||||||
|
}
|
||||||
|
return mDrawingBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight(boolean cap) {
|
||||||
|
if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
|
||||||
|
&& Log.isLoggable(TAG, Log.WARN)) {
|
||||||
|
Log.w(TAG, "maxLineHeight should not be -1. "
|
||||||
|
+ " maxLines:" + mMaximumVisibleLineCount
|
||||||
|
+ " lineCount:" + mLineCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
|
||||||
|
? mMaxLineHeight : super.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mLineCount;
|
||||||
|
private int mTopPadding, mBottomPadding;
|
||||||
|
private int mColumns;
|
||||||
|
private RectF mDrawingBounds = null; // lazy calculation.
|
||||||
|
|
||||||
|
private boolean mEllipsized;
|
||||||
|
|
||||||
|
private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
|
||||||
|
|
||||||
|
private static final int COLUMNS_NORMAL = 5;
|
||||||
|
private static final int COLUMNS_ELLIPSIZE = 7;
|
||||||
|
private static final int START = 0;
|
||||||
|
private static final int DIR = START;
|
||||||
|
private static final int TAB = START;
|
||||||
|
private static final int TOP = 1;
|
||||||
|
private static final int DESCENT = 2;
|
||||||
|
private static final int EXTRA = 3;
|
||||||
|
private static final int HYPHEN = 4;
|
||||||
|
private static final int ELLIPSIS_START = 5;
|
||||||
|
private static final int ELLIPSIS_COUNT = 6;
|
||||||
|
|
||||||
|
private int[] mLines;
|
||||||
|
private Directions[] mLineDirections;
|
||||||
|
private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
private static final int START_MASK = 0x1FFFFFFF;
|
||||||
|
private static final int DIR_SHIFT = 30;
|
||||||
|
private static final int TAB_MASK = 0x20000000;
|
||||||
|
private static final int HYPHEN_MASK = 0xFF;
|
||||||
|
private static final int START_HYPHEN_BITS_SHIFT = 3;
|
||||||
|
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
|
||||||
|
private static final int END_HYPHEN_MASK = 0x7; // 0b00111
|
||||||
|
|
||||||
|
private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
|
||||||
|
|
||||||
|
private static final char CHAR_NEW_LINE = '\n';
|
||||||
|
|
||||||
|
private static final double EXTRA_ROUNDING = 0.5;
|
||||||
|
|
||||||
|
private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
|
||||||
|
|
||||||
|
// Unused, here because of gray list private API accesses.
|
||||||
|
/*package*/ static class LineBreaks {
|
||||||
|
private static final int INITIAL_SIZE = 16;
|
||||||
|
public int[] breaks = new int[INITIAL_SIZE];
|
||||||
|
public float[] widths = new float[INITIAL_SIZE];
|
||||||
|
public float[] ascents = new float[INITIAL_SIZE];
|
||||||
|
public float[] descents = new float[INITIAL_SIZE];
|
||||||
|
public int[] flags = new int[INITIAL_SIZE]; // hasTab
|
||||||
|
// breaks, widths, and flags should all have the same length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable private int[] mLeftIndents;
|
||||||
|
@Nullable private int[] mRightIndents;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.text;
|
||||||
|
|
||||||
|
import android.annotation.IntRange;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint.FontMetricsInt;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.text.Layout.Directions;
|
||||||
|
import android.text.Layout.TabStops;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.TextMeasurer;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
|
||||||
|
public class TextLine {
|
||||||
|
private TextPaint mPaint;
|
||||||
|
private CharSequence mText;
|
||||||
|
private int mStart;
|
||||||
|
private int mLen;
|
||||||
|
private int mDir;
|
||||||
|
private Directions mDirections;
|
||||||
|
private boolean mHasTabs;
|
||||||
|
private TabStops mTabs;
|
||||||
|
private char[] mChars;
|
||||||
|
private boolean mCharsValid;
|
||||||
|
private Spanned mSpanned;
|
||||||
|
private PrecomputedText mComputed;
|
||||||
|
private RectF mTmpRectForMeasure;
|
||||||
|
private RectF mTmpRectForPaintAPI;
|
||||||
|
private Rect mTmpRectForPrecompute;
|
||||||
|
|
||||||
|
|
||||||
|
public static final class LineInfo {
|
||||||
|
private int mClusterCount;
|
||||||
|
|
||||||
|
public int getClusterCount() {
|
||||||
|
return mClusterCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClusterCount(int clusterCount) {
|
||||||
|
mClusterCount = clusterCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public float getAddedWordSpacingInPx() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAddedLetterSpacingInPx() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isJustifying() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Not allowed to access. If it's for memory leak workaround, it was already fixed M. */
|
||||||
|
private static final TextLine[] sCached = new TextLine[3];
|
||||||
|
|
||||||
|
public static TextLine obtain() {
|
||||||
|
TextLine tl;
|
||||||
|
synchronized (sCached) {
|
||||||
|
for (int i = sCached.length; --i >= 0;) {
|
||||||
|
if (sCached[i] != null) {
|
||||||
|
tl = sCached[i];
|
||||||
|
sCached[i] = null;
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tl = new TextLine();
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextLine recycle(TextLine tl) {
|
||||||
|
synchronized(sCached) {
|
||||||
|
for (int i = 0; i < sCached.length; ++i) {
|
||||||
|
if (sCached[i] == null) {
|
||||||
|
sCached[i] = tl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
|
||||||
|
Directions directions, boolean hasTabs, TabStops tabStops,
|
||||||
|
int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) {
|
||||||
|
mPaint = paint;
|
||||||
|
mText = text;
|
||||||
|
mStart = start;
|
||||||
|
mLen = limit - start;
|
||||||
|
mDir = dir;
|
||||||
|
mDirections = directions;
|
||||||
|
if (mDirections == null) {
|
||||||
|
throw new IllegalArgumentException("Directions cannot be null");
|
||||||
|
}
|
||||||
|
mHasTabs = hasTabs;
|
||||||
|
mSpanned = null;
|
||||||
|
|
||||||
|
if (text instanceof Spanned) {
|
||||||
|
mSpanned = (Spanned) text;
|
||||||
|
}
|
||||||
|
|
||||||
|
mComputed = null;
|
||||||
|
if (text instanceof PrecomputedText) {
|
||||||
|
// Here, no need to check line break strategy or hyphenation frequency since there is no
|
||||||
|
// line break concept here.
|
||||||
|
mComputed = (PrecomputedText) text;
|
||||||
|
if (!mComputed.getParams().getTextPaint().equalsForTextMeasurement(paint)) {
|
||||||
|
mComputed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mTabs = tabStops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
|
||||||
|
int runEnd, int spanStart, int spanEnd) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||||
|
@Nullable LineInfo lineInfo) {
|
||||||
|
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||||
|
AttributedString text = mPaint.getTypeface().createWithFallback(mText.toString());
|
||||||
|
TextMeasurer tm = new TextMeasurer(text.getIterator(), frc);
|
||||||
|
return (float) tm.getLayout(mStart, mStart + mLen).getBounds().getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||||
|
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
|
||||||
|
public static boolean isLineEndSpace(char ch) {
|
||||||
|
return ch == ' ' || ch == '\t' || ch == 0x1680
|
||||||
|
|| (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
|
||||||
|
|| ch == 0x205F || ch == 0x3000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(Canvas c, float x, int top, int y, int bottom) {
|
||||||
|
c.drawText(mText, mStart, mStart + mLen, x, y, mPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.text;
|
||||||
|
|
||||||
|
import android.annotation.ColorInt;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
public class TextPaint extends Paint {
|
||||||
|
|
||||||
|
// Special value 0 means no background paint
|
||||||
|
@ColorInt
|
||||||
|
public int bgColor;
|
||||||
|
public int baselineShift;
|
||||||
|
@ColorInt
|
||||||
|
public int linkColor;
|
||||||
|
public int[] drawableState;
|
||||||
|
public float density = 1.0f;
|
||||||
|
@ColorInt
|
||||||
|
public int underlineColor = 0;
|
||||||
|
|
||||||
|
public float underlineThickness;
|
||||||
|
|
||||||
|
public TextPaint() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextPaint(int flags) {
|
||||||
|
super(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextPaint(Paint p) {
|
||||||
|
super(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(TextPaint tp) {
|
||||||
|
super.set(tp);
|
||||||
|
|
||||||
|
bgColor = tp.bgColor;
|
||||||
|
baselineShift = tp.baselineShift;
|
||||||
|
linkColor = tp.linkColor;
|
||||||
|
drawableState = tp.drawableState;
|
||||||
|
density = tp.density;
|
||||||
|
underlineColor = tp.underlineColor;
|
||||||
|
underlineThickness = tp.underlineThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnderlineText(int color, float thickness) {
|
||||||
|
underlineColor = color;
|
||||||
|
underlineThickness = thickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getUnderlineThickness() {
|
||||||
|
if (underlineColor != 0) { // Return custom thickness only if underline color is set.
|
||||||
|
return underlineThickness;
|
||||||
|
} else {
|
||||||
|
return super.getUnderlineThickness();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
package android.util;
|
package android.util;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.event.Level;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -92,7 +93,7 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int log(int level, String tag, String msg) {
|
private static int log(int level, String tag, String msg) {
|
||||||
logger.info(formatLog(level, tag, msg));
|
logger.atLevel(intToLevel(level)).log(formatLog(tag, msg));
|
||||||
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,39 +102,35 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int log(int level, String tag, String msg, Throwable t) {
|
private static int log(int level, String tag, String msg, Throwable t) {
|
||||||
logger.info(formatLog(level, tag, msg), t);
|
logger.atLevel(intToLevel(level)).setCause(t).log(formatLog(tag, msg));
|
||||||
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
return tag.length() + msg.length(); //Not accurate, but never used anyways
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatLog(int level, String tag, String msg) {
|
private static String formatLog(String tag, String msg) {
|
||||||
StringBuilder first = new StringBuilder("[");
|
StringBuilder first = new StringBuilder("[");
|
||||||
switch(level) {
|
|
||||||
case ASSERT:
|
|
||||||
first.append("ASSERT");
|
|
||||||
break;
|
|
||||||
case DEBUG:
|
|
||||||
first.append("DEBUG");
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
first.append("ERROR");
|
|
||||||
break;
|
|
||||||
case INFO:
|
|
||||||
first.append("INFO");
|
|
||||||
break;
|
|
||||||
case VERBOSE:
|
|
||||||
first.append("VERBOSE");
|
|
||||||
break;
|
|
||||||
case WARN:
|
|
||||||
first.append("WARN");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
first.append("UNKNOWN");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
first.append("] ");
|
|
||||||
first.append(tag);
|
first.append(tag);
|
||||||
first.append(": ");
|
first.append("]: ");
|
||||||
first.append(msg);
|
first.append(msg);
|
||||||
return first.toString();
|
return first.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Level intToLevel(int level) {
|
||||||
|
switch(level) {
|
||||||
|
case ASSERT:
|
||||||
|
return Level.ERROR;
|
||||||
|
case DEBUG:
|
||||||
|
return Level.DEBUG;
|
||||||
|
case ERROR:
|
||||||
|
return Level.ERROR;
|
||||||
|
case INFO:
|
||||||
|
return Level.INFO;
|
||||||
|
case VERBOSE:
|
||||||
|
return Level.TRACE;
|
||||||
|
case WARN:
|
||||||
|
return Level.WARN;
|
||||||
|
default:
|
||||||
|
return Level.INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.util;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache that holds strong references to a limited number of values. Each time
|
||||||
|
* a value is accessed, it is moved to the head of a queue. When a value is
|
||||||
|
* added to a full cache, the value at the end of that queue is evicted and may
|
||||||
|
* become eligible for garbage collection.
|
||||||
|
*
|
||||||
|
* <p>If your cached values hold resources that need to be explicitly released,
|
||||||
|
* override {@link #entryRemoved}.
|
||||||
|
*
|
||||||
|
* <p>If a cache miss should be computed on demand for the corresponding keys,
|
||||||
|
* override {@link #create}. This simplifies the calling code, allowing it to
|
||||||
|
* assume a value will always be returned, even when there's a cache miss.
|
||||||
|
*
|
||||||
|
* <p>By default, the cache size is measured in the number of entries. Override
|
||||||
|
* {@link #sizeOf} to size the cache in different units. For example, this cache
|
||||||
|
* is limited to 4MiB of bitmaps:
|
||||||
|
* <pre> {@code
|
||||||
|
* int cacheSize = 4 * 1024 * 1024; // 4MiB
|
||||||
|
* LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
* protected int sizeOf(String key, Bitmap value) {
|
||||||
|
* return value.getByteCount();
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>This class is thread-safe. Perform multiple cache operations atomically by
|
||||||
|
* synchronizing on the cache: <pre> {@code
|
||||||
|
* synchronized (cache) {
|
||||||
|
* if (cache.get(key) == null) {
|
||||||
|
* cache.put(key, value);
|
||||||
|
* }
|
||||||
|
* }}</pre>
|
||||||
|
*
|
||||||
|
* <p>This class does not allow null to be used as a key or value. A return
|
||||||
|
* value of null from {@link #get}, {@link #put} or {@link #remove} is
|
||||||
|
* unambiguous: the key was not in the cache.
|
||||||
|
*
|
||||||
|
* <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
|
||||||
|
* of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
|
||||||
|
* Support Package</a> for earlier releases.
|
||||||
|
*/
|
||||||
|
public class LruCache<K, V> {
|
||||||
|
private final LinkedHashMap<K, V> map;
|
||||||
|
|
||||||
|
/** Size of this cache in units. Not necessarily the number of elements. */
|
||||||
|
private int size;
|
||||||
|
private int maxSize;
|
||||||
|
|
||||||
|
private int putCount;
|
||||||
|
private int createCount;
|
||||||
|
private int evictionCount;
|
||||||
|
private int hitCount;
|
||||||
|
private int missCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param maxSize for caches that do not override {@link #sizeOf}, this is
|
||||||
|
* the maximum number of entries in the cache. For all other caches,
|
||||||
|
* this is the maximum sum of the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public LruCache(int maxSize) {
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxSize <= 0");
|
||||||
|
}
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the cache.
|
||||||
|
*
|
||||||
|
* @param maxSize The new maximum size.
|
||||||
|
*/
|
||||||
|
public void resize(int maxSize) {
|
||||||
|
if (maxSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxSize <= 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
}
|
||||||
|
trimToSize(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value for {@code key} if it exists in the cache or can be
|
||||||
|
* created by {@code #create}. If a value was returned, it is moved to the
|
||||||
|
* head of the queue. This returns null if a value is not cached and cannot
|
||||||
|
* be created.
|
||||||
|
*/
|
||||||
|
public final V get(K key) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V mapValue;
|
||||||
|
synchronized (this) {
|
||||||
|
mapValue = map.get(key);
|
||||||
|
if (mapValue != null) {
|
||||||
|
hitCount++;
|
||||||
|
return mapValue;
|
||||||
|
}
|
||||||
|
missCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to create a value. This may take a long time, and the map
|
||||||
|
* may be different when create() returns. If a conflicting value was
|
||||||
|
* added to the map while create() was working, we leave that value in
|
||||||
|
* the map and release the created value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
V createdValue = create(key);
|
||||||
|
if (createdValue == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
createCount++;
|
||||||
|
mapValue = map.put(key, createdValue);
|
||||||
|
|
||||||
|
if (mapValue != null) {
|
||||||
|
// There was a conflict so undo that last put
|
||||||
|
map.put(key, mapValue);
|
||||||
|
} else {
|
||||||
|
size += safeSizeOf(key, createdValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapValue != null) {
|
||||||
|
entryRemoved(false, key, createdValue, mapValue);
|
||||||
|
return mapValue;
|
||||||
|
} else {
|
||||||
|
trimToSize(maxSize);
|
||||||
|
return createdValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches {@code value} for {@code key}. The value is moved to the head of
|
||||||
|
* the queue.
|
||||||
|
*
|
||||||
|
* @return the previous value mapped by {@code key}.
|
||||||
|
*/
|
||||||
|
public final V put(K key, V value) {
|
||||||
|
if (key == null || value == null) {
|
||||||
|
throw new NullPointerException("key == null || value == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V previous;
|
||||||
|
synchronized (this) {
|
||||||
|
putCount++;
|
||||||
|
size += safeSizeOf(key, value);
|
||||||
|
previous = map.put(key, value);
|
||||||
|
if (previous != null) {
|
||||||
|
size -= safeSizeOf(key, previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
entryRemoved(false, key, previous, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
trimToSize(maxSize);
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the eldest entries until the total of remaining entries is at or
|
||||||
|
* below the requested size.
|
||||||
|
*
|
||||||
|
* @param maxSize the maximum size of the cache before returning. May be -1
|
||||||
|
* to evict even 0-sized elements.
|
||||||
|
*/
|
||||||
|
public void trimToSize(int maxSize) {
|
||||||
|
while (true) {
|
||||||
|
K key;
|
||||||
|
V value;
|
||||||
|
synchronized (this) {
|
||||||
|
if (size < 0 || (map.isEmpty() && size != 0)) {
|
||||||
|
throw new IllegalStateException(getClass().getName()
|
||||||
|
+ ".sizeOf() is reporting inconsistent results!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= maxSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.Entry<K, V> toEvict = eldest();
|
||||||
|
if (toEvict == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = toEvict.getKey();
|
||||||
|
value = toEvict.getValue();
|
||||||
|
map.remove(key);
|
||||||
|
size -= safeSizeOf(key, value);
|
||||||
|
evictionCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
entryRemoved(true, key, value, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map.Entry<K, V> eldest() {
|
||||||
|
return map.firstEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the entry for {@code key} if it exists.
|
||||||
|
*
|
||||||
|
* @return the previous value mapped by {@code key}.
|
||||||
|
*/
|
||||||
|
public final V remove(K key) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
V previous;
|
||||||
|
synchronized (this) {
|
||||||
|
previous = map.remove(key);
|
||||||
|
if (previous != null) {
|
||||||
|
size -= safeSizeOf(key, previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
entryRemoved(false, key, previous, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for entries that have been evicted or removed. This method is
|
||||||
|
* invoked when a value is evicted to make space, removed by a call to
|
||||||
|
* {@link #remove}, or replaced by a call to {@link #put}. The default
|
||||||
|
* implementation does nothing.
|
||||||
|
*
|
||||||
|
* <p>The method is called without synchronization: other threads may
|
||||||
|
* access the cache while this method is executing.
|
||||||
|
*
|
||||||
|
* @param evicted true if the entry is being removed to make space, false
|
||||||
|
* if the removal was caused by a {@link #put} or {@link #remove}.
|
||||||
|
* @param newValue the new value for {@code key}, if it exists. If non-null,
|
||||||
|
* this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
|
||||||
|
* an eviction or a {@link #remove}.
|
||||||
|
*/
|
||||||
|
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after a cache miss to compute a value for the corresponding key.
|
||||||
|
* Returns the computed value or null if no value can be computed. The
|
||||||
|
* default implementation returns null.
|
||||||
|
*
|
||||||
|
* <p>The method is called without synchronization: other threads may
|
||||||
|
* access the cache while this method is executing.
|
||||||
|
*
|
||||||
|
* <p>If a value for {@code key} exists in the cache when this method
|
||||||
|
* returns, the created value will be released with {@link #entryRemoved}
|
||||||
|
* and discarded. This can occur when multiple threads request the same key
|
||||||
|
* at the same time (causing multiple values to be created), or when one
|
||||||
|
* thread calls {@link #put} while another is creating a value for the same
|
||||||
|
* key.
|
||||||
|
*/
|
||||||
|
protected V create(K key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int safeSizeOf(K key, V value) {
|
||||||
|
int result = sizeOf(key, value);
|
||||||
|
if (result < 0) {
|
||||||
|
throw new IllegalStateException("Negative size: " + key + "=" + value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the entry for {@code key} and {@code value} in
|
||||||
|
* user-defined units. The default implementation returns 1 so that size
|
||||||
|
* is the number of entries and max size is the maximum number of entries.
|
||||||
|
*
|
||||||
|
* <p>An entry's size must not change while it is in the cache.
|
||||||
|
*/
|
||||||
|
protected int sizeOf(K key, V value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
|
||||||
|
*/
|
||||||
|
public final void evictAll() {
|
||||||
|
trimToSize(-1); // -1 will evict 0-sized elements
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For caches that do not override {@link #sizeOf}, this returns the number
|
||||||
|
* of entries in the cache. For all other caches, this returns the sum of
|
||||||
|
* the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For caches that do not override {@link #sizeOf}, this returns the maximum
|
||||||
|
* number of entries in the cache. For all other caches, this returns the
|
||||||
|
* maximum sum of the sizes of the entries in this cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int maxSize() {
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #get} returned a value that was
|
||||||
|
* already present in the cache.
|
||||||
|
*/
|
||||||
|
public synchronized final int hitCount() {
|
||||||
|
return hitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #get} returned null or required a new
|
||||||
|
* value to be created.
|
||||||
|
*/
|
||||||
|
public synchronized final int missCount() {
|
||||||
|
return missCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #create(Object)} returned a value.
|
||||||
|
*/
|
||||||
|
public synchronized final int createCount() {
|
||||||
|
return createCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of times {@link #put} was called.
|
||||||
|
*/
|
||||||
|
public synchronized final int putCount() {
|
||||||
|
return putCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values that have been evicted.
|
||||||
|
*/
|
||||||
|
public synchronized final int evictionCount() {
|
||||||
|
return evictionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the current contents of the cache, ordered from least
|
||||||
|
* recently accessed to most recently accessed.
|
||||||
|
*/
|
||||||
|
public synchronized final Map<K, V> snapshot() {
|
||||||
|
return new LinkedHashMap<K, V>(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized final String toString() {
|
||||||
|
int accesses = hitCount + missCount;
|
||||||
|
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
|
||||||
|
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
|
||||||
|
maxSize, hitCount, missCount, hitPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.util;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
|
||||||
|
*
|
||||||
|
* <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
|
||||||
|
*
|
||||||
|
* @see Log
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public final class Slog {
|
||||||
|
|
||||||
|
private Slog() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#VERBOSE} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#v(String, String)
|
||||||
|
*/
|
||||||
|
public static int v(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(Log.VERBOSE, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#VERBOSE} level, attaching stack trace of the {@code tr} to
|
||||||
|
* the end of the log statement.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#v(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int v(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.VERBOSE, tag,
|
||||||
|
msg + '\n' + Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#DEBUG} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#d(String, String)
|
||||||
|
*/
|
||||||
|
public static int d(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(Log.DEBUG, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#DEBUG} level, attaching stack trace of the {@code tr} to
|
||||||
|
* the end of the log statement.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#d(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int d(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.DEBUG, tag,
|
||||||
|
msg + '\n' + Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#INFO} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#i(String, String)
|
||||||
|
*/
|
||||||
|
public static int i(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(Log.INFO, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#INFO} level, attaching stack trace of the {@code tr} to
|
||||||
|
* the end of the log statement.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#i(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int i(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.INFO, tag,
|
||||||
|
msg + '\n' + Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#WARN} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#w(String, String)
|
||||||
|
*/
|
||||||
|
public static int w(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(Log.WARN, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#WARN} level, attaching stack trace of the {@code tr} to
|
||||||
|
* the end of the log statement.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#w(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int w(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.WARN, tag,
|
||||||
|
msg + '\n' + Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs stack trace of {@code tr} at {@link Log#WARN} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#w(String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int w(@Nullable String tag, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.WARN, tag, Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#ERROR} level.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#e(String, String)
|
||||||
|
*/
|
||||||
|
public static int e(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(Log.ERROR, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs {@code msg} at {@link Log#ERROR} level, attaching stack trace of the {@code tr} to
|
||||||
|
* the end of the log statement.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#e(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int e(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.println(Log.ERROR, tag,
|
||||||
|
msg + '\n' + Log.getStackTraceString(tr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a condition that should never happen.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Similar to {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
|
||||||
|
* will always be handled asynchronously. Primarily to be used by the system server.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#wtf(String, String)
|
||||||
|
*/
|
||||||
|
public static int wtf(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.wtf(tag, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a condition that should never happen, attaching the full call stack to the log.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Similar to {@link Log#wtfStack(String, String)}, but will never cause the caller to crash,
|
||||||
|
* and will always be handled asynchronously. Primarily to be used by the system server.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
*
|
||||||
|
* @see Log#wtfStack(String, String)
|
||||||
|
*/
|
||||||
|
public static int wtfStack(@Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.wtf(tag, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
|
||||||
|
* end of the log statement.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Similar to {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
|
||||||
|
* and will always be handled asynchronously. Primarily to be used by the system server.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#wtf(String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
|
||||||
|
return Log.wtf(tag, tr.getMessage(), tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
|
||||||
|
* end of the log statement.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Similar to {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to
|
||||||
|
* crash, and will always be handled asynchronously. Primarily to be used by the system server.
|
||||||
|
*
|
||||||
|
* @param tag identifies the source of a log message. It usually represents system service,
|
||||||
|
* e.g. {@code PackageManager}.
|
||||||
|
* @param msg the message to log.
|
||||||
|
* @param tr an exception to log.
|
||||||
|
*
|
||||||
|
* @see Log#wtf(String, String, Throwable)
|
||||||
|
*/
|
||||||
|
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||||
|
return Log.wtf(tag, msg, tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static int println(int priority, @Nullable String tag, @NonNull String msg) {
|
||||||
|
return Log.println(priority, tag, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.util;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.TestApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class containing utility methods related to time zones.
|
||||||
|
*/
|
||||||
|
public class TimeUtils {
|
||||||
|
/** @hide */ public TimeUtils() {}
|
||||||
|
/** {@hide} */
|
||||||
|
private static final SimpleDateFormat sLoggingFormat =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final SimpleDateFormat sDumpDateFormat =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This timestamp is used in TimeUtils methods and by the SettingsUI to filter time zones
|
||||||
|
* to only "effective" ones in a country. It is compared against the notUsedAfter metadata that
|
||||||
|
* Android records for some time zones.
|
||||||
|
*
|
||||||
|
* <p>What is notUsedAfter?</p>
|
||||||
|
* Android chooses to avoid making users choose between functionally identical time zones at the
|
||||||
|
* expense of not being able to represent local times in the past.
|
||||||
|
*
|
||||||
|
* notUsedAfter exists because some time zones can "merge" with other time zones after a given
|
||||||
|
* point in time (i.e. they change to have identical transitions, offsets, display names, etc.).
|
||||||
|
* From the notUsedAfter time, the zone will express the same local time as the one it merged
|
||||||
|
* with.
|
||||||
|
*
|
||||||
|
* <p>Why hardcoded?</p>
|
||||||
|
* Rather than using System.currentTimeMillis(), a timestamp known to be in the recent past is
|
||||||
|
* used to ensure consistent behavior across devices and time, and avoid assumptions that the
|
||||||
|
* system clock on a device is currently set correctly. The fixed value should be updated
|
||||||
|
* occasionally, but it doesn't have to be very often as effective time zones for a country
|
||||||
|
* don't change very often.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final Instant MIN_USE_DATE_OF_TIMEZONE =
|
||||||
|
Instant.ofEpochMilli(1546300800000L); // 1/1/2019 00:00 UTC
|
||||||
|
|
||||||
|
/** @hide Field length that can hold 999 days of time */
|
||||||
|
public static final int HUNDRED_DAY_FIELD_LEN = 19;
|
||||||
|
|
||||||
|
private static final int SECONDS_PER_MINUTE = 60;
|
||||||
|
private static final int SECONDS_PER_HOUR = 60 * 60;
|
||||||
|
private static final int SECONDS_PER_DAY = 24 * 60 * 60;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final long NANOS_PER_MS = 1000000;
|
||||||
|
|
||||||
|
private static final Object sFormatSync = new Object();
|
||||||
|
private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
|
||||||
|
private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
|
||||||
|
|
||||||
|
static private int accumField(int amt, int suffix, boolean always, int zeropad) {
|
||||||
|
if (amt > 999) {
|
||||||
|
int num = 0;
|
||||||
|
while (amt != 0) {
|
||||||
|
num++;
|
||||||
|
amt /= 10;
|
||||||
|
}
|
||||||
|
return num + suffix;
|
||||||
|
} else {
|
||||||
|
if (amt > 99 || (always && zeropad >= 3)) {
|
||||||
|
return 3+suffix;
|
||||||
|
}
|
||||||
|
if (amt > 9 || (always && zeropad >= 2)) {
|
||||||
|
return 2+suffix;
|
||||||
|
}
|
||||||
|
if (always || amt > 0) {
|
||||||
|
return 1+suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
|
||||||
|
boolean always, int zeropad) {
|
||||||
|
if (always || amt > 0) {
|
||||||
|
final int startPos = pos;
|
||||||
|
if (amt > 999) {
|
||||||
|
int tmp = 0;
|
||||||
|
while (amt != 0 && tmp < sTmpFormatStr.length) {
|
||||||
|
int dig = amt % 10;
|
||||||
|
sTmpFormatStr[tmp] = (char)(dig + '0');
|
||||||
|
tmp++;
|
||||||
|
amt /= 10;
|
||||||
|
}
|
||||||
|
tmp--;
|
||||||
|
while (tmp >= 0) {
|
||||||
|
formatStr[pos] = sTmpFormatStr[tmp];
|
||||||
|
pos++;
|
||||||
|
tmp--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((always && zeropad >= 3) || amt > 99) {
|
||||||
|
int dig = amt/100;
|
||||||
|
formatStr[pos] = (char)(dig + '0');
|
||||||
|
pos++;
|
||||||
|
amt -= (dig*100);
|
||||||
|
}
|
||||||
|
if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
|
||||||
|
int dig = amt/10;
|
||||||
|
formatStr[pos] = (char)(dig + '0');
|
||||||
|
pos++;
|
||||||
|
amt -= (dig*10);
|
||||||
|
}
|
||||||
|
formatStr[pos] = (char)(amt + '0');
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
formatStr[pos] = suffix;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int formatDurationLocked(long duration, int fieldLen) {
|
||||||
|
if (sFormatStr.length < fieldLen) {
|
||||||
|
sFormatStr = new char[fieldLen];
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] formatStr = sFormatStr;
|
||||||
|
|
||||||
|
if (duration == 0) {
|
||||||
|
int pos = 0;
|
||||||
|
fieldLen -= 1;
|
||||||
|
while (pos < fieldLen) {
|
||||||
|
formatStr[pos++] = ' ';
|
||||||
|
}
|
||||||
|
formatStr[pos] = '0';
|
||||||
|
return pos+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char prefix;
|
||||||
|
if (duration > 0) {
|
||||||
|
prefix = '+';
|
||||||
|
} else {
|
||||||
|
prefix = '-';
|
||||||
|
duration = -duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
int millis = (int)(duration%1000);
|
||||||
|
int seconds = (int) Math.floor(duration / 1000);
|
||||||
|
int days = 0, hours = 0, minutes = 0;
|
||||||
|
|
||||||
|
if (seconds >= SECONDS_PER_DAY) {
|
||||||
|
days = seconds / SECONDS_PER_DAY;
|
||||||
|
seconds -= days * SECONDS_PER_DAY;
|
||||||
|
}
|
||||||
|
if (seconds >= SECONDS_PER_HOUR) {
|
||||||
|
hours = seconds / SECONDS_PER_HOUR;
|
||||||
|
seconds -= hours * SECONDS_PER_HOUR;
|
||||||
|
}
|
||||||
|
if (seconds >= SECONDS_PER_MINUTE) {
|
||||||
|
minutes = seconds / SECONDS_PER_MINUTE;
|
||||||
|
seconds -= minutes * SECONDS_PER_MINUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
if (fieldLen != 0) {
|
||||||
|
int myLen = accumField(days, 1, false, 0);
|
||||||
|
myLen += accumField(hours, 1, myLen > 0, 2);
|
||||||
|
myLen += accumField(minutes, 1, myLen > 0, 2);
|
||||||
|
myLen += accumField(seconds, 1, myLen > 0, 2);
|
||||||
|
myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
|
||||||
|
while (myLen < fieldLen) {
|
||||||
|
formatStr[pos] = ' ';
|
||||||
|
pos++;
|
||||||
|
myLen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatStr[pos] = prefix;
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
int start = pos;
|
||||||
|
boolean zeropad = fieldLen != 0;
|
||||||
|
pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
|
||||||
|
pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
|
||||||
|
pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
|
||||||
|
pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
|
||||||
|
pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
|
||||||
|
formatStr[pos] = 's';
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long duration, StringBuilder builder) {
|
||||||
|
synchronized (sFormatSync) {
|
||||||
|
int len = formatDurationLocked(duration, 0);
|
||||||
|
builder.append(sFormatStr, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
|
||||||
|
synchronized (sFormatSync) {
|
||||||
|
int len = formatDurationLocked(duration, fieldLen);
|
||||||
|
builder.append(sFormatStr, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
|
||||||
|
synchronized (sFormatSync) {
|
||||||
|
int len = formatDurationLocked(duration, fieldLen);
|
||||||
|
pw.print(new String(sFormatStr, 0, len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
@TestApi
|
||||||
|
public static String formatDuration(long duration) {
|
||||||
|
synchronized (sFormatSync) {
|
||||||
|
int len = formatDurationLocked(duration, 0);
|
||||||
|
return new String(sFormatStr, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long duration, PrintWriter pw) {
|
||||||
|
formatDuration(duration, pw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long time, long now, StringBuilder sb) {
|
||||||
|
if (time == 0) {
|
||||||
|
sb.append("--");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formatDuration(time-now, sb, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static void formatDuration(long time, long now, PrintWriter pw) {
|
||||||
|
if (time == 0) {
|
||||||
|
pw.print("--");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formatDuration(time-now, pw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static String formatUptime(long time) {
|
||||||
|
return formatTime(time, SystemClock.uptimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static String formatRealtime(long time) {
|
||||||
|
return formatTime(time, SystemClock.elapsedRealtime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide Just for debugging; not internationalized. */
|
||||||
|
public static String formatTime(long time, long referenceTime) {
|
||||||
|
long diff = time - referenceTime;
|
||||||
|
if (diff > 0) {
|
||||||
|
return time + " (in " + diff + " ms)";
|
||||||
|
}
|
||||||
|
if (diff < 0) {
|
||||||
|
return time + " (" + -diff + " ms ago)";
|
||||||
|
}
|
||||||
|
return time + " (now)";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a System.currentTimeMillis() value to a time of day value like
|
||||||
|
* that printed in logs. MM-DD HH:MM:SS.MMM
|
||||||
|
*
|
||||||
|
* @param millis since the epoch (1/1/1970)
|
||||||
|
* @return String representation of the time.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static String logTimeOfDay(long millis) {
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
if (millis >= 0) {
|
||||||
|
c.setTimeInMillis(millis);
|
||||||
|
return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
|
||||||
|
} else {
|
||||||
|
return Long.toString(millis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
public static String formatForLogging(long millis) {
|
||||||
|
if (millis <= 0) {
|
||||||
|
return "unknown";
|
||||||
|
} else {
|
||||||
|
return sLoggingFormat.format(new Date(millis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump a currentTimeMillis style timestamp for dumpsys.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void dumpTime(PrintWriter pw, long time) {
|
||||||
|
pw.print(sDumpDateFormat.format(new Date(time)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to find if a clock time is inclusively between two other clock times
|
||||||
|
* @param reference The time of the day we want check if it is between start and end
|
||||||
|
* @param start The start time reference
|
||||||
|
* @param end The end time
|
||||||
|
* @return true if the reference time is between the two clock times, and false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isTimeBetween(@NonNull LocalTime reference,
|
||||||
|
@NonNull LocalTime start,
|
||||||
|
@NonNull LocalTime end) {
|
||||||
|
// ////////E----+-----S////////
|
||||||
|
if ((reference.isBefore(start) && reference.isAfter(end)
|
||||||
|
// -----+----S//////////E------
|
||||||
|
|| (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end))
|
||||||
|
// ---------S//////////E---+---
|
||||||
|
|| (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
|
||||||
|
pw.print(sDumpDateFormat.format(new Date(time)));
|
||||||
|
if (time == now) {
|
||||||
|
pw.print(" (now)");
|
||||||
|
} else {
|
||||||
|
pw.print(" (");
|
||||||
|
TimeUtils.formatDuration(time, now, pw);
|
||||||
|
pw.print(")");
|
||||||
|
}
|
||||||
|
}}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package android.os;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplier for custom trace messages.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public interface TraceNameSupplier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name used for trace messages.
|
||||||
|
*/
|
||||||
|
@NonNull String getTraceName();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
|||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import xyz.nulldev.androidcompat.webkit.CookieManagerImpl;
|
||||||
|
|
||||||
|
public abstract class CookieManager {
|
||||||
|
/**
|
||||||
|
* @deprecated This class should not be constructed by applications, use {@link #getInstance}
|
||||||
|
* instead to fetch the singleton instance.
|
||||||
|
*/
|
||||||
|
// TODO(ntfschr): mark this as @SystemApi after a year.
|
||||||
|
@Deprecated
|
||||||
|
public CookieManager() {}
|
||||||
|
@Override
|
||||||
|
protected Object clone() throws CloneNotSupportedException {
|
||||||
|
throw new CloneNotSupportedException("doesn't implement Cloneable");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CookieManager INSTANCE = null;
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
/**
|
||||||
|
* Gets the singleton CookieManager instance.
|
||||||
|
*
|
||||||
|
* @return the singleton CookieManager instance
|
||||||
|
*/
|
||||||
|
public static CookieManager getInstance() {
|
||||||
|
if (INSTANCE != null) {
|
||||||
|
return INSTANCE;
|
||||||
|
} else {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new CookieManagerImpl();
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets whether the application's {@link WebView} instances should send and
|
||||||
|
* accept cookies.
|
||||||
|
* By default this is set to {@code true} and the WebView accepts cookies.
|
||||||
|
* <p>
|
||||||
|
* When this is {@code true}
|
||||||
|
* {@link CookieManager#setAcceptThirdPartyCookies setAcceptThirdPartyCookies} and
|
||||||
|
* {@link CookieManager#setAcceptFileSchemeCookies setAcceptFileSchemeCookies}
|
||||||
|
* can be used to control the policy for those specific types of cookie.
|
||||||
|
*
|
||||||
|
* @param accept whether {@link WebView} instances should send and accept
|
||||||
|
* cookies
|
||||||
|
*/
|
||||||
|
public abstract void setAcceptCookie(boolean accept);
|
||||||
|
/**
|
||||||
|
* Gets whether the application's {@link WebView} instances send and accept
|
||||||
|
* cookies.
|
||||||
|
*
|
||||||
|
* @return {@code true} if {@link WebView} instances send and accept cookies
|
||||||
|
*/
|
||||||
|
public abstract boolean acceptCookie();
|
||||||
|
/**
|
||||||
|
* Sets whether the {@link WebView} should allow third party cookies to be set.
|
||||||
|
* Allowing third party cookies is a per WebView policy and can be set
|
||||||
|
* differently on different WebView instances.
|
||||||
|
* <p>
|
||||||
|
* Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
|
||||||
|
* default to allowing third party cookies. Apps targeting
|
||||||
|
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later default to disallowing
|
||||||
|
* third party cookies.
|
||||||
|
*
|
||||||
|
* @param webview the {@link WebView} instance to set the cookie policy on
|
||||||
|
* @param accept whether the {@link WebView} instance should accept
|
||||||
|
* third party cookies
|
||||||
|
*/
|
||||||
|
public abstract void setAcceptThirdPartyCookies(WebView webview, boolean accept);
|
||||||
|
/**
|
||||||
|
* Gets whether the {@link WebView} should allow third party cookies to be set.
|
||||||
|
*
|
||||||
|
* @param webview the {@link WebView} instance to get the cookie policy for
|
||||||
|
* @return {@code true} if the {@link WebView} accepts third party cookies
|
||||||
|
*/
|
||||||
|
public abstract boolean acceptThirdPartyCookies(WebView webview);
|
||||||
|
/**
|
||||||
|
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
|
||||||
|
* host, path and name will be replaced with the new cookie. The cookie being set
|
||||||
|
* will be ignored if it is expired. To set multiple cookies, your application should invoke
|
||||||
|
* this method multiple times.
|
||||||
|
*
|
||||||
|
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
|
||||||
|
* response header defined by
|
||||||
|
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
|
||||||
|
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
|
||||||
|
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
|
||||||
|
* consult the RFC specification for a list of valid attributes.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
|
||||||
|
* attribute, {@code url} must use the {@code "https://"} scheme.
|
||||||
|
*
|
||||||
|
* @param url the URL for which the cookie is to be set
|
||||||
|
* @param value the cookie as a string, using the format of the 'Set-Cookie'
|
||||||
|
* HTTP response header
|
||||||
|
*/
|
||||||
|
public abstract void setCookie(String url, String value);
|
||||||
|
/**
|
||||||
|
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
|
||||||
|
* host, path and name will be replaced with the new cookie. The cookie being set
|
||||||
|
* will be ignored if it is expired. To set multiple cookies, your application should invoke
|
||||||
|
* this method multiple times.
|
||||||
|
*
|
||||||
|
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
|
||||||
|
* response header defined by
|
||||||
|
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
|
||||||
|
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
|
||||||
|
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
|
||||||
|
* consult the RFC specification for a list of valid attributes.
|
||||||
|
*
|
||||||
|
* <p>This method is asynchronous. If a {@link ValueCallback} is provided,
|
||||||
|
* {@link ValueCallback#onReceiveValue} will be called on the current
|
||||||
|
* thread's {@link android.os.Looper} once the operation is complete.
|
||||||
|
* The value provided to the callback indicates whether the cookie was set successfully.
|
||||||
|
* You can pass {@code null} as the callback if you don't need to know when the operation
|
||||||
|
* completes or whether it succeeded, and in this case it is safe to call the method from a
|
||||||
|
* thread without a Looper.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
|
||||||
|
* attribute, {@code url} must use the {@code "https://"} scheme.
|
||||||
|
*
|
||||||
|
* @param url the URL for which the cookie is to be set
|
||||||
|
* @param value the cookie as a string, using the format of the 'Set-Cookie'
|
||||||
|
* HTTP response header
|
||||||
|
* @param callback a callback to be executed when the cookie has been set
|
||||||
|
*/
|
||||||
|
public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
|
||||||
|
callback);
|
||||||
|
/**
|
||||||
|
* Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
|
||||||
|
* cookies are associated with this URL, in which case each cookie will be delimited by {@code
|
||||||
|
* "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
|
||||||
|
* {@code "key=value"}.
|
||||||
|
*
|
||||||
|
* @param url the URL for which the cookies are requested
|
||||||
|
* @return value the cookies as a string, using the format of the 'Cookie'
|
||||||
|
* HTTP request header
|
||||||
|
*/
|
||||||
|
public abstract String getCookie(String url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all session cookies, which are cookies without an expiration
|
||||||
|
* date.
|
||||||
|
* @deprecated use {@link #removeSessionCookies(ValueCallback)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public abstract void removeSessionCookie();
|
||||||
|
/**
|
||||||
|
* Removes all session cookies, which are cookies without an expiration
|
||||||
|
* date.
|
||||||
|
* <p>
|
||||||
|
* This method is asynchronous.
|
||||||
|
* If a {@link ValueCallback} is provided,
|
||||||
|
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
|
||||||
|
* thread's {@link android.os.Looper} once the operation is complete.
|
||||||
|
* The value provided to the callback indicates whether any cookies were removed.
|
||||||
|
* You can pass {@code null} as the callback if you don't need to know when the operation
|
||||||
|
* completes or whether any cookie were removed, and in this case it is safe to call the
|
||||||
|
* method from a thread without a Looper.
|
||||||
|
* @param callback a callback which is executed when the session cookies have been removed
|
||||||
|
*/
|
||||||
|
public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
|
||||||
|
/**
|
||||||
|
* Removes all cookies.
|
||||||
|
* @deprecated Use {@link #removeAllCookies(ValueCallback)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public abstract void removeAllCookie();
|
||||||
|
/**
|
||||||
|
* Removes all cookies.
|
||||||
|
* <p>
|
||||||
|
* This method is asynchronous.
|
||||||
|
* If a {@link ValueCallback} is provided,
|
||||||
|
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
|
||||||
|
* thread's {@link android.os.Looper} once the operation is complete.
|
||||||
|
* The value provided to the callback indicates whether any cookies were removed.
|
||||||
|
* You can pass {@code null} as the callback if you don't need to know when the operation
|
||||||
|
* completes or whether any cookies were removed, and in this case it is safe to call the
|
||||||
|
* method from a thread without a Looper.
|
||||||
|
* @param callback a callback which is executed when the cookies have been removed
|
||||||
|
*/
|
||||||
|
public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
|
||||||
|
/**
|
||||||
|
* Gets whether there are stored cookies.
|
||||||
|
*
|
||||||
|
* @return {@code true} if there are stored cookies
|
||||||
|
*/
|
||||||
|
public abstract boolean hasCookies();
|
||||||
|
/**
|
||||||
|
* Removes all expired cookies.
|
||||||
|
* @deprecated The WebView handles removing expired cookies automatically.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public abstract void removeExpiredCookie();
|
||||||
|
/**
|
||||||
|
* Ensures all cookies currently accessible through the getCookie API are
|
||||||
|
* written to persistent storage.
|
||||||
|
* This call will block the caller until it is done and may perform I/O.
|
||||||
|
*/
|
||||||
|
public abstract void flush();
|
||||||
|
/**
|
||||||
|
* Gets whether the application's {@link WebView} instances send and accept
|
||||||
|
* cookies for file scheme URLs.
|
||||||
|
*
|
||||||
|
* @return {@code true} if {@link WebView} instances send and accept cookies for
|
||||||
|
* file scheme URLs
|
||||||
|
*/
|
||||||
|
// Static for backward compatibility.
|
||||||
|
public static boolean allowFileSchemeCookies() {
|
||||||
|
return getInstance().allowFileSchemeCookiesImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean allowFileSchemeCookiesImpl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the application's {@link WebView} instances should send and accept cookies for
|
||||||
|
* file scheme URLs.
|
||||||
|
* <p>
|
||||||
|
* Use of cookies with file scheme URLs is potentially insecure and turned off by default. All
|
||||||
|
* {@code file://} URLs share all their cookies, which may lead to leaking private app cookies
|
||||||
|
* (ex. any malicious file can access cookies previously set by other (trusted) files).
|
||||||
|
* <p class="note">
|
||||||
|
* Loading content via {@code file://} URLs is generally discouraged. See the note in
|
||||||
|
* {@link WebSettings#setAllowFileAccess}.
|
||||||
|
* Using <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
|
||||||
|
* androidx.webkit.WebViewAssetLoader</a> to load files over {@code http(s)://} URLs allows
|
||||||
|
* the standard web security model to be used for setting and sharing cookies for local files.
|
||||||
|
* <p>
|
||||||
|
* Note that calls to this method will have no effect if made after calling other
|
||||||
|
* {@link CookieManager} APIs.
|
||||||
|
*
|
||||||
|
* @deprecated This setting is not secure, please use
|
||||||
|
* <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
|
||||||
|
* androidx.webkit.WebViewAssetLoader</a> instead.
|
||||||
|
*/
|
||||||
|
// Static for backward compatibility.
|
||||||
|
@Deprecated
|
||||||
|
public static void setAcceptFileSchemeCookies(boolean accept) {
|
||||||
|
getInstance().setAcceptFileSchemeCookiesImpl(accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setAcceptFileSchemeCookiesImpl(boolean accept);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class defines a permission request and is used when web content
|
||||||
|
* requests access to protected resources. The permission request related events
|
||||||
|
* are delivered via {@link WebChromeClient#onPermissionRequest} and
|
||||||
|
* {@link WebChromeClient#onPermissionRequestCanceled}.
|
||||||
|
*
|
||||||
|
* Either {@link #grant(String[]) grant()} or {@link #deny()} must be called in UI
|
||||||
|
* thread to respond to the request.
|
||||||
|
*
|
||||||
|
* New protected resources whose names are not defined here may be requested in
|
||||||
|
* future versions of WebView, even when running on an older Android release. To
|
||||||
|
* avoid unintentionally granting requests for new permissions, you should pass the
|
||||||
|
* specific permissions you intend to grant to {@link #grant(String[]) grant()},
|
||||||
|
* and avoid writing code like this example:
|
||||||
|
* <pre class="prettyprint">
|
||||||
|
* permissionRequest.grant(permissionRequest.getResources()) // This is wrong!!!
|
||||||
|
* </pre>
|
||||||
|
* See the WebView's release notes for information about new protected resources.
|
||||||
|
*/
|
||||||
|
public abstract class PermissionRequest {
|
||||||
|
/**
|
||||||
|
* Resource belongs to video capture device, like camera.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_VIDEO_CAPTURE = "android.webkit.resource.VIDEO_CAPTURE";
|
||||||
|
/**
|
||||||
|
* Resource belongs to audio capture device, like microphone.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_AUDIO_CAPTURE = "android.webkit.resource.AUDIO_CAPTURE";
|
||||||
|
/**
|
||||||
|
* Resource belongs to protected media identifier.
|
||||||
|
* After the user grants this resource, the origin can use EME APIs to generate the license
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_PROTECTED_MEDIA_ID =
|
||||||
|
"android.webkit.resource.PROTECTED_MEDIA_ID";
|
||||||
|
/**
|
||||||
|
* Resource will allow sysex messages to be sent to or received from MIDI devices. These
|
||||||
|
* messages are privileged operations, e.g. modifying sound libraries and sampling data, or
|
||||||
|
* even updating the MIDI device's firmware.
|
||||||
|
*
|
||||||
|
* Permission may be requested for this resource in API levels 21 and above, if the Android
|
||||||
|
* device has been updated to WebView 45 or above.
|
||||||
|
*/
|
||||||
|
public final static String RESOURCE_MIDI_SYSEX = "android.webkit.resource.MIDI_SYSEX";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to get the origin of the web page which is trying to access
|
||||||
|
* the restricted resources.
|
||||||
|
*
|
||||||
|
* @return the origin of web content which attempt to access the restricted
|
||||||
|
* resources.
|
||||||
|
*/
|
||||||
|
public abstract Uri getOrigin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to get the resources the web page is trying to access.
|
||||||
|
*
|
||||||
|
* @return the array of resources the web content wants to access.
|
||||||
|
*/
|
||||||
|
public abstract String[] getResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to grant origin the permission to access the given resources.
|
||||||
|
* The granted permission is only valid for this WebView.
|
||||||
|
*
|
||||||
|
* @param resources the resources granted to be accessed by origin, to grant
|
||||||
|
* request, the requested resources returned by {@link #getResources()}
|
||||||
|
* must be equals or a subset of granted resources.
|
||||||
|
* This parameter is designed to avoid granting permission by accident
|
||||||
|
* especially when new resources are requested by web content.
|
||||||
|
*/
|
||||||
|
public abstract void grant(String[] resources);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method to deny the request.
|
||||||
|
*/
|
||||||
|
public abstract void deny();
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2008 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
public class WebChromeClient {
|
||||||
|
|
||||||
|
public void onProgressChanged(WebView view, int newProgress) {}
|
||||||
|
|
||||||
|
public void onReceivedTitle(WebView view, String title) {}
|
||||||
|
|
||||||
|
public void onReceivedIcon(WebView view, Bitmap icon) {}
|
||||||
|
|
||||||
|
public void onReceivedTouchIconUrl(WebView view, String url,
|
||||||
|
boolean precomposed) {}
|
||||||
|
|
||||||
|
public interface CustomViewCallback {
|
||||||
|
public void onCustomViewHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onShowCustomView(View view, CustomViewCallback callback) {};
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void onShowCustomView(View view, int requestedOrientation,
|
||||||
|
CustomViewCallback callback) {};
|
||||||
|
|
||||||
|
public void onHideCustomView() {}
|
||||||
|
|
||||||
|
public boolean onCreateWindow(WebView view, boolean isDialog,
|
||||||
|
boolean isUserGesture, Message resultMsg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRequestFocus(WebView view) {}
|
||||||
|
|
||||||
|
public void onCloseWindow(WebView window) {}
|
||||||
|
|
||||||
|
public boolean onJsAlert(WebView view, String url, String message,
|
||||||
|
JsResult result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onJsConfirm(WebView view, String url, String message,
|
||||||
|
JsResult result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onJsPrompt(WebView view, String url, String message,
|
||||||
|
String defaultValue, JsPromptResult result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onJsBeforeUnload(WebView view, String url, String message,
|
||||||
|
JsResult result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
|
||||||
|
long quota, long estimatedDatabaseSize, long totalQuota,
|
||||||
|
WebStorage.QuotaUpdater quotaUpdater) {
|
||||||
|
// This default implementation passes the current quota back to WebCore.
|
||||||
|
// WebCore will interpret this that new quota was declined.
|
||||||
|
quotaUpdater.updateQuota(quota);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
|
||||||
|
WebStorage.QuotaUpdater quotaUpdater) {
|
||||||
|
quotaUpdater.updateQuota(quota);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onGeolocationPermissionsShowPrompt(String origin,
|
||||||
|
GeolocationPermissions.Callback callback) {}
|
||||||
|
|
||||||
|
public void onGeolocationPermissionsHidePrompt() {}
|
||||||
|
|
||||||
|
public void onPermissionRequest(PermissionRequest request) {
|
||||||
|
request.deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPermissionRequestCanceled(PermissionRequest request) {}
|
||||||
|
|
||||||
|
// This method was only called when using the JSC javascript engine. V8 became
|
||||||
|
// the default JS engine with Froyo and support for building with JSC was
|
||||||
|
// removed in b/5495373. V8 does not have a mechanism for making a callback such
|
||||||
|
// as this.
|
||||||
|
@Deprecated
|
||||||
|
public boolean onJsTimeout() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void onConsoleMessage(String message, int lineNumber, String sourceID) { }
|
||||||
|
|
||||||
|
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
||||||
|
// Call the old version of this function for backwards compatability.
|
||||||
|
onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
|
||||||
|
consoleMessage.sourceId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Bitmap getDefaultVideoPoster() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public View getVideoLoadingProgressView() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Obtains a list of all visited history items, used for link coloring
|
||||||
|
*/
|
||||||
|
public void getVisitedHistory(ValueCallback<String[]> callback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||||
|
FileChooserParams fileChooserParams) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class FileChooserParams {
|
||||||
|
@SystemApi
|
||||||
|
public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Mode {}
|
||||||
|
|
||||||
|
/** Open single file. Requires that the file exists before allowing the user to pick it. */
|
||||||
|
public static final int MODE_OPEN = 0;
|
||||||
|
/** Like Open but allows multiple files to be selected. */
|
||||||
|
public static final int MODE_OPEN_MULTIPLE = 1;
|
||||||
|
/** Like Open but allows a folder to be selected. */
|
||||||
|
public static final int MODE_OPEN_FOLDER = 2;
|
||||||
|
/** Allows picking a nonexistent file and saving it. */
|
||||||
|
public static final int MODE_SAVE = 3;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface PermissionMode {}
|
||||||
|
|
||||||
|
/** File or directory should be opened for reading only. */
|
||||||
|
public static final int PERMISSION_MODE_READ = 0;
|
||||||
|
/** File or directory should be opened for read and write. */
|
||||||
|
public static final int PERMISSION_MODE_READ_WRITE = 1;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Uri[] parseResult(int resultCode, Intent data) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mode
|
||||||
|
public abstract int getMode();
|
||||||
|
|
||||||
|
public abstract String[] getAcceptTypes();
|
||||||
|
|
||||||
|
public abstract boolean isCaptureEnabled();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract CharSequence getTitle();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public abstract String getFilenameHint();
|
||||||
|
|
||||||
|
@PermissionMode
|
||||||
|
public int getPermissionMode() {
|
||||||
|
return PERMISSION_MODE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Intent createIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SystemApi
|
||||||
|
@Deprecated
|
||||||
|
public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
|
||||||
|
uploadFile.onReceiveValue(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.os.Build;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringBufferInputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a resource response. Applications can return an instance of this
|
||||||
|
* class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
|
||||||
|
* response when the WebView requests a particular resource.
|
||||||
|
*/
|
||||||
|
public class WebResourceResponse {
|
||||||
|
private boolean mImmutable;
|
||||||
|
private String mMimeType;
|
||||||
|
private String mEncoding;
|
||||||
|
private int mStatusCode;
|
||||||
|
private String mReasonPhrase;
|
||||||
|
private Map<String, String> mResponseHeaders;
|
||||||
|
private InputStream mInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a resource response with the given MIME type, character encoding,
|
||||||
|
* and input stream. Callers must implement {@link InputStream#read(byte[])} for
|
||||||
|
* the input stream. {@link InputStream#close()} will be called after the WebView
|
||||||
|
* has finished with the response.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> The MIME type and character encoding must
|
||||||
|
* be specified as separate parameters (for example {@code "text/html"} and
|
||||||
|
* {@code "utf-8"}), not a single value like the {@code "text/html; charset=utf-8"}
|
||||||
|
* format used in the HTTP Content-Type header. Do not use the value of a HTTP
|
||||||
|
* Content-Encoding header for {@code encoding}, as that header does not specify a
|
||||||
|
* character encoding. Content without a defined character encoding (for example
|
||||||
|
* image resources) should pass {@code null} for {@code encoding}.
|
||||||
|
*
|
||||||
|
* @param mimeType the resource response's MIME type, for example {@code "text/html"}.
|
||||||
|
* @param encoding the resource response's character encoding, for example {@code "utf-8"}.
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public WebResourceResponse(String mimeType, String encoding,
|
||||||
|
InputStream data) {
|
||||||
|
mMimeType = mimeType;
|
||||||
|
mEncoding = encoding;
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a resource response with the given parameters. Callers must implement
|
||||||
|
* {@link InputStream#read(byte[])} for the input stream. {@link InputStream#close()} will be
|
||||||
|
* called after the WebView has finished with the response.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> See {@link #WebResourceResponse(String,String,InputStream)}
|
||||||
|
* for details on what should be specified for {@code mimeType} and {@code encoding}.
|
||||||
|
*
|
||||||
|
* @param mimeType the resource response's MIME type, for example {@code "text/html"}.
|
||||||
|
* @param encoding the resource response's character encoding, for example {@code "utf-8"}.
|
||||||
|
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
|
||||||
|
* Causing a redirect by specifying a 3xx code is not supported.
|
||||||
|
* @param reasonPhrase the phrase describing the status code, for example "OK". Must be
|
||||||
|
* non-empty.
|
||||||
|
* @param responseHeaders the resource response's headers represented as a mapping of header
|
||||||
|
* name -> header value.
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public WebResourceResponse(String mimeType, String encoding, int statusCode,
|
||||||
|
@NonNull String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||||
|
this(mimeType, encoding, data);
|
||||||
|
setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
|
||||||
|
setResponseHeaders(responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's MIME type, for example "text/html".
|
||||||
|
*
|
||||||
|
* @param mimeType The resource response's MIME type
|
||||||
|
*/
|
||||||
|
public void setMimeType(String mimeType) {
|
||||||
|
checkImmutable();
|
||||||
|
mMimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's MIME type.
|
||||||
|
*
|
||||||
|
* @return The resource response's MIME type
|
||||||
|
*/
|
||||||
|
public String getMimeType() {
|
||||||
|
return mMimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's encoding, for example "UTF-8". This is used
|
||||||
|
* to decode the data from the input stream.
|
||||||
|
*
|
||||||
|
* @param encoding The resource response's encoding
|
||||||
|
*/
|
||||||
|
public void setEncoding(String encoding) {
|
||||||
|
checkImmutable();
|
||||||
|
mEncoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's encoding.
|
||||||
|
*
|
||||||
|
* @return The resource response's encoding
|
||||||
|
*/
|
||||||
|
public String getEncoding() {
|
||||||
|
return mEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource response's status code and reason phrase.
|
||||||
|
*
|
||||||
|
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
|
||||||
|
* Causing a redirect by specifying a 3xx code is not supported.
|
||||||
|
* @param reasonPhrase the phrase describing the status code, for example "OK". Must be
|
||||||
|
* non-empty.
|
||||||
|
*/
|
||||||
|
public void setStatusCodeAndReasonPhrase(int statusCode, @NonNull String reasonPhrase) {
|
||||||
|
checkImmutable();
|
||||||
|
if (statusCode < 100)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be less than 100.");
|
||||||
|
if (statusCode > 599)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be greater than 599.");
|
||||||
|
if (statusCode > 299 && statusCode < 400)
|
||||||
|
throw new IllegalArgumentException("statusCode can't be in the [300, 399] range.");
|
||||||
|
if (reasonPhrase == null)
|
||||||
|
throw new IllegalArgumentException("reasonPhrase can't be null.");
|
||||||
|
if (reasonPhrase.trim().isEmpty())
|
||||||
|
throw new IllegalArgumentException("reasonPhrase can't be empty.");
|
||||||
|
for (int i = 0; i < reasonPhrase.length(); i++) {
|
||||||
|
int c = reasonPhrase.charAt(i);
|
||||||
|
if (c > 0x7F) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"reasonPhrase can't contain non-ASCII characters.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mStatusCode = statusCode;
|
||||||
|
mReasonPhrase = reasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource response's status code.
|
||||||
|
*
|
||||||
|
* @return The resource response's status code.
|
||||||
|
*/
|
||||||
|
public int getStatusCode() {
|
||||||
|
return mStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the resource response's status code.
|
||||||
|
*
|
||||||
|
* @return The description of the resource response's status code.
|
||||||
|
*/
|
||||||
|
public String getReasonPhrase() {
|
||||||
|
return mReasonPhrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the headers for the resource response.
|
||||||
|
*
|
||||||
|
* @param headers Mapping of header name -> header value.
|
||||||
|
*/
|
||||||
|
public void setResponseHeaders(Map<String, String> headers) {
|
||||||
|
checkImmutable();
|
||||||
|
mResponseHeaders = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the headers for the resource response.
|
||||||
|
*
|
||||||
|
* @return The headers for the resource response.
|
||||||
|
*/
|
||||||
|
public Map<String, String> getResponseHeaders() {
|
||||||
|
return mResponseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the input stream that provides the resource response's data. Callers
|
||||||
|
* must implement {@link InputStream#read(byte[])}. {@link InputStream#close()}
|
||||||
|
* will be called after the WebView has finished with the response.
|
||||||
|
*
|
||||||
|
* @param data the input stream that provides the resource response's data. Must not be a
|
||||||
|
* StringBufferInputStream.
|
||||||
|
*/
|
||||||
|
public void setData(InputStream data) {
|
||||||
|
checkImmutable();
|
||||||
|
// If data is (or is a subclass of) StringBufferInputStream
|
||||||
|
if (data != null && StringBufferInputStream.class.isAssignableFrom(data.getClass())) {
|
||||||
|
throw new IllegalArgumentException("StringBufferInputStream is deprecated and must " +
|
||||||
|
"not be passed to a WebResourceResponse");
|
||||||
|
}
|
||||||
|
mInputStream = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the input stream that provides the resource response's data.
|
||||||
|
*
|
||||||
|
* @return The input stream that provides the resource response's data
|
||||||
|
*/
|
||||||
|
public InputStream getData() {
|
||||||
|
return mInputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal version of the constructor that doesn't perform arguments checks.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SystemApi
|
||||||
|
public WebResourceResponse(boolean immutable, String mimeType, String encoding, int statusCode,
|
||||||
|
String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
|
||||||
|
mImmutable = immutable;
|
||||||
|
mMimeType = mimeType;
|
||||||
|
mEncoding = encoding;
|
||||||
|
mStatusCode = statusCode;
|
||||||
|
mReasonPhrase = reasonPhrase;
|
||||||
|
mResponseHeaders = responseHeaders;
|
||||||
|
mInputStream = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkImmutable() {
|
||||||
|
if (mImmutable)
|
||||||
|
throw new IllegalStateException("This WebResourceResponse instance is immutable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,609 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2008 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.http.SslError;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.view.InputEvent;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
public class WebViewClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the host application a chance to take control when a URL is about to be loaded in the
|
||||||
|
* current WebView. If a WebViewClient is not provided, by default WebView will ask Activity
|
||||||
|
* Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning
|
||||||
|
* {@code true} causes the current WebView to abort loading the URL, while returning
|
||||||
|
* {@code false} causes the WebView to continue loading the URL as usual.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the same
|
||||||
|
* URL and then return {@code true}. This unnecessarily cancels the current load and starts a
|
||||||
|
* new load with the same URL. The correct way to continue loading a given URL is to simply
|
||||||
|
* return {@code false}, without calling {@link WebView#loadUrl(String)}.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method is not called for POST requests.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S)
|
||||||
|
* schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The URL to be loaded.
|
||||||
|
* @return {@code true} to cancel the current load, otherwise return {@code false}.
|
||||||
|
* @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest)
|
||||||
|
* shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the host application a chance to take control when a URL is about to be loaded in the
|
||||||
|
* current WebView. If a WebViewClient is not provided, by default WebView will ask Activity
|
||||||
|
* Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning
|
||||||
|
* {@code true} causes the current WebView to abort loading the URL, while returning
|
||||||
|
* {@code false} causes the WebView to continue loading the URL as usual.
|
||||||
|
*
|
||||||
|
* <p>This callback is not called for all page navigations. In particular, this is not called
|
||||||
|
* for navigations which the app initiated with {@code loadUrl()}: this callback would not serve
|
||||||
|
* a purpose in this case, because the app already knows about the navigation. This callback
|
||||||
|
* lets the app know about navigations initiated by the web page (such as navigations initiated
|
||||||
|
* by JavaScript code), by the user (such as when the user taps on a link), or by an HTTP
|
||||||
|
* redirect (ex. if {@code loadUrl("foo.com")} redirects to {@code "bar.com"} because of HTTP
|
||||||
|
* 301).
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's
|
||||||
|
* URL and then return {@code true}. This unnecessarily cancels the current load and starts a
|
||||||
|
* new load with the same URL. The correct way to continue loading a given URL is to simply
|
||||||
|
* return {@code false}, without calling {@link WebView#loadUrl(String)}.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method is not called for POST requests.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S)
|
||||||
|
* schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param request Object containing the details of the request.
|
||||||
|
* @return {@code true} to cancel the current load, otherwise return {@code false}.
|
||||||
|
*/
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||||
|
return shouldOverrideUrlLoading(view, request.getUrl().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has started loading. This method
|
||||||
|
* is called once for each main frame load so a page with iframes or
|
||||||
|
* framesets will call onPageStarted one time for the main frame. This also
|
||||||
|
* means that onPageStarted will not be called when the contents of an
|
||||||
|
* embedded frame changes, i.e. clicking a link whose target is an iframe,
|
||||||
|
* it will also not be called for fragment navigations (navigations to
|
||||||
|
* #fragment_id).
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url to be loaded.
|
||||||
|
* @param favicon The favicon for this page if it already exists in the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a page has finished loading. This method
|
||||||
|
* is called only for main frame. Receiving an {@code onPageFinished()} callback does not
|
||||||
|
* guarantee that the next frame drawn by WebView will reflect the state of the DOM at this
|
||||||
|
* point. In order to be notified that the current DOM state is ready to be rendered, request a
|
||||||
|
* visual state callback with {@link WebView#postVisualStateCallback} and wait for the supplied
|
||||||
|
* callback to be triggered.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url of the page.
|
||||||
|
*/
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that the WebView will load the resource
|
||||||
|
* specified by the given url.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url of the resource the WebView will load.
|
||||||
|
*/
|
||||||
|
public void onLoadResource(WebView view, String url) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that {@link android.webkit.WebView} content left over from
|
||||||
|
* previous page navigations will no longer be drawn.
|
||||||
|
*
|
||||||
|
* <p>This callback can be used to determine the point at which it is safe to make a recycled
|
||||||
|
* {@link android.webkit.WebView} visible, ensuring that no stale content is shown. It is called
|
||||||
|
* at the earliest point at which it can be guaranteed that {@link WebView#onDraw} will no
|
||||||
|
* longer draw any content from previous navigations. The next draw will display either the
|
||||||
|
* {@link WebView#setBackgroundColor background color} of the {@link WebView}, or some of the
|
||||||
|
* contents of the newly loaded page.
|
||||||
|
*
|
||||||
|
* <p>This method is called when the body of the HTTP response has started loading, is reflected
|
||||||
|
* in the DOM, and will be visible in subsequent draws. This callback occurs early in the
|
||||||
|
* document loading process, and as such you should expect that linked resources (for example,
|
||||||
|
* CSS and images) may not be available.
|
||||||
|
*
|
||||||
|
* <p>For more fine-grained notification of visual state updates, see {@link
|
||||||
|
* WebView#postVisualStateCallback}.
|
||||||
|
*
|
||||||
|
* <p>Please note that all the conditions and recommendations applicable to
|
||||||
|
* {@link WebView#postVisualStateCallback} also apply to this API.
|
||||||
|
*
|
||||||
|
* <p>This callback is only called for main frame navigations.
|
||||||
|
*
|
||||||
|
* @param view The {@link android.webkit.WebView} for which the navigation occurred.
|
||||||
|
* @param url The URL corresponding to the page navigation that triggered this callback.
|
||||||
|
*/
|
||||||
|
public void onPageCommitVisible(WebView view, String url) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application of a resource request and allow the
|
||||||
|
* application to return the data. If the return value is {@code null}, the WebView
|
||||||
|
* will continue to load the resource as usual. Otherwise, the return
|
||||||
|
* response and data will be used.
|
||||||
|
*
|
||||||
|
* <p>This callback is invoked for a variety of URL schemes (e.g., {@code http(s):}, {@code
|
||||||
|
* data:}, {@code file:}, etc.), not only those schemes which send requests over the network.
|
||||||
|
* This is not called for {@code javascript:} URLs, {@code blob:} URLs, or for assets accessed
|
||||||
|
* via {@code file:///android_asset/} or {@code file:///android_res/} URLs.
|
||||||
|
*
|
||||||
|
* <p>In the case of redirects, this is only called for the initial resource URL, not any
|
||||||
|
* subsequent redirect URLs.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method is called on a thread
|
||||||
|
* other than the UI thread so clients should exercise caution
|
||||||
|
* when accessing private data or the view system.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
|
||||||
|
* Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist}
|
||||||
|
* to skip Safe Browsing checks for that host or dismiss the warning in {@link
|
||||||
|
* #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}.
|
||||||
|
*
|
||||||
|
* @param view The {@link android.webkit.WebView} that is requesting the
|
||||||
|
* resource.
|
||||||
|
* @param url The raw url of the resource.
|
||||||
|
* @return A {@link android.webkit.WebResourceResponse} containing the
|
||||||
|
* response information or {@code null} if the WebView should load the
|
||||||
|
* resource itself.
|
||||||
|
* @deprecated Use {@link #shouldInterceptRequest(WebView, WebResourceRequest)
|
||||||
|
* shouldInterceptRequest(WebView, WebResourceRequest)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Nullable
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view,
|
||||||
|
String url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application of a resource request and allow the
|
||||||
|
* application to return the data. If the return value is {@code null}, the WebView
|
||||||
|
* will continue to load the resource as usual. Otherwise, the return
|
||||||
|
* response and data will be used.
|
||||||
|
*
|
||||||
|
* <p>This callback is invoked for a variety of URL schemes (e.g., {@code http(s):}, {@code
|
||||||
|
* data:}, {@code file:}, etc.), not only those schemes which send requests over the network.
|
||||||
|
* This is not called for {@code javascript:} URLs, {@code blob:} URLs, or for assets accessed
|
||||||
|
* via {@code file:///android_asset/} or {@code file:///android_res/} URLs.
|
||||||
|
*
|
||||||
|
* <p>In the case of redirects, this is only called for the initial resource URL, not any
|
||||||
|
* subsequent redirect URLs.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> This method is called on a thread
|
||||||
|
* other than the UI thread so clients should exercise caution
|
||||||
|
* when accessing private data or the view system.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
|
||||||
|
* Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist}
|
||||||
|
* to skip Safe Browsing checks for that host or dismiss the warning in {@link
|
||||||
|
* #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}.
|
||||||
|
*
|
||||||
|
* @param view The {@link android.webkit.WebView} that is requesting the
|
||||||
|
* resource.
|
||||||
|
* @param request Object containing the details of the request.
|
||||||
|
* @return A {@link android.webkit.WebResourceResponse} containing the
|
||||||
|
* response information or {@code null} if the WebView should load the
|
||||||
|
* resource itself.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public WebResourceResponse shouldInterceptRequest(WebView view,
|
||||||
|
WebResourceRequest request) {
|
||||||
|
return shouldInterceptRequest(view, request.getUrl().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that there have been an excessive number of
|
||||||
|
* HTTP redirects. As the host application if it would like to continue
|
||||||
|
* trying to load the resource. The default behavior is to send the cancel
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param cancelMsg The message to send if the host wants to cancel
|
||||||
|
* @param continueMsg The message to send if the host wants to continue
|
||||||
|
* @deprecated This method is no longer called. When the WebView encounters
|
||||||
|
* a redirect loop, it will cancel the load.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void onTooManyRedirects(WebView view, Message cancelMsg,
|
||||||
|
Message continueMsg) {
|
||||||
|
cancelMsg.sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// These ints must match up to the hidden values in EventHandler.
|
||||||
|
/** Generic error */
|
||||||
|
public static final int ERROR_UNKNOWN = -1;
|
||||||
|
/** Server or proxy hostname lookup failed */
|
||||||
|
public static final int ERROR_HOST_LOOKUP = -2;
|
||||||
|
/** Unsupported authentication scheme (not basic or digest) */
|
||||||
|
public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
|
||||||
|
/** User authentication failed on server */
|
||||||
|
public static final int ERROR_AUTHENTICATION = -4;
|
||||||
|
/** User authentication failed on proxy */
|
||||||
|
public static final int ERROR_PROXY_AUTHENTICATION = -5;
|
||||||
|
/** Failed to connect to the server */
|
||||||
|
public static final int ERROR_CONNECT = -6;
|
||||||
|
/** Failed to read or write to the server */
|
||||||
|
public static final int ERROR_IO = -7;
|
||||||
|
/** Connection timed out */
|
||||||
|
public static final int ERROR_TIMEOUT = -8;
|
||||||
|
/** Too many redirects */
|
||||||
|
public static final int ERROR_REDIRECT_LOOP = -9;
|
||||||
|
/** Unsupported URI scheme */
|
||||||
|
public static final int ERROR_UNSUPPORTED_SCHEME = -10;
|
||||||
|
/** Failed to perform SSL handshake */
|
||||||
|
public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
|
||||||
|
/** Malformed URL */
|
||||||
|
public static final int ERROR_BAD_URL = -12;
|
||||||
|
/** Generic file error */
|
||||||
|
public static final int ERROR_FILE = -13;
|
||||||
|
/** File not found */
|
||||||
|
public static final int ERROR_FILE_NOT_FOUND = -14;
|
||||||
|
/** Too many requests during this load */
|
||||||
|
public static final int ERROR_TOO_MANY_REQUESTS = -15;
|
||||||
|
/** Resource load was canceled by Safe Browsing */
|
||||||
|
public static final int ERROR_UNSAFE_RESOURCE = -16;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface SafeBrowsingThreat {}
|
||||||
|
|
||||||
|
/** The resource was blocked for an unknown reason. */
|
||||||
|
public static final int SAFE_BROWSING_THREAT_UNKNOWN = 0;
|
||||||
|
/** The resource was blocked because it contains malware. */
|
||||||
|
public static final int SAFE_BROWSING_THREAT_MALWARE = 1;
|
||||||
|
/** The resource was blocked because it contains deceptive content. */
|
||||||
|
public static final int SAFE_BROWSING_THREAT_PHISHING = 2;
|
||||||
|
/** The resource was blocked because it contains unwanted software. */
|
||||||
|
public static final int SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE = 3;
|
||||||
|
/**
|
||||||
|
* The resource was blocked because it may trick the user into a billing agreement.
|
||||||
|
*
|
||||||
|
* <p>This constant is only used when targetSdkVersion is at least {@link
|
||||||
|
* android.os.Build.VERSION_CODES#Q}. Otherwise, {@link #SAFE_BROWSING_THREAT_UNKNOWN} is used
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
public static final int SAFE_BROWSING_THREAT_BILLING = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error to the host application. These errors are unrecoverable
|
||||||
|
* (i.e. the main resource is unavailable). The {@code errorCode} parameter
|
||||||
|
* corresponds to one of the {@code ERROR_*} constants.
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||||
|
* @param description A String describing the error.
|
||||||
|
* @param failingUrl The url that failed to load.
|
||||||
|
* @deprecated Use {@link #onReceivedError(WebView, WebResourceRequest, WebResourceError)
|
||||||
|
* onReceivedError(WebView, WebResourceRequest, WebResourceError)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void onReceivedError(WebView view, int errorCode,
|
||||||
|
String description, String failingUrl) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report web resource loading error to the host application. These errors usually indicate
|
||||||
|
* inability to connect to the server. Note that unlike the deprecated version of the callback,
|
||||||
|
* the new version will be called for any resource (iframe, image, etc.), not just for the main
|
||||||
|
* page. Thus, it is recommended to perform minimum required work in this callback.
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param request The originating request.
|
||||||
|
* @param error Information about the error occurred.
|
||||||
|
*/
|
||||||
|
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||||
|
if (request.isForMainFrame()) {
|
||||||
|
onReceivedError(view,
|
||||||
|
error.getErrorCode(), error.getDescription().toString(),
|
||||||
|
request.getUrl().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that an HTTP error has been received from the server while
|
||||||
|
* loading a resource. HTTP errors have status codes >= 400. This callback will be called
|
||||||
|
* for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
|
||||||
|
* to perform minimum required work in this callback. Note that the content of the server
|
||||||
|
* response may not be provided within the {@code errorResponse} parameter.
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param request The originating request.
|
||||||
|
* @param errorResponse Information about the error occurred.
|
||||||
|
*/
|
||||||
|
public void onReceivedHttpError(
|
||||||
|
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As the host application if the browser should resend data as the
|
||||||
|
* requested page was a result of a POST. The default is to not resend the
|
||||||
|
* data.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param dontResend The message to send if the browser should not resend
|
||||||
|
* @param resend The message to send if the browser should resend data
|
||||||
|
*/
|
||||||
|
public void onFormResubmission(WebView view, Message dontResend,
|
||||||
|
Message resend) {
|
||||||
|
dontResend.sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application to update its visited links database.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param url The url being visited.
|
||||||
|
* @param isReload {@code true} if this url is being reloaded.
|
||||||
|
*/
|
||||||
|
public void doUpdateVisitedHistory(WebView view, String url,
|
||||||
|
boolean isReload) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the host application that an SSL error occurred while loading a
|
||||||
|
* resource. The host application must call either
|
||||||
|
* {@link SslErrorHandler#cancel()} or {@link SslErrorHandler#proceed()}.
|
||||||
|
*
|
||||||
|
* <p class="warning"><b>Warning:</b> Application overrides of this method
|
||||||
|
* can be used to display custom error pages or to silently log issues, but
|
||||||
|
* the host application should always call {@code SslErrorHandler#cancel()}
|
||||||
|
* and never proceed past errors.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> Do not prompt the user about SSL errors.
|
||||||
|
* Users are unlikely to be able to make an informed security decision, and
|
||||||
|
* {@code WebView} does not provide a UI for showing the details of the
|
||||||
|
* error in a meaningful way.
|
||||||
|
*
|
||||||
|
* <p>The decision to call {@code proceed()} or {@code cancel()} may be
|
||||||
|
* retained to facilitate responses to future SSL errors. The default
|
||||||
|
* behavior is to cancel the resource loading process.
|
||||||
|
*
|
||||||
|
* <p>This API is called only for recoverable SSL certificate errors. For
|
||||||
|
* non-recoverable errors (such as when the server fails the client), the
|
||||||
|
* {@code WebView} calls {@link #onReceivedError(WebView,
|
||||||
|
* WebResourceRequest, WebResourceError) onReceivedError(WebView,
|
||||||
|
* WebResourceRequest, WebResourceError)} with the
|
||||||
|
* {@link #ERROR_FAILED_SSL_HANDSHAKE} argument.
|
||||||
|
*
|
||||||
|
* @param view {@code WebView} that initiated the callback.
|
||||||
|
* @param handler {@link SslErrorHandler} that handles the user's response.
|
||||||
|
* @param error SSL error object.
|
||||||
|
*/
|
||||||
|
public void onReceivedSslError(WebView view, SslErrorHandler handler,
|
||||||
|
SslError error) {
|
||||||
|
handler.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application to handle a SSL client certificate request. The host application
|
||||||
|
* is responsible for showing the UI if desired and providing the keys. There are three ways to
|
||||||
|
* respond: {@link ClientCertRequest#proceed}, {@link ClientCertRequest#cancel}, or {@link
|
||||||
|
* ClientCertRequest#ignore}. Webview stores the response in memory (for the life of the
|
||||||
|
* application) if {@link ClientCertRequest#proceed} or {@link ClientCertRequest#cancel} is
|
||||||
|
* called and does not call {@code onReceivedClientCertRequest()} again for the same host and
|
||||||
|
* port pair. Webview does not store the response if {@link ClientCertRequest#ignore}
|
||||||
|
* is called. Note that, multiple layers in chromium network stack might be
|
||||||
|
* caching the responses, so the behavior for ignore is only a best case
|
||||||
|
* effort.
|
||||||
|
*
|
||||||
|
* This method is called on the UI thread. During the callback, the
|
||||||
|
* connection is suspended.
|
||||||
|
*
|
||||||
|
* For most use cases, the application program should implement the
|
||||||
|
* {@link android.security.KeyChainAliasCallback} interface and pass it to
|
||||||
|
* {@link android.security.KeyChain#choosePrivateKeyAlias} to start an
|
||||||
|
* activity for the user to choose the proper alias. The keychain activity will
|
||||||
|
* provide the alias through the callback method in the implemented interface. Next
|
||||||
|
* the application should create an async task to call
|
||||||
|
* {@link android.security.KeyChain#getPrivateKey} to receive the key.
|
||||||
|
*
|
||||||
|
* An example implementation of client certificates can be seen at
|
||||||
|
* <A href="https://android.googlesource.com/platform/packages/apps/Browser/+/android-5.1.1_r1/src/com/android/browser/Tab.java">
|
||||||
|
* AOSP Browser</a>
|
||||||
|
*
|
||||||
|
* The default behavior is to cancel, returning no client certificate.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback
|
||||||
|
* @param request An instance of a {@link ClientCertRequest}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||||
|
request.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the host application that the WebView received an HTTP
|
||||||
|
* authentication request. The host application can use the supplied
|
||||||
|
* {@link HttpAuthHandler} to set the WebView's response to the request.
|
||||||
|
* The default behavior is to cancel the request.
|
||||||
|
*
|
||||||
|
* <p class="note"><b>Note:</b> The supplied HttpAuthHandler must be used on
|
||||||
|
* the UI thread.
|
||||||
|
*
|
||||||
|
* @param view the WebView that is initiating the callback
|
||||||
|
* @param handler the HttpAuthHandler used to set the WebView's response
|
||||||
|
* @param host the host requiring authentication
|
||||||
|
* @param realm the realm for which authentication is required
|
||||||
|
* @see WebView#getHttpAuthUsernamePassword
|
||||||
|
*/
|
||||||
|
public void onReceivedHttpAuthRequest(WebView view,
|
||||||
|
HttpAuthHandler handler, String host, String realm) {
|
||||||
|
handler.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give the host application a chance to handle the key event synchronously.
|
||||||
|
* e.g. menu shortcut key events need to be filtered this way. If return
|
||||||
|
* true, WebView will not handle the key event. If return {@code false}, WebView
|
||||||
|
* will always handle the key event, so none of the super in the view chain
|
||||||
|
* will see the key event. The default behavior returns {@code false}.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param event The key event.
|
||||||
|
* @return {@code true} if the host application wants to handle the key event
|
||||||
|
* itself, otherwise return {@code false}
|
||||||
|
*/
|
||||||
|
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a key was not handled by the WebView.
|
||||||
|
* Except system keys, WebView always consumes the keys in the normal flow
|
||||||
|
* or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
|
||||||
|
* from where the key is dispatched. It gives the host application a chance
|
||||||
|
* to handle the unhandled key events.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param event The key event.
|
||||||
|
*/
|
||||||
|
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
|
||||||
|
onUnhandledInputEventInternal(view, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a input event was not handled by the WebView.
|
||||||
|
* Except system keys, WebView always consumes input events in the normal flow
|
||||||
|
* or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
|
||||||
|
* from where the event is dispatched. It gives the host application a chance
|
||||||
|
* to handle the unhandled input events.
|
||||||
|
*
|
||||||
|
* Note that if the event is a {@link android.view.MotionEvent}, then it's lifetime is only
|
||||||
|
* that of the function call. If the WebViewClient wishes to use the event beyond that, then it
|
||||||
|
* <i>must</i> create a copy of the event.
|
||||||
|
*
|
||||||
|
* It is the responsibility of overriders of this method to call
|
||||||
|
* {@link #onUnhandledKeyEvent(WebView, KeyEvent)}
|
||||||
|
* when appropriate if they wish to continue receiving events through it.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param event The input event.
|
||||||
|
* @removed
|
||||||
|
*/
|
||||||
|
public void onUnhandledInputEvent(WebView view, InputEvent event) {
|
||||||
|
if (event instanceof KeyEvent) {
|
||||||
|
onUnhandledKeyEvent(view, (KeyEvent) event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onUnhandledInputEventInternal(view, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUnhandledInputEventInternal(WebView view, InputEvent event) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that the scale applied to the WebView has
|
||||||
|
* changed.
|
||||||
|
*
|
||||||
|
* @param view The WebView that is initiating the callback.
|
||||||
|
* @param oldScale The old scale factor
|
||||||
|
* @param newScale The new scale factor
|
||||||
|
*/
|
||||||
|
public void onScaleChanged(WebView view, float oldScale, float newScale) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a request to automatically log in the
|
||||||
|
* user has been processed.
|
||||||
|
* @param view The WebView requesting the login.
|
||||||
|
* @param realm The account realm used to look up accounts.
|
||||||
|
* @param account An optional account. If not {@code null}, the account should be
|
||||||
|
* checked against accounts on the device. If it is a valid
|
||||||
|
* account, it should be used to log in the user.
|
||||||
|
* @param args Authenticator specific arguments used to log in the user.
|
||||||
|
*/
|
||||||
|
public void onReceivedLoginRequest(WebView view, String realm,
|
||||||
|
@Nullable String account, String args) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify host application that the given WebView's render process has exited.
|
||||||
|
*
|
||||||
|
* Multiple WebView instances may be associated with a single render process;
|
||||||
|
* onRenderProcessGone will be called for each WebView that was affected.
|
||||||
|
* The application's implementation of this callback should only attempt to
|
||||||
|
* clean up the specific WebView given as a parameter, and should not assume
|
||||||
|
* that other WebView instances are affected.
|
||||||
|
*
|
||||||
|
* The given WebView can't be used, and should be removed from the view hierarchy,
|
||||||
|
* all references to it should be cleaned up, e.g any references in the Activity
|
||||||
|
* or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
|
||||||
|
*
|
||||||
|
* To cause an render process crash for test purpose, the application can
|
||||||
|
* call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
|
||||||
|
* instances may be affected if they share a render process, not just the
|
||||||
|
* specific WebView which loaded chrome://crash.
|
||||||
|
*
|
||||||
|
* @param view The WebView which needs to be cleaned up.
|
||||||
|
* @param detail the reason why it exited.
|
||||||
|
* @return {@code true} if the host application handled the situation that process has
|
||||||
|
* exited, otherwise, application will crash if render process crashed,
|
||||||
|
* or be killed if render process was killed by the system.
|
||||||
|
*/
|
||||||
|
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the host application that a loading URL has been flagged by Safe Browsing.
|
||||||
|
*
|
||||||
|
* The application must invoke the callback to indicate the preferred response. The default
|
||||||
|
* behavior is to show an interstitial to the user, with the reporting checkbox visible.
|
||||||
|
*
|
||||||
|
* If the application needs to show its own custom interstitial UI, the callback can be invoked
|
||||||
|
* asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
|
||||||
|
* SafeBrowsingResponse#proceed}, depending on user response.
|
||||||
|
*
|
||||||
|
* @param view The WebView that hit the malicious resource.
|
||||||
|
* @param request Object containing the details of the request.
|
||||||
|
* @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
|
||||||
|
* {@code SAFE_BROWSING_THREAT_*} value.
|
||||||
|
* @param callback Applications must invoke one of the callback methods.
|
||||||
|
*/
|
||||||
|
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
|
||||||
|
@SafeBrowsingThreat int threatType, SafeBrowsingResponse callback) {
|
||||||
|
callback.showInterstitial(/* allowReporting */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,541 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.webkit;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Picture;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.net.http.SslCertificate;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.print.PrintDocumentAdapter;
|
||||||
|
import android.util.LongSparseArray;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.DragEvent;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.PointerIcon;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup.LayoutParams;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.view.accessibility.AccessibilityNodeProvider;
|
||||||
|
import android.view.autofill.AutofillId;
|
||||||
|
import android.view.autofill.AutofillValue;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputConnection;
|
||||||
|
import android.view.textclassifier.TextClassifier;
|
||||||
|
import android.webkit.WebView.HitTestResult;
|
||||||
|
import android.webkit.WebView.PictureListener;
|
||||||
|
import android.webkit.WebView.VisualStateCallback;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebView backend provider interface: this interface is the abstract backend to a WebView
|
||||||
|
* instance; each WebView object is bound to exactly one WebViewProvider object which implements
|
||||||
|
* the runtime behavior of that WebView.
|
||||||
|
*
|
||||||
|
* All methods must behave as per their namesake in {@link WebView}, unless otherwise noted.
|
||||||
|
*
|
||||||
|
* @hide Not part of the public API; only required by system implementors.
|
||||||
|
*/
|
||||||
|
@SystemApi
|
||||||
|
public interface WebViewProvider {
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Main interface for backend provider of the WebView class.
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Initialize this WebViewProvider instance. Called after the WebView has fully constructed.
|
||||||
|
* @param javaScriptInterfaces is a Map of interface names, as keys, and
|
||||||
|
* object implementing those interfaces, as values.
|
||||||
|
* @param privateBrowsing If {@code true} the web view will be initialized in private /
|
||||||
|
* incognito mode.
|
||||||
|
*/
|
||||||
|
public void init(Map<String, Object> javaScriptInterfaces,
|
||||||
|
boolean privateBrowsing);
|
||||||
|
|
||||||
|
// Deprecated - should never be called
|
||||||
|
public void setHorizontalScrollbarOverlay(boolean overlay);
|
||||||
|
|
||||||
|
// Deprecated - should never be called
|
||||||
|
public void setVerticalScrollbarOverlay(boolean overlay);
|
||||||
|
|
||||||
|
// Deprecated - should never be called
|
||||||
|
public boolean overlayHorizontalScrollbar();
|
||||||
|
|
||||||
|
// Deprecated - should never be called
|
||||||
|
public boolean overlayVerticalScrollbar();
|
||||||
|
|
||||||
|
public int getVisibleTitleHeight();
|
||||||
|
|
||||||
|
public SslCertificate getCertificate();
|
||||||
|
|
||||||
|
public void setCertificate(SslCertificate certificate);
|
||||||
|
|
||||||
|
public void savePassword(String host, String username, String password);
|
||||||
|
|
||||||
|
public void setHttpAuthUsernamePassword(String host, String realm,
|
||||||
|
String username, String password);
|
||||||
|
|
||||||
|
public String[] getHttpAuthUsernamePassword(String host, String realm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link WebView#destroy()}.
|
||||||
|
* As well as releasing the internal state and resources held by the implementation,
|
||||||
|
* the provider should null all references it holds on the WebView proxy class, and ensure
|
||||||
|
* no further method calls are made to it.
|
||||||
|
*/
|
||||||
|
public void destroy();
|
||||||
|
|
||||||
|
public void setNetworkAvailable(boolean networkUp);
|
||||||
|
|
||||||
|
public WebBackForwardList saveState(Bundle outState);
|
||||||
|
|
||||||
|
public boolean savePicture(Bundle b, final File dest);
|
||||||
|
|
||||||
|
public boolean restorePicture(Bundle b, File src);
|
||||||
|
|
||||||
|
public WebBackForwardList restoreState(Bundle inState);
|
||||||
|
|
||||||
|
public void loadUrl(String url, Map<String, String> additionalHttpHeaders);
|
||||||
|
|
||||||
|
public void loadUrl(String url);
|
||||||
|
|
||||||
|
public void postUrl(String url, byte[] postData);
|
||||||
|
|
||||||
|
public void loadData(String data, String mimeType, String encoding);
|
||||||
|
|
||||||
|
public void loadDataWithBaseURL(String baseUrl, String data,
|
||||||
|
String mimeType, String encoding, String historyUrl);
|
||||||
|
|
||||||
|
public void evaluateJavaScript(String script, ValueCallback<String> resultCallback);
|
||||||
|
|
||||||
|
public void saveWebArchive(String filename);
|
||||||
|
|
||||||
|
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback);
|
||||||
|
|
||||||
|
public void stopLoading();
|
||||||
|
|
||||||
|
public void reload();
|
||||||
|
|
||||||
|
public boolean canGoBack();
|
||||||
|
|
||||||
|
public void goBack();
|
||||||
|
|
||||||
|
public boolean canGoForward();
|
||||||
|
|
||||||
|
public void goForward();
|
||||||
|
|
||||||
|
public boolean canGoBackOrForward(int steps);
|
||||||
|
|
||||||
|
public void goBackOrForward(int steps);
|
||||||
|
|
||||||
|
public boolean isPrivateBrowsingEnabled();
|
||||||
|
|
||||||
|
public boolean pageUp(boolean top);
|
||||||
|
|
||||||
|
public boolean pageDown(boolean bottom);
|
||||||
|
|
||||||
|
public void insertVisualStateCallback(long requestId, VisualStateCallback callback);
|
||||||
|
|
||||||
|
public void clearView();
|
||||||
|
|
||||||
|
public Picture capturePicture();
|
||||||
|
|
||||||
|
public PrintDocumentAdapter createPrintDocumentAdapter(String documentName);
|
||||||
|
|
||||||
|
public float getScale();
|
||||||
|
|
||||||
|
public void setInitialScale(int scaleInPercent);
|
||||||
|
|
||||||
|
public void invokeZoomPicker();
|
||||||
|
|
||||||
|
public HitTestResult getHitTestResult();
|
||||||
|
|
||||||
|
public void requestFocusNodeHref(Message hrefMsg);
|
||||||
|
|
||||||
|
public void requestImageRef(Message msg);
|
||||||
|
|
||||||
|
public String getUrl();
|
||||||
|
|
||||||
|
public String getOriginalUrl();
|
||||||
|
|
||||||
|
public String getTitle();
|
||||||
|
|
||||||
|
public Bitmap getFavicon();
|
||||||
|
|
||||||
|
public String getTouchIconUrl();
|
||||||
|
|
||||||
|
public int getProgress();
|
||||||
|
|
||||||
|
public int getContentHeight();
|
||||||
|
|
||||||
|
public int getContentWidth();
|
||||||
|
|
||||||
|
public void pauseTimers();
|
||||||
|
|
||||||
|
public void resumeTimers();
|
||||||
|
|
||||||
|
public void onPause();
|
||||||
|
|
||||||
|
public void onResume();
|
||||||
|
|
||||||
|
public boolean isPaused();
|
||||||
|
|
||||||
|
public void freeMemory();
|
||||||
|
|
||||||
|
public void clearCache(boolean includeDiskFiles);
|
||||||
|
|
||||||
|
public void clearFormData();
|
||||||
|
|
||||||
|
public void clearHistory();
|
||||||
|
|
||||||
|
public void clearSslPreferences();
|
||||||
|
|
||||||
|
public WebBackForwardList copyBackForwardList();
|
||||||
|
|
||||||
|
public void setFindListener(WebView.FindListener listener);
|
||||||
|
|
||||||
|
public void findNext(boolean forward);
|
||||||
|
|
||||||
|
public int findAll(String find);
|
||||||
|
|
||||||
|
public void findAllAsync(String find);
|
||||||
|
|
||||||
|
public boolean showFindDialog(String text, boolean showIme);
|
||||||
|
|
||||||
|
public void clearMatches();
|
||||||
|
|
||||||
|
public void documentHasImages(Message response);
|
||||||
|
|
||||||
|
public void setWebViewClient(WebViewClient client);
|
||||||
|
|
||||||
|
public WebViewClient getWebViewClient();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public WebViewRenderProcess getWebViewRenderProcess();
|
||||||
|
|
||||||
|
public void setWebViewRenderProcessClient(
|
||||||
|
@Nullable Executor executor,
|
||||||
|
@Nullable WebViewRenderProcessClient client);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public WebViewRenderProcessClient getWebViewRenderProcessClient();
|
||||||
|
|
||||||
|
public void setDownloadListener(DownloadListener listener);
|
||||||
|
|
||||||
|
public void setWebChromeClient(WebChromeClient client);
|
||||||
|
|
||||||
|
public WebChromeClient getWebChromeClient();
|
||||||
|
|
||||||
|
public void setPictureListener(PictureListener listener);
|
||||||
|
|
||||||
|
public void addJavascriptInterface(Object obj, String interfaceName);
|
||||||
|
|
||||||
|
public void removeJavascriptInterface(String interfaceName);
|
||||||
|
|
||||||
|
public WebMessagePort[] createWebMessageChannel();
|
||||||
|
|
||||||
|
public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
|
||||||
|
|
||||||
|
public WebSettings getSettings();
|
||||||
|
|
||||||
|
public void setMapTrackballToArrowKeys(boolean setMap);
|
||||||
|
|
||||||
|
public void flingScroll(int vx, int vy);
|
||||||
|
|
||||||
|
public View getZoomControls();
|
||||||
|
|
||||||
|
public boolean canZoomIn();
|
||||||
|
|
||||||
|
public boolean canZoomOut();
|
||||||
|
|
||||||
|
public boolean zoomBy(float zoomFactor);
|
||||||
|
|
||||||
|
public boolean zoomIn();
|
||||||
|
|
||||||
|
public boolean zoomOut();
|
||||||
|
|
||||||
|
public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
|
||||||
|
|
||||||
|
public View findHierarchyView(String className, int hashCode);
|
||||||
|
|
||||||
|
public void setRendererPriorityPolicy(int rendererRequestedPriority, boolean waivedWhenNotVisible);
|
||||||
|
|
||||||
|
public int getRendererRequestedPriority();
|
||||||
|
|
||||||
|
public boolean getRendererPriorityWaivedWhenNotVisible();
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public default void setTextClassifier(@Nullable TextClassifier textClassifier) {}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public default TextClassifier getTextClassifier() { return TextClassifier.NO_OP; }
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// Provider internal methods
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ViewDelegate implementation. This provides the functionality to back all of
|
||||||
|
* the name-sake functions from the View and ViewGroup base classes of WebView.
|
||||||
|
*/
|
||||||
|
/* package */ ViewDelegate getViewDelegate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ScrollDelegate implementation. Normally this would be same object as is
|
||||||
|
* returned by getViewDelegate().
|
||||||
|
*/
|
||||||
|
/* package */ ScrollDelegate getScrollDelegate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only used by FindActionModeCallback to inform providers that the find dialog has
|
||||||
|
* been dismissed.
|
||||||
|
*/
|
||||||
|
public void notifyFindDialogDismissed();
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// View / ViewGroup delegation methods
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
|
||||||
|
* into the WebViewProvider instance.
|
||||||
|
* NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
|
||||||
|
* making the call into the provider instance. This is done for convenience in the common case
|
||||||
|
* of maintaining backward compatibility. For remaining super class calls (e.g. where the
|
||||||
|
* provider may need to only conditionally make the call based on some internal state) see the
|
||||||
|
* {@link WebView.PrivateAccess} callback class.
|
||||||
|
*/
|
||||||
|
// TODO: See if the pattern of the super-class calls can be rationalized at all, and document
|
||||||
|
// the remainder on the methods below.
|
||||||
|
interface ViewDelegate {
|
||||||
|
public boolean shouldDelayChildPressedState();
|
||||||
|
|
||||||
|
public void onProvideVirtualStructure(android.view.ViewStructure structure);
|
||||||
|
|
||||||
|
default void onProvideAutofillVirtualStructure(
|
||||||
|
@SuppressWarnings("unused") android.view.ViewStructure structure,
|
||||||
|
@SuppressWarnings("unused") int flags) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void autofill(@SuppressWarnings("unused") SparseArray<AutofillValue> values) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isVisibleToUserForAutofill(@SuppressWarnings("unused") int virtualId) {
|
||||||
|
return true; // true is the default value returned by View.isVisibleToUserForAutofill()
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onProvideContentCaptureStructure(
|
||||||
|
@NonNull @SuppressWarnings("unused") android.view.ViewStructure structure,
|
||||||
|
@SuppressWarnings("unused") int flags) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// @SuppressLint("NullableCollection")
|
||||||
|
// default void onCreateVirtualViewTranslationRequests(
|
||||||
|
// @NonNull @SuppressWarnings("unused") long[] virtualIds,
|
||||||
|
// @NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
|
||||||
|
// @NonNull @SuppressWarnings("unused")
|
||||||
|
// Consumer<ViewTranslationRequest> requestsCollector) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// default void onVirtualViewTranslationResponses(
|
||||||
|
// @NonNull @SuppressWarnings("unused")
|
||||||
|
// LongSparseArray<ViewTranslationResponse> response) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// default void dispatchCreateViewTranslationRequest(
|
||||||
|
// @NonNull @SuppressWarnings("unused") Map<AutofillId, long[]> viewIds,
|
||||||
|
// @NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
|
||||||
|
// @Nullable @SuppressWarnings("unused") TranslationCapability capability,
|
||||||
|
// @NonNull @SuppressWarnings("unused") List<ViewTranslationRequest> requests) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
public AccessibilityNodeProvider getAccessibilityNodeProvider();
|
||||||
|
|
||||||
|
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
|
||||||
|
|
||||||
|
public void onInitializeAccessibilityEvent(AccessibilityEvent event);
|
||||||
|
|
||||||
|
public boolean performAccessibilityAction(int action, Bundle arguments);
|
||||||
|
|
||||||
|
public void setOverScrollMode(int mode);
|
||||||
|
|
||||||
|
public void setScrollBarStyle(int style);
|
||||||
|
|
||||||
|
public void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t,
|
||||||
|
int r, int b);
|
||||||
|
|
||||||
|
public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY);
|
||||||
|
|
||||||
|
public void onWindowVisibilityChanged(int visibility);
|
||||||
|
|
||||||
|
public void onDraw(Canvas canvas);
|
||||||
|
|
||||||
|
public void setLayoutParams(LayoutParams layoutParams);
|
||||||
|
|
||||||
|
public boolean performLongClick();
|
||||||
|
|
||||||
|
public void onConfigurationChanged(Configuration newConfig);
|
||||||
|
|
||||||
|
public InputConnection onCreateInputConnection(EditorInfo outAttrs);
|
||||||
|
|
||||||
|
public boolean onDragEvent(DragEvent event);
|
||||||
|
|
||||||
|
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
|
||||||
|
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event);
|
||||||
|
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event);
|
||||||
|
|
||||||
|
public void onAttachedToWindow();
|
||||||
|
|
||||||
|
public void onDetachedFromWindow();
|
||||||
|
|
||||||
|
public default void onMovedToDisplay(int displayId, Configuration config) {}
|
||||||
|
|
||||||
|
public void onVisibilityChanged(View changedView, int visibility);
|
||||||
|
|
||||||
|
public void onWindowFocusChanged(boolean hasWindowFocus);
|
||||||
|
|
||||||
|
public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect);
|
||||||
|
|
||||||
|
public boolean setFrame(int left, int top, int right, int bottom);
|
||||||
|
|
||||||
|
public void onSizeChanged(int w, int h, int ow, int oh);
|
||||||
|
|
||||||
|
public void onScrollChanged(int l, int t, int oldl, int oldt);
|
||||||
|
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event);
|
||||||
|
|
||||||
|
public boolean onTouchEvent(MotionEvent ev);
|
||||||
|
|
||||||
|
public boolean onHoverEvent(MotionEvent event);
|
||||||
|
|
||||||
|
public boolean onGenericMotionEvent(MotionEvent event);
|
||||||
|
|
||||||
|
public boolean onTrackballEvent(MotionEvent ev);
|
||||||
|
|
||||||
|
public boolean requestFocus(int direction, Rect previouslyFocusedRect);
|
||||||
|
|
||||||
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
|
||||||
|
|
||||||
|
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate);
|
||||||
|
|
||||||
|
public void setBackgroundColor(int color);
|
||||||
|
|
||||||
|
public void setLayerType(int layerType, Paint paint);
|
||||||
|
|
||||||
|
public void preDispatchDraw(Canvas canvas);
|
||||||
|
|
||||||
|
public void onStartTemporaryDetach();
|
||||||
|
|
||||||
|
public void onFinishTemporaryDetach();
|
||||||
|
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||||
|
|
||||||
|
public Handler getHandler(Handler originalHandler);
|
||||||
|
|
||||||
|
public View findFocus(View originalFocusedView);
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
default boolean onCheckIsTextEditor() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see View#onApplyWindowInsets(WindowInsets).
|
||||||
|
*
|
||||||
|
* <p>This is the entry point for the WebView implementation to override. It returns
|
||||||
|
* {@code null} when the WebView implementation hasn't implemented the WindowInsets support
|
||||||
|
* on S yet. In this case, the {@link View#onApplyWindowInsets()} super method will be
|
||||||
|
* called instead.
|
||||||
|
*
|
||||||
|
* @param insets Insets to apply
|
||||||
|
* @return The supplied insets with any applied insets consumed.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Nullable
|
||||||
|
default WindowInsets onApplyWindowInsets(@Nullable WindowInsets insets) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide Only used by WebView.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Nullable
|
||||||
|
default PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScrollDelegate {
|
||||||
|
// These methods are declared protected in the ViewGroup base class. This interface
|
||||||
|
// exists to promote them to public so they may be called by the WebView proxy class.
|
||||||
|
// TODO: Combine into ViewDelegate?
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeHorizontalScrollRange}
|
||||||
|
*/
|
||||||
|
public int computeHorizontalScrollRange();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeHorizontalScrollOffset}
|
||||||
|
*/
|
||||||
|
public int computeHorizontalScrollOffset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeVerticalScrollRange}
|
||||||
|
*/
|
||||||
|
public int computeVerticalScrollRange();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeVerticalScrollOffset}
|
||||||
|
*/
|
||||||
|
public int computeVerticalScrollOffset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeVerticalScrollExtent}
|
||||||
|
*/
|
||||||
|
public int computeVerticalScrollExtent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link android.webkit.WebView#computeScroll}
|
||||||
|
*/
|
||||||
|
public void computeScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.RemoteViews.RemoteView;
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@RemoteView
|
||||||
|
public class AbsoluteLayout extends ViewGroup {
|
||||||
|
public AbsoluteLayout(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbsoluteLayout(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
this(context, attrs, defStyleAttr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||||
|
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int l, int t,
|
||||||
|
int r, int b) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||||
|
return new AbsoluteLayout.LayoutParams(getContext(), attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override to allow type-checking of LayoutParams.
|
||||||
|
@Override
|
||||||
|
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||||
|
return p instanceof AbsoluteLayout.LayoutParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
||||||
|
return new LayoutParams(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldDelayChildPressedState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LayoutParams extends ViewGroup.LayoutParams {
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
|
||||||
|
public LayoutParams(int width, int height, int x, int y) {
|
||||||
|
super(width, height);
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutParams(Context c, AttributeSet attrs) {
|
||||||
|
super(c, attrs);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutParams(ViewGroup.LayoutParams source) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String debug(String output) {
|
||||||
|
return output + "Absolute.LayoutParams={width="
|
||||||
|
+ String.valueOf(width) + ", height=" + String.valueOf(height)
|
||||||
|
+ " x=" + x + " y=" + y + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,26 @@ package android.widget;
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
public class EditText {
|
public class EditText extends TextView {
|
||||||
public EditText(android.content.Context context) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) { throw new RuntimeException("Stub!"); }
|
public EditText(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context);
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
public boolean getFreezesText() { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
|
||||||
|
|
||||||
|
package androidx.core.net
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Uri from the given encoded URI string.
|
||||||
|
*
|
||||||
|
* @see Uri.parse
|
||||||
|
*/
|
||||||
|
public inline fun String.toUri(): Uri = Uri.parse(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Uri from the given file.
|
||||||
|
*
|
||||||
|
* @see Uri.fromFile
|
||||||
|
*/
|
||||||
|
public inline fun File.toUri(): Uri = Uri.fromFile(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [File] from the given [Uri]. Note that this will throw an
|
||||||
|
* [IllegalArgumentException] when invoked on a [Uri] that lacks `file` scheme.
|
||||||
|
*/
|
||||||
|
public fun Uri.toFile(): File {
|
||||||
|
require(scheme == "file") { "Uri lacks 'file' scheme: $this" }
|
||||||
|
return File(requireNotNull(path) { "Uri path is null: $this" })
|
||||||
|
}
|
||||||
@@ -13,33 +13,49 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MultiSelectListPreference extends DialogPreference {
|
public class MultiSelectListPreference extends DialogPreference {
|
||||||
|
// reference: https://android.googlesource.com/platform/frameworks/support/+/996971f962fcd554339a7cb2859cef9ca89dbcb7/preference/preference/src/main/java/androidx/preference/MultiSelectListPreference.java
|
||||||
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
public MultiSelectListPreference(Context context) { super(context); }
|
private CharSequence[] entries;
|
||||||
|
private CharSequence[] entryValues;
|
||||||
|
|
||||||
|
public MultiSelectListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntries(CharSequence[] entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntryValues(CharSequence[] entryValues) {
|
||||||
|
this.entryValues = entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharSequence[] getEntryValues() {
|
||||||
|
return entryValues;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public void setEntries(CharSequence[] entries) { throw new RuntimeException("Stub!"); }
|
public void setValues(Set<String> values) {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public CharSequence[] getEntries() { throw new RuntimeException("Stub!"); }
|
public Set<String> getValues() {
|
||||||
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
public int findIndexOfValue(String value) {
|
||||||
public void setEntryValues(CharSequence[] entryValues) { throw new RuntimeException("Stub!"); }
|
throw new RuntimeException("Stub!");
|
||||||
|
}
|
||||||
@JsonIgnore
|
|
||||||
public CharSequence[] getEntryValues() { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public void setValues(Set<String> values) { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public Set<String> getValues() { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
public int findIndexOfValue(String value) { throw new RuntimeException("Stub!"); }
|
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
@Override
|
@Override
|
||||||
public String getDefaultValueType() {
|
public String getDefaultValueType() {
|
||||||
return "Set";
|
return "Set<String>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,10 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A minimal implementation of androidx.preference.Preference
|
* A minimal implementation of androidx.preference.Preference
|
||||||
*/
|
*/
|
||||||
@@ -21,6 +25,8 @@ public class Preference {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
protected Context context;
|
protected Context context;
|
||||||
|
|
||||||
|
private boolean isVisible = true;
|
||||||
|
private boolean isEnabled = true;
|
||||||
private String key;
|
private String key;
|
||||||
private CharSequence title;
|
private CharSequence title;
|
||||||
private CharSequence summary;
|
private CharSequence summary;
|
||||||
@@ -66,7 +72,11 @@ public class Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
throw new RuntimeException("Stub!");
|
isEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
@@ -99,6 +109,14 @@ public class Preference {
|
|||||||
return sharedPreferences;
|
return sharedPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVisible(boolean visible) {
|
||||||
|
isVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getVisible() {
|
||||||
|
return isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
public void setSharedPreferences(SharedPreferences sharedPreferences) {
|
public void setSharedPreferences(SharedPreferences sharedPreferences) {
|
||||||
this.sharedPreferences = sharedPreferences;
|
this.sharedPreferences = sharedPreferences;
|
||||||
@@ -113,28 +131,36 @@ public class Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Object getCurrentValue() {
|
public Object getCurrentValue() {
|
||||||
switch (getDefaultValueType()) {
|
if (key == null) {
|
||||||
case "String":
|
return Objects.requireNonNullElseGet(defaultValue, () -> switch (getDefaultValueType()) {
|
||||||
return sharedPreferences.getString(key, (String)defaultValue);
|
case "String" -> "";
|
||||||
case "Boolean":
|
case "Boolean" -> false;
|
||||||
return sharedPreferences.getBoolean(key, (Boolean)defaultValue);
|
case "Set<String>" -> new HashSet<>();
|
||||||
default:
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
throw new RuntimeException("Unsupported type");
|
});
|
||||||
}
|
}
|
||||||
|
return switch (getDefaultValueType()) {
|
||||||
|
case "String" -> sharedPreferences.getString(key, (String) defaultValue);
|
||||||
|
case "Boolean" -> sharedPreferences.getBoolean(key, (Boolean) defaultValue);
|
||||||
|
case "Set<String>" -> sharedPreferences.getStringSet(key, (Set<String>) defaultValue);
|
||||||
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void saveNewValue(Object value) {
|
public void saveNewValue(Object value) {
|
||||||
|
if (key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (getDefaultValueType()) {
|
switch (getDefaultValueType()) {
|
||||||
case "String":
|
case "String" -> sharedPreferences.edit().putString(key, (String) value).apply();
|
||||||
sharedPreferences.edit().putString(key, (String)value).apply();
|
case "Boolean" -> sharedPreferences.edit().putBoolean(key, (Boolean) value).apply();
|
||||||
break;
|
case "Set<String>" ->
|
||||||
case "Boolean":
|
sharedPreferences.edit().putStringSet(key, (Set<String>) value).apply();
|
||||||
sharedPreferences.edit().putBoolean(key, (Boolean)value).apply();
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unsupported type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import android.content.Context;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
public class TwoStatePreference extends Preference {
|
public class TwoStatePreference extends Preference {
|
||||||
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
private CharSequence mSummaryOn;
|
||||||
|
private CharSequence mSummaryOff;
|
||||||
|
|
||||||
public TwoStatePreference(Context context) { super(context); }
|
public TwoStatePreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
setDefaultValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public boolean isChecked() { throw new RuntimeException("Stub!"); }
|
public boolean isChecked() { throw new RuntimeException("Stub!"); }
|
||||||
@@ -22,16 +26,41 @@ public class TwoStatePreference extends Preference {
|
|||||||
public void setChecked(boolean checked) { throw new RuntimeException("Stub!"); }
|
public void setChecked(boolean checked) { throw new RuntimeException("Stub!"); }
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public CharSequence getSummaryOn() { throw new RuntimeException("Stub!"); }
|
public CharSequence getSummaryOn() {
|
||||||
|
return mSummaryOn;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public void setSummaryOn(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
public void setSummaryOn(CharSequence summary) {
|
||||||
|
this.mSummaryOn = summary;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public CharSequence getSummaryOff() { throw new RuntimeException("Stub!"); }
|
public CharSequence getSummaryOff() {
|
||||||
|
return mSummaryOff;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public void setSummaryOff(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
public void setSummaryOff(CharSequence summary) {
|
||||||
|
this.mSummaryOff = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getSummary() {
|
||||||
|
final CharSequence summary = super.getSummary();
|
||||||
|
if (summary != null) {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean checked = (Boolean) getCurrentValue();
|
||||||
|
if (checked && mSummaryOn != null) {
|
||||||
|
return mSummaryOn;
|
||||||
|
} else if (!checked && mSummaryOff != null) {
|
||||||
|
return mSummaryOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public boolean getDisableDependentsState() { throw new RuntimeException("Stub!"); }
|
public boolean getDisableDependentsState() { throw new RuntimeException("Stub!"); }
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package app.cash.quickjs;
|
||||||
|
|
||||||
|
import org.graalvm.polyglot.*;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public final class QuickJs implements Closeable {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public static QuickJs create() {
|
||||||
|
return new QuickJs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuickJs() {
|
||||||
|
this.context = Context
|
||||||
|
.newBuilder("js")
|
||||||
|
.allowHostAccess(HostAccess.ALL)
|
||||||
|
.allowPolyglotAccess(PolyglotAccess.NONE)
|
||||||
|
.allowHostClassLoading(false)
|
||||||
|
.build();
|
||||||
|
context.enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object evaluate(String script, String ignoredFileName) {
|
||||||
|
return this.evaluate(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object evaluate(String script) {
|
||||||
|
try {
|
||||||
|
Value value = context.eval("js", script);
|
||||||
|
return translateType(value);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new QuickJsException(exception.getMessage(), exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object translateType(Value obj) {
|
||||||
|
if (obj.isBoolean()) {
|
||||||
|
return obj.asBoolean();
|
||||||
|
} else if (obj.hasArrayElements()) {
|
||||||
|
if (obj.getArraySize() == 0) {
|
||||||
|
return new int[0];
|
||||||
|
} else {
|
||||||
|
Value element = obj.getArrayElement(0);
|
||||||
|
if (element.isBoolean()) {
|
||||||
|
return obj.as(boolean[].class);
|
||||||
|
} else if (element.isNumber()) {
|
||||||
|
if (element.fitsInInt()) {
|
||||||
|
return obj.as(int[].class);
|
||||||
|
} else if (element.fitsInBigInteger()) {
|
||||||
|
return Arrays.stream(obj.as(BigInteger[].class)).map(BigInteger::longValue).toArray();
|
||||||
|
} else {
|
||||||
|
return obj.as(double[].class);
|
||||||
|
}
|
||||||
|
} else if (element.isHostObject()) {
|
||||||
|
return obj.as(Object[].class);
|
||||||
|
} else if (element.isString()) {
|
||||||
|
return obj.as(String[].class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (obj.isNumber()) {
|
||||||
|
if (obj.fitsInInt()) {
|
||||||
|
return obj.asInt();
|
||||||
|
} else if (obj.fitsInBigInteger()) {
|
||||||
|
return obj.asBigInteger().longValue();
|
||||||
|
} else {
|
||||||
|
return obj.asDouble();
|
||||||
|
}
|
||||||
|
} else if (obj.isHostObject()) {
|
||||||
|
return obj.asHostObject();
|
||||||
|
} else if (obj.isString()) {
|
||||||
|
return obj.asString();
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] compile(String sourceCode, String ignoredFileName) {
|
||||||
|
return sourceCode.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(byte[] bytecode) {
|
||||||
|
return this.evaluate(new String(bytecode));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void set(String name, Class<T> ignoredType, T object) {
|
||||||
|
context.getBindings("js").putMember(name, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.context != null) {
|
||||||
|
this.context.leave();
|
||||||
|
this.context.close();
|
||||||
|
this.context = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package app.cash.quickjs;
|
||||||
|
|
||||||
|
public final class QuickJsException extends RuntimeException {
|
||||||
|
public QuickJsException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
import android.annotation.NonNull;
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import libcore.util.EmptyArray;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import libcore.util.EmptyArray;
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ArrayUtils contains some methods that you can call to find out
|
* ArrayUtils contains some methods that you can call to find out
|
||||||
* the most efficient increments by which to grow arrays.
|
* the most efficient increments by which to grow arrays.
|
||||||
@@ -50,6 +50,10 @@ public class ArrayUtils {
|
|||||||
public static Object[] newUnpaddedObjectArray(int minLen) {
|
public static Object[] newUnpaddedObjectArray(int minLen) {
|
||||||
return new Object[minLen];
|
return new Object[minLen];
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
|
||||||
|
return (T[])Array.newInstance(clazz, minLen);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Checks if the beginnings of two byte arrays are equal.
|
* Checks if the beginnings of two byte arrays are equal.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.internal.util;
|
||||||
|
|
||||||
|
public final class GrowingArrayUtils {
|
||||||
|
|
||||||
|
public static <T> T[] append(T[] array, int currentSize, T element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] newArray = ArrayUtils.newUnpaddedArray(
|
||||||
|
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] append(int[] array, int currentSize, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] append(long[] array, int currentSize, long element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean[] append(boolean[] array, int currentSize, boolean element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] append(float[] array, int currentSize, float element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
|
||||||
|
growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] insert(int[] array, int currentSize, int index, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long[] insert(long[] array, int currentSize, int index, long element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int growSize(int currentSize) {
|
||||||
|
return currentSize <= 4 ? 8 : currentSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstantiable
|
||||||
|
private GrowingArrayUtils() {}
|
||||||
|
}
|
||||||
@@ -14,9 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.internal.util;
|
package com.android.internal.util;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Bitmap.CompressFormat;
|
import android.graphics.Bitmap.CompressFormat;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
@@ -25,19 +26,14 @@ import android.util.Xml;
|
|||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
/** {@hide} */
|
/** {@hide} */
|
||||||
public class XmlUtils {
|
public class XmlUtils {
|
||||||
private static final String STRING_ARRAY_SEPARATOR = ":";
|
private static final String STRING_ARRAY_SEPARATOR = ":";
|
||||||
@@ -1396,9 +1392,9 @@ public class XmlUtils {
|
|||||||
} else if (tagName.equals("long")) {
|
} else if (tagName.equals("long")) {
|
||||||
return Long.valueOf(parser.getAttributeValue(null, "value"));
|
return Long.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("float")) {
|
} else if (tagName.equals("float")) {
|
||||||
return new Float(parser.getAttributeValue(null, "value"));
|
return Float.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("double")) {
|
} else if (tagName.equals("double")) {
|
||||||
return new Double(parser.getAttributeValue(null, "value"));
|
return Double.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else if (tagName.equals("boolean")) {
|
} else if (tagName.equals("boolean")) {
|
||||||
return Boolean.valueOf(parser.getAttributeValue(null, "value"));
|
return Boolean.valueOf(parser.getAttributeValue(null, "value"));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ package dalvik.system;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import xyz.nulldev.androidcompat.pm.PackageController;
|
import xyz.nulldev.androidcompat.pm.PackageController;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -33,7 +33,7 @@ import java.util.Enumeration;
|
|||||||
* {@link ClassLoader} implementations.
|
* {@link ClassLoader} implementations.
|
||||||
*/
|
*/
|
||||||
public class BaseDexClassLoader extends ClassLoader {
|
public class BaseDexClassLoader extends ClassLoader {
|
||||||
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
|
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
|
||||||
|
|
||||||
private final URLClassLoader realClassloader;
|
private final URLClassLoader realClassloader;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
@file:Suppress("UNUSED")
|
||||||
|
|
||||||
|
package kotlinx.coroutines.android
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Delay
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.DisposableHandle
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.InternalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.MainCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.NonDisposableHandle
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.internal.MainDispatcherFactory
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches execution onto Android [Handler].
|
||||||
|
*
|
||||||
|
* This class provides type-safety and a point for future extensions.
|
||||||
|
*/
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
public sealed class HandlerDispatcher :
|
||||||
|
MainCoroutineDispatcher(),
|
||||||
|
Delay {
|
||||||
|
/**
|
||||||
|
* Returns dispatcher that executes coroutines immediately when it is already in the right context
|
||||||
|
* (current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch].
|
||||||
|
* This dispatcher does not use [Handler.post] when current looper is the same as looper of the handler.
|
||||||
|
*
|
||||||
|
* Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined].
|
||||||
|
* The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation.
|
||||||
|
*
|
||||||
|
* Example of usage:
|
||||||
|
* ```
|
||||||
|
* suspend fun updateUiElement(val text: String) {
|
||||||
|
* /*
|
||||||
|
* * If it is known that updateUiElement can be invoked both from the Main thread and from other threads,
|
||||||
|
* * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch.
|
||||||
|
* *
|
||||||
|
* * In that case, when `updateUiElement` is invoked from the Main thread, `uiElement.text` will be
|
||||||
|
* * invoked immediately without any dispatching, otherwise, the `Dispatchers.Main` dispatch cycle via
|
||||||
|
* * `Handler.post` will be triggered.
|
||||||
|
* */
|
||||||
|
* withContext(Dispatchers.Main.immediate) {
|
||||||
|
* uiElement.text = text
|
||||||
|
* }
|
||||||
|
* // Do context-independent logic such as logging
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public abstract override val immediate: HandlerDispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
internal class AndroidDispatcherFactory : MainDispatcherFactory {
|
||||||
|
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
|
||||||
|
val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
|
||||||
|
return HandlerContext(mainLooper.asHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
|
||||||
|
|
||||||
|
override val loadPriority: Int
|
||||||
|
get() = Int.MAX_VALUE / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an arbitrary [Handler] as an implementation of [CoroutineDispatcher]
|
||||||
|
* with an optional [name] for nicer debugging
|
||||||
|
*
|
||||||
|
* ## Rejected execution
|
||||||
|
*
|
||||||
|
* If the underlying handler is closed and its message-scheduling methods start to return `false` on
|
||||||
|
* an attempt to submit a continuation task to the resulting dispatcher,
|
||||||
|
* then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the
|
||||||
|
* [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete.
|
||||||
|
*/
|
||||||
|
@JvmName("from") // this is for a nice Java API, see issue #255
|
||||||
|
@JvmOverloads
|
||||||
|
public fun Handler.asCoroutineDispatcher(name: String? = null): HandlerDispatcher =
|
||||||
|
HandlerContext(this, name)
|
||||||
|
|
||||||
|
private const val MAX_DELAY = Long.MAX_VALUE / 2 // cannot delay for too long on Android
|
||||||
|
|
||||||
|
internal fun Looper.asHandler(): Handler = Handler(this)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
|
||||||
|
internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler()) }.getOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler].
|
||||||
|
*/
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
internal class HandlerContext private constructor(
|
||||||
|
private val handler: Handler,
|
||||||
|
private val name: String?,
|
||||||
|
private val invokeImmediately: Boolean,
|
||||||
|
) : HandlerDispatcher(),
|
||||||
|
Delay {
|
||||||
|
/**
|
||||||
|
* Creates [CoroutineDispatcher] for the given Android [handler].
|
||||||
|
*
|
||||||
|
* @param handler a handler.
|
||||||
|
* @param name an optional name for debugging.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
handler: Handler,
|
||||||
|
name: String? = null,
|
||||||
|
) : this(handler, name, false)
|
||||||
|
|
||||||
|
override val immediate: HandlerContext =
|
||||||
|
if (invokeImmediately) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
HandlerContext(handler, name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDispatchNeeded(context: CoroutineContext): Boolean = !invokeImmediately || Looper.myLooper() != handler.looper
|
||||||
|
|
||||||
|
override fun dispatch(
|
||||||
|
context: CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) {
|
||||||
|
if (!handler.post(block)) {
|
||||||
|
cancelOnRejection(context, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
override fun scheduleResumeAfterDelay(
|
||||||
|
timeMillis: Long,
|
||||||
|
continuation: CancellableContinuation<Unit>,
|
||||||
|
) {
|
||||||
|
val block =
|
||||||
|
Runnable {
|
||||||
|
with(continuation) { resumeUndispatched(Unit) }
|
||||||
|
}
|
||||||
|
if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
|
||||||
|
continuation.invokeOnCancellation { handler.removeCallbacks(block) }
|
||||||
|
} else {
|
||||||
|
cancelOnRejection(continuation.context, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invokeOnTimeout(
|
||||||
|
timeMillis: Long,
|
||||||
|
block: Runnable,
|
||||||
|
context: CoroutineContext,
|
||||||
|
): DisposableHandle {
|
||||||
|
if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
|
||||||
|
return DisposableHandle { handler.removeCallbacks(block) }
|
||||||
|
}
|
||||||
|
cancelOnRejection(context, block)
|
||||||
|
return NonDisposableHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelOnRejection(
|
||||||
|
context: CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) {
|
||||||
|
context.cancel(CancellationException("The task was rejected, the handler underlying the dispatcher '${toString()}' was closed"))
|
||||||
|
Dispatchers.IO.dispatch(context, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
toStringInternalImpl() ?: run {
|
||||||
|
val str = name ?: handler.toString()
|
||||||
|
if (invokeImmediately) "$str.immediate" else str
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean =
|
||||||
|
other is HandlerContext && other.handler === handler && other.invokeImmediately == invokeImmediately
|
||||||
|
|
||||||
|
// inlining `Boolean.hashCode()` for Android compatibility, as requested by Animal Sniffer
|
||||||
|
override fun hashCode(): Int = System.identityHashCode(handler) xor if (invokeImmediately) 1231 else 1237
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package libcore.net;
|
package libcore.net;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -249,10 +251,12 @@ public final class MimeUtils {
|
|||||||
add("audio/x-scpls", "pls");
|
add("audio/x-scpls", "pls");
|
||||||
add("audio/x-sd2", "sd2");
|
add("audio/x-sd2", "sd2");
|
||||||
add("audio/x-wav", "wav");
|
add("audio/x-wav", "wav");
|
||||||
|
add("image/avif", "avif");
|
||||||
// image/bmp isn't IANA, so image/x-ms-bmp should come first.
|
// image/bmp isn't IANA, so image/x-ms-bmp should come first.
|
||||||
add("image/x-ms-bmp", "bmp");
|
add("image/x-ms-bmp", "bmp");
|
||||||
add("image/bmp", "bmp");
|
add("image/bmp", "bmp");
|
||||||
add("image/gif", "gif");
|
add("image/gif", "gif");
|
||||||
|
add("image/heif", "heif");
|
||||||
// image/ico isn't IANA, so image/x-icon should come first.
|
// image/ico isn't IANA, so image/x-icon should come first.
|
||||||
add("image/x-icon", "ico");
|
add("image/x-icon", "ico");
|
||||||
add("image/ico", "cur");
|
add("image/ico", "cur");
|
||||||
@@ -262,6 +266,7 @@ public final class MimeUtils {
|
|||||||
add("image/jpeg", "jpg");
|
add("image/jpeg", "jpg");
|
||||||
add("image/jpeg", "jpeg");
|
add("image/jpeg", "jpeg");
|
||||||
add("image/jpeg", "jpe");
|
add("image/jpeg", "jpe");
|
||||||
|
add("image/jxl", "jxl");
|
||||||
add("image/pcx", "pcx");
|
add("image/pcx", "pcx");
|
||||||
add("image/png", "png");
|
add("image/png", "png");
|
||||||
add("image/svg+xml", "svg");
|
add("image/svg+xml", "svg");
|
||||||
@@ -438,6 +443,7 @@ public final class MimeUtils {
|
|||||||
* @return The extension has been registered for
|
* @return The extension has been registered for
|
||||||
* the given case insensitive MIME type or null if there is none.
|
* the given case insensitive MIME type or null if there is none.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public static String guessExtensionFromMimeType(String mimeType) {
|
public static String guessExtensionFromMimeType(String mimeType) {
|
||||||
if (mimeType == null || mimeType.isEmpty()) {
|
if (mimeType == null || mimeType.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package xyz.nulldev.androidcompat
|
package xyz.nulldev.androidcompat
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import org.kodein.di.DI
|
import org.koin.mp.KoinPlatformTools
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
||||||
|
|
||||||
class AndroidCompat {
|
class AndroidCompat {
|
||||||
|
val context: CustomContext by KoinPlatformTools.defaultContext().get().inject()
|
||||||
val context: CustomContext by DI.global.instance()
|
|
||||||
|
|
||||||
fun startApp(application: Application) {
|
fun startApp(application: Application) {
|
||||||
application.attach(context)
|
application.attach(context)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package xyz.nulldev.androidcompat
|
package xyz.nulldev.androidcompat
|
||||||
|
|
||||||
import org.kodein.di.DI
|
import android.webkit.WebView
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
|
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
|
||||||
import xyz.nulldev.androidcompat.config.FilesConfigModule
|
import xyz.nulldev.androidcompat.config.FilesConfigModule
|
||||||
import xyz.nulldev.androidcompat.config.SystemConfigModule
|
import xyz.nulldev.androidcompat.config.SystemConfigModule
|
||||||
|
import xyz.nulldev.androidcompat.webkit.KcefWebViewProvider
|
||||||
import xyz.nulldev.ts.config.GlobalConfigManager
|
import xyz.nulldev.ts.config.GlobalConfigManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,16 +12,19 @@ import xyz.nulldev.ts.config.GlobalConfigManager
|
|||||||
*/
|
*/
|
||||||
class AndroidCompatInitializer {
|
class AndroidCompatInitializer {
|
||||||
fun init() {
|
fun init() {
|
||||||
DI.global.addImport(AndroidCompatModule().create())
|
// Register config modules
|
||||||
|
|
||||||
//Register config modules
|
|
||||||
GlobalConfigManager.registerModules(
|
GlobalConfigManager.registerModules(
|
||||||
FilesConfigModule.register(GlobalConfigManager.config),
|
FilesConfigModule.register(GlobalConfigManager.config),
|
||||||
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
|
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
|
||||||
SystemConfigModule.register(GlobalConfigManager.config)
|
SystemConfigModule.register(GlobalConfigManager.config),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WebView.setProviderFactory({ view: WebView -> KcefWebViewProvider(view) })
|
||||||
|
|
||||||
// Set some properties extensions use
|
// Set some properties extensions use
|
||||||
System.setProperty("http.agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
|
System.setProperty(
|
||||||
|
"http.agent",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package xyz.nulldev.androidcompat
|
package xyz.nulldev.androidcompat
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.kodein.di.DI
|
import org.koin.core.module.Module
|
||||||
import org.kodein.di.bind
|
import org.koin.dsl.module
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import org.kodein.di.singleton
|
|
||||||
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
||||||
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
|
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
|
||||||
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
|
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
|
||||||
@@ -17,23 +14,19 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
|
|||||||
* AndroidCompatModule
|
* AndroidCompatModule
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AndroidCompatModule {
|
fun androidCompatModule(): Module =
|
||||||
fun create() = DI.Module("AndroidCompat") {
|
module {
|
||||||
bind<AndroidFiles>() with singleton { AndroidFiles() }
|
single { AndroidFiles() }
|
||||||
|
|
||||||
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
|
single { ApplicationInfoImpl(get()) }
|
||||||
|
|
||||||
bind<ServiceSupport>() with singleton { ServiceSupport() }
|
single { ServiceSupport() }
|
||||||
|
|
||||||
bind<FakePackageManager>() with singleton { FakePackageManager() }
|
single { FakePackageManager() }
|
||||||
|
|
||||||
bind<PackageController>() with singleton { PackageController() }
|
single { PackageController() }
|
||||||
|
|
||||||
//Context
|
single { CustomContext() }
|
||||||
bind<CustomContext>() with singleton { CustomContext() }
|
|
||||||
bind<Context>() with singleton {
|
single<Context> { get<CustomContext>() }
|
||||||
val context: Context by DI.global.instance<CustomContext>()
|
|
||||||
context
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package xyz.nulldev.androidcompat
|
||||||
|
|
||||||
|
fun interface CallableArgument<A, R> {
|
||||||
|
fun call(arg: A): R
|
||||||
|
}
|
||||||
+13
-27
@@ -32,15 +32,14 @@ import android.os.*;
|
|||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayAdjustments;
|
import android.view.DisplayAdjustments;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.koin.core.Koin;
|
||||||
import org.kodein.di.*;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
||||||
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
import xyz.nulldev.androidcompat.io.AndroidFiles;
|
||||||
import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences;
|
import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences;
|
||||||
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
import xyz.nulldev.androidcompat.service.ServiceSupport;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -51,26 +50,25 @@ import java.util.Map;
|
|||||||
* Custom context implementation.
|
* Custom context implementation.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class CustomContext extends Context implements DIAware {
|
public class CustomContext extends Context {
|
||||||
private final DI kodein;
|
private final Koin koin;
|
||||||
public CustomContext() {
|
public CustomContext() {
|
||||||
this(KodeinGlobalHelper.kodein());
|
this(KoinGlobalHelper.koin());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomContext(DI kodein) {
|
public CustomContext(Koin koin) {
|
||||||
this.kodein = kodein;
|
this.koin = koin;
|
||||||
|
|
||||||
//Init configs
|
//Init configs
|
||||||
androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class, getDi());
|
androidFiles = KoinGlobalHelper.instance(AndroidFiles.class, getDi());
|
||||||
applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
|
applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
|
||||||
serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class, getDi());
|
serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class, getDi());
|
||||||
fakePackageManager = KodeinGlobalHelper.instance(FakePackageManager.class, getDi());
|
fakePackageManager = KoinGlobalHelper.instance(FakePackageManager.class, getDi());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
public Koin getDi() {
|
||||||
public DI getDi() {
|
return koin;
|
||||||
return kodein;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AndroidFiles androidFiles;
|
private AndroidFiles androidFiles;
|
||||||
@@ -719,17 +717,5 @@ public class CustomContext extends Context implements DIAware {
|
|||||||
public boolean isCredentialProtectedStorage() {
|
public boolean isCredentialProtectedStorage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public DIContext<?> getDiContext() {
|
|
||||||
return getDi().getDiContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public DITrigger getDiTrigger() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,14 +16,14 @@ import android.os.UserHandle;
|
|||||||
import kotlin.NotImplementedError;
|
import kotlin.NotImplementedError;
|
||||||
import xyz.nulldev.androidcompat.pm.InstalledPackage;
|
import xyz.nulldev.androidcompat.pm.InstalledPackage;
|
||||||
import xyz.nulldev.androidcompat.pm.PackageController;
|
import xyz.nulldev.androidcompat.pm.PackageController;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FakePackageManager extends PackageManager {
|
public class FakePackageManager extends PackageManager {
|
||||||
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
|
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
|
public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
|
||||||
|
|||||||
+6
-5
@@ -8,12 +8,13 @@ import xyz.nulldev.ts.config.ConfigModule
|
|||||||
* Application info config.
|
* Application info config.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
|
class ApplicationInfoConfigModule(
|
||||||
val packageName: String by config
|
getConfig: () -> Config,
|
||||||
val debug: Boolean by config
|
) : ConfigModule(getConfig) {
|
||||||
|
val packageName: String by getConfig()
|
||||||
|
val debug: Boolean by getConfig()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") }
|
||||||
= ApplicationInfoConfigModule(config.getConfig("android.app"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,27 +8,28 @@ import xyz.nulldev.ts.config.ConfigModule
|
|||||||
* Files configuration modules. Specifies where to store the Android files.
|
* Files configuration modules. Specifies where to store the Android files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class FilesConfigModule(config: Config) : ConfigModule(config) {
|
class FilesConfigModule(
|
||||||
val dataDir:String by config
|
getConfig: () -> Config,
|
||||||
val filesDir:String by config
|
) : ConfigModule(getConfig) {
|
||||||
val noBackupFilesDir:String by config
|
val dataDir: String by getConfig()
|
||||||
val externalFilesDirs: MutableList<String> by config
|
val filesDir: String by getConfig()
|
||||||
val obbDirs: MutableList<String> by config
|
val noBackupFilesDir: String by getConfig()
|
||||||
val cacheDir:String by config
|
val externalFilesDirs: MutableList<String> by getConfig()
|
||||||
val codeCacheDir:String by config
|
val obbDirs: MutableList<String> by getConfig()
|
||||||
val externalCacheDirs: MutableList<String> by config
|
val cacheDir: String by getConfig()
|
||||||
val externalMediaDirs: MutableList<String> by config
|
val codeCacheDir: String by getConfig()
|
||||||
val rootDir:String by config
|
val externalCacheDirs: MutableList<String> by getConfig()
|
||||||
val externalStorageDir:String by config
|
val externalMediaDirs: MutableList<String> by getConfig()
|
||||||
val downloadCacheDir:String by config
|
val rootDir: String by getConfig()
|
||||||
val databasesDir:String by config
|
val externalStorageDir: String by getConfig()
|
||||||
|
val downloadCacheDir: String by getConfig()
|
||||||
|
val databasesDir: String by getConfig()
|
||||||
|
|
||||||
val prefsDir:String by config
|
val prefsDir: String by getConfig()
|
||||||
|
|
||||||
val packageDir:String by config
|
val packageDir: String by getConfig()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") }
|
||||||
= FilesConfigModule(config.getConfig("android.files"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+15
-10
@@ -1,22 +1,27 @@
|
|||||||
package xyz.nulldev.androidcompat.config
|
package xyz.nulldev.androidcompat.config
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import xyz.nulldev.ts.config.ConfigModule
|
|
||||||
import io.github.config4k.getValue
|
import io.github.config4k.getValue
|
||||||
|
import xyz.nulldev.ts.config.ConfigModule
|
||||||
|
|
||||||
class SystemConfigModule(val config: Config) : ConfigModule(config) {
|
class SystemConfigModule(
|
||||||
val isDebuggable: Boolean by config
|
val getConfig: () -> Config,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
|
val isDebuggable: Boolean by getConfig()
|
||||||
|
|
||||||
val propertyPrefix = "properties."
|
val propertyPrefix = "properties."
|
||||||
|
|
||||||
fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!!
|
fun getStringProperty(property: String) = getConfig().getString("$propertyPrefix$property")!!
|
||||||
fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property")
|
|
||||||
fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property")
|
fun getIntProperty(property: String) = getConfig().getInt("$propertyPrefix$property")
|
||||||
fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property")
|
|
||||||
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
|
fun getLongProperty(property: String) = getConfig().getLong("$propertyPrefix$property")
|
||||||
|
|
||||||
|
fun getBooleanProperty(property: String) = getConfig().getBoolean("$propertyPrefix$property")
|
||||||
|
|
||||||
|
fun hasProperty(property: String) = getConfig().hasPath("$propertyPrefix$property")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(config: Config)
|
fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") }
|
||||||
= SystemConfigModule(config.getConfig("android.system"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,12 @@
|
|||||||
package xyz.nulldev.androidcompat.info
|
package xyz.nulldev.androidcompat.info
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.DIAware
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
|
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
|
||||||
import xyz.nulldev.ts.config.ConfigManager
|
import xyz.nulldev.ts.config.ConfigManager
|
||||||
|
|
||||||
class ApplicationInfoImpl(override val di: DI = DI.global) : ApplicationInfo(), DIAware {
|
class ApplicationInfoImpl(
|
||||||
val configManager: ConfigManager by di.instance()
|
private val configManager: ConfigManager,
|
||||||
|
) : ApplicationInfo() {
|
||||||
val appInfoConfig: ApplicationInfoConfigModule
|
val appInfoConfig: ApplicationInfoConfigModule
|
||||||
get() = configManager.module()
|
get() = configManager.module()
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import java.io.File
|
|||||||
/**
|
/**
|
||||||
* Android file constants.
|
* Android file constants.
|
||||||
*/
|
*/
|
||||||
class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
|
class AndroidFiles(
|
||||||
|
val configManager: ConfigManager = GlobalConfigManager,
|
||||||
|
) {
|
||||||
val filesConfig: FilesConfigModule
|
val filesConfig: FilesConfigModule
|
||||||
get() = configManager.module()
|
get() = configManager.module()
|
||||||
|
|
||||||
@@ -30,9 +32,8 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
|
|||||||
|
|
||||||
val packagesDir: File get() = registerFile(filesConfig.packageDir)
|
val packagesDir: File get() = registerFile(filesConfig.packageDir)
|
||||||
|
|
||||||
fun registerFile(file: String): File {
|
fun registerFile(file: String): File =
|
||||||
return File(file).apply {
|
File(file).apply {
|
||||||
mkdirs()
|
mkdirs()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+191
-82
@@ -9,124 +9,211 @@ package xyz.nulldev.androidcompat.io.sharedprefs
|
|||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.russhwolf.settings.ExperimentalSettingsApi
|
import com.russhwolf.settings.ExperimentalSettingsApi
|
||||||
import com.russhwolf.settings.ExperimentalSettingsImplementation
|
import com.russhwolf.settings.PropertiesSettings
|
||||||
import com.russhwolf.settings.JvmPreferencesSettings
|
import com.russhwolf.settings.Settings
|
||||||
import com.russhwolf.settings.serialization.decodeValue
|
import com.russhwolf.settings.serialization.decodeValue
|
||||||
|
import com.russhwolf.settings.serialization.decodeValueOrNull
|
||||||
import com.russhwolf.settings.serialization.encodeValue
|
import com.russhwolf.settings.serialization.encodeValue
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.builtins.SetSerializer
|
import kotlinx.serialization.builtins.SetSerializer
|
||||||
import kotlinx.serialization.builtins.nullable
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import java.util.prefs.PreferenceChangeListener
|
import xyz.nulldev.androidcompat.util.SafePath
|
||||||
import java.util.prefs.Preferences
|
import xyz.nulldev.ts.config.ApplicationRootDir
|
||||||
|
import java.util.Properties
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.createParentDirectories
|
||||||
|
import kotlin.io.path.deleteIfExists
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
@OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
||||||
class JavaSharedPreferences(key: String) : SharedPreferences {
|
class JavaSharedPreferences(
|
||||||
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
|
key: String,
|
||||||
private val preferences = JvmPreferencesSettings(javaPreferences)
|
) : SharedPreferences {
|
||||||
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
|
companion object {
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
|
|
||||||
override fun getAll(): MutableMap<String, *> {
|
|
||||||
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getString(key: String, defValue: String?): String? {
|
private val file =
|
||||||
return if (defValue != null) {
|
Path(
|
||||||
|
ApplicationRootDir,
|
||||||
|
"settings",
|
||||||
|
"${SafePath.buildValidFilename(key)}.xml",
|
||||||
|
)
|
||||||
|
private val properties =
|
||||||
|
Properties().also { properties ->
|
||||||
|
try {
|
||||||
|
if (file.exists()) {
|
||||||
|
file.inputStream().use { properties.loadFromXML(it) }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Error loading settings from $key" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val preferences =
|
||||||
|
PropertiesSettings(
|
||||||
|
properties,
|
||||||
|
onModify = { properties ->
|
||||||
|
try {
|
||||||
|
if (properties.isEmpty) {
|
||||||
|
file.deleteIfExists()
|
||||||
|
} else {
|
||||||
|
file.createParentDirectories()
|
||||||
|
file.outputStream().use {
|
||||||
|
properties.storeToXML(it, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Error saving settings in $key" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
|
||||||
|
|
||||||
|
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
|
||||||
|
override fun getAll(): MutableMap<String, *> = preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
|
||||||
|
|
||||||
|
override fun getString(
|
||||||
|
key: String,
|
||||||
|
defValue: String?,
|
||||||
|
): String? =
|
||||||
|
if (defValue != null) {
|
||||||
preferences.getString(key, defValue)
|
preferences.getString(key, defValue)
|
||||||
} else {
|
} else {
|
||||||
preferences.getStringOrNull(key)
|
preferences.getStringOrNull(key)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
|
override fun getStringSet(
|
||||||
|
key: String,
|
||||||
|
defValues: Set<String>?,
|
||||||
|
): Set<String>? {
|
||||||
try {
|
try {
|
||||||
return if (defValues != null) {
|
return if (defValues != null) {
|
||||||
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, defValues)
|
preferences.decodeValue(SetSerializer(String.serializer()), key, defValues)
|
||||||
} else {
|
} else {
|
||||||
preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, null)
|
preferences.decodeValueOrNull(SetSerializer(String.serializer()), key)
|
||||||
}?.toMutableSet()
|
}
|
||||||
} catch (e: SerializationException) {
|
} catch (_: SerializationException) {
|
||||||
throw ClassCastException("$key was not a StringSet")
|
throw ClassCastException("$key was not a StringSet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInt(key: String, defValue: Int): Int {
|
override fun getInt(
|
||||||
return preferences.getInt(key, defValue)
|
key: String,
|
||||||
}
|
defValue: Int,
|
||||||
|
): Int = preferences.getInt(key, defValue)
|
||||||
|
|
||||||
override fun getLong(key: String, defValue: Long): Long {
|
override fun getLong(
|
||||||
return preferences.getLong(key, defValue)
|
key: String,
|
||||||
}
|
defValue: Long,
|
||||||
|
): Long = preferences.getLong(key, defValue)
|
||||||
|
|
||||||
override fun getFloat(key: String, defValue: Float): Float {
|
override fun getFloat(
|
||||||
return preferences.getFloat(key, defValue)
|
key: String,
|
||||||
}
|
defValue: Float,
|
||||||
|
): Float = preferences.getFloat(key, defValue)
|
||||||
|
|
||||||
override fun getBoolean(key: String, defValue: Boolean): Boolean {
|
override fun getBoolean(
|
||||||
return preferences.getBoolean(key, defValue)
|
key: String,
|
||||||
}
|
defValue: Boolean,
|
||||||
|
): Boolean = preferences.getBoolean(key, defValue)
|
||||||
|
|
||||||
override fun contains(key: String): Boolean {
|
override fun contains(key: String): Boolean = key in preferences.keys
|
||||||
return key in preferences.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun edit(): SharedPreferences.Editor {
|
override fun edit(): SharedPreferences.Editor =
|
||||||
return Editor(preferences)
|
Editor(preferences) { key ->
|
||||||
}
|
listeners.forEach { (_, listener) ->
|
||||||
|
listener(key)
|
||||||
class Editor(private val preferences: JvmPreferencesSettings) : SharedPreferences.Editor {
|
|
||||||
val itemsToAdd = mutableMapOf<String, Any>()
|
|
||||||
|
|
||||||
override fun putString(key: String, value: String?): SharedPreferences.Editor {
|
|
||||||
if (value != null) {
|
|
||||||
itemsToAdd[key] = value
|
|
||||||
} else {
|
|
||||||
remove(key)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Editor(
|
||||||
|
private val preferences: Settings,
|
||||||
|
private val notify: (String) -> Unit,
|
||||||
|
) : SharedPreferences.Editor {
|
||||||
|
private val actions = mutableListOf<Action>()
|
||||||
|
|
||||||
|
private sealed class Action {
|
||||||
|
data class Add(
|
||||||
|
val key: String,
|
||||||
|
val value: Any,
|
||||||
|
) : Action()
|
||||||
|
|
||||||
|
data class Remove(
|
||||||
|
val key: String,
|
||||||
|
) : Action()
|
||||||
|
|
||||||
|
data object Clear : Action()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putString(
|
||||||
|
key: String,
|
||||||
|
value: String?,
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
actions +=
|
||||||
|
if (value != null) {
|
||||||
|
Action.Add(key, value)
|
||||||
|
} else {
|
||||||
|
Action.Remove(key)
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putStringSet(
|
override fun putStringSet(
|
||||||
key: String,
|
key: String,
|
||||||
values: MutableSet<String>?
|
values: MutableSet<String>?,
|
||||||
): SharedPreferences.Editor {
|
): SharedPreferences.Editor {
|
||||||
if (values != null) {
|
actions +=
|
||||||
itemsToAdd[key] = values
|
if (values != null) {
|
||||||
} else {
|
Action.Add(key, values)
|
||||||
remove(key)
|
} else {
|
||||||
}
|
Action.Remove(key)
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putInt(key: String, value: Int): SharedPreferences.Editor {
|
override fun putInt(
|
||||||
itemsToAdd[key] = value
|
key: String,
|
||||||
|
value: Int,
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
actions += Action.Add(key, value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putLong(key: String, value: Long): SharedPreferences.Editor {
|
override fun putLong(
|
||||||
itemsToAdd[key] = value
|
key: String,
|
||||||
|
value: Long,
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
actions += Action.Add(key, value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
|
override fun putFloat(
|
||||||
itemsToAdd[key] = value
|
key: String,
|
||||||
|
value: Float,
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
actions += Action.Add(key, value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
|
override fun putBoolean(
|
||||||
itemsToAdd[key] = value
|
key: String,
|
||||||
|
value: Boolean,
|
||||||
|
): SharedPreferences.Editor {
|
||||||
|
actions += Action.Add(key, value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(key: String): SharedPreferences.Editor {
|
override fun remove(key: String): SharedPreferences.Editor {
|
||||||
itemsToAdd.remove(key)
|
actions += Action.Remove(key)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clear(): SharedPreferences.Editor {
|
override fun clear(): SharedPreferences.Editor {
|
||||||
itemsToAdd.clear()
|
actions.add(Action.Clear)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,38 +227,60 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addToPreferences() {
|
private fun addToPreferences() {
|
||||||
itemsToAdd.forEach { (key, value) ->
|
actions.forEach {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
when (value) {
|
when (it) {
|
||||||
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set<String>)
|
is Action.Add -> {
|
||||||
is String -> preferences.putString(key, value)
|
when (val value = it.value) {
|
||||||
is Int -> preferences.putInt(key, value)
|
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>)
|
||||||
is Long -> preferences.putLong(key, value)
|
is String -> preferences.putString(it.key, value)
|
||||||
is Float -> preferences.putFloat(key, value)
|
is Int -> preferences.putInt(it.key, value)
|
||||||
is Double -> preferences.putDouble(key, value)
|
is Long -> preferences.putLong(it.key, value)
|
||||||
is Boolean -> preferences.putBoolean(key, value)
|
is Float -> preferences.putFloat(it.key, value)
|
||||||
|
is Double -> preferences.putDouble(it.key, value)
|
||||||
|
is Boolean -> preferences.putBoolean(it.key, value)
|
||||||
|
}
|
||||||
|
notify(it.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Action.Remove -> {
|
||||||
|
preferences.remove(it.key)
|
||||||
|
/*
|
||||||
|
Set<String> are stored like
|
||||||
|
key.0 = value1
|
||||||
|
key.1 = value2
|
||||||
|
key.size = 2
|
||||||
|
*/
|
||||||
|
preferences.keys.forEach { key ->
|
||||||
|
if (key.startsWith(it.key + ".")) {
|
||||||
|
preferences.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(it.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
Action.Clear -> {
|
||||||
|
preferences.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||||
val javaListener = PreferenceChangeListener {
|
val javaListener: (String) -> Unit = {
|
||||||
listener.onSharedPreferenceChanged(this, it.key)
|
listener.onSharedPreferenceChanged(this, it)
|
||||||
}
|
}
|
||||||
listeners[listener] = javaListener
|
listeners[listener] = javaListener
|
||||||
javaPreferences.addPreferenceChangeListener(javaListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||||
val registeredListener = listeners.remove(listener)
|
listeners.remove(listener)
|
||||||
if (registeredListener != null) {
|
|
||||||
javaPreferences.removePreferenceChangeListener(registeredListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAll(): Boolean {
|
fun deleteAll(): Boolean {
|
||||||
javaPreferences.removeNode()
|
preferences.clear()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,44 +14,58 @@ import java.io.File
|
|||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
|
data class InstalledPackage(
|
||||||
|
val root: File,
|
||||||
data class InstalledPackage(val root: File) {
|
) {
|
||||||
val apk = File(root, "package.apk")
|
val apk = File(root, "package.apk")
|
||||||
val jar = File(root, "translated.jar")
|
val jar = File(root, "translated.jar")
|
||||||
val icon = File(root, "icon.png")
|
val icon = File(root, "icon.png")
|
||||||
|
|
||||||
val info: PackageInfo
|
val info: PackageInfo
|
||||||
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
|
get() =
|
||||||
val parsed = ApkFile(apk)
|
ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
|
||||||
val dbFactory = DocumentBuilderFactory.newInstance()
|
val parsed = ApkFile(apk)
|
||||||
val dBuilder = dbFactory.newDocumentBuilder()
|
val dbFactory = DocumentBuilderFactory.newInstance()
|
||||||
val doc = parsed.manifestXml.byteInputStream().use {
|
val dBuilder = dbFactory.newDocumentBuilder()
|
||||||
dBuilder.parse(it)
|
val doc =
|
||||||
|
parsed.manifestXml.byteInputStream().use {
|
||||||
|
dBuilder.parse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.applicationInfo.metaData =
|
||||||
|
Bundle().apply {
|
||||||
|
val appTag = doc.getElementsByTagName("application").item(0)
|
||||||
|
|
||||||
|
appTag
|
||||||
|
?.childNodes
|
||||||
|
?.toList()
|
||||||
|
?.filter {
|
||||||
|
it.nodeType == Node.ELEMENT_NODE
|
||||||
|
}?.map {
|
||||||
|
it as Element
|
||||||
|
}?.filter {
|
||||||
|
it.tagName == "meta-data"
|
||||||
|
}?.map {
|
||||||
|
putString(
|
||||||
|
it.attributes.getNamedItem("android:name").nodeValue,
|
||||||
|
it.attributes.getNamedItem("android:value").nodeValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it.signatures =
|
||||||
|
(
|
||||||
|
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||||
|
// + parsed.apkV2Singers.flatMap { it.certificateMetas }
|
||||||
|
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||||
|
.map { Signature(it.data) }
|
||||||
|
.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
it.applicationInfo.metaData = Bundle().apply {
|
|
||||||
val appTag = doc.getElementsByTagName("application").item(0)
|
|
||||||
|
|
||||||
appTag?.childNodes?.toList()?.filter {
|
|
||||||
it.nodeType == Node.ELEMENT_NODE
|
|
||||||
}?.map {
|
|
||||||
it as Element
|
|
||||||
}?.filter {
|
|
||||||
it.tagName == "meta-data"
|
|
||||||
}?.map {
|
|
||||||
putString(it.attributes.getNamedItem("android:name").nodeValue,
|
|
||||||
it.attributes.getNamedItem("android:value").nodeValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it.signatures = (parsed.apkSingers.flatMap { it.certificateMetas }
|
|
||||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
|
||||||
.map { Signature(it.data) }.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun verify(): Boolean {
|
fun verify(): Boolean {
|
||||||
val res = ApkVerifier.Builder(apk)
|
val res =
|
||||||
|
ApkVerifier
|
||||||
|
.Builder(apk)
|
||||||
.build()
|
.build()
|
||||||
.verify()
|
.verify()
|
||||||
|
|
||||||
@@ -62,14 +76,18 @@ data class InstalledPackage(val root: File) {
|
|||||||
try {
|
try {
|
||||||
val icons = ApkFile(apk).allIcons
|
val icons = ApkFile(apk).allIcons
|
||||||
|
|
||||||
val read = icons.filter { it.isFile }.map {
|
val read =
|
||||||
it.data.inputStream().use {
|
icons
|
||||||
ImageIO.read(it)
|
.filter { it.isFile }
|
||||||
}
|
.map {
|
||||||
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
|
it.data.inputStream().use {
|
||||||
|
ImageIO.read(it)
|
||||||
|
}
|
||||||
|
}.sortedByDescending { it.width * it.height }
|
||||||
|
.firstOrNull() ?: return
|
||||||
|
|
||||||
ImageIO.write(read, "png", icon)
|
ImageIO.write(read, "png", icon)
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
icon.delete()
|
icon.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +95,7 @@ data class InstalledPackage(val root: File) {
|
|||||||
fun writeJar() {
|
fun writeJar() {
|
||||||
try {
|
try {
|
||||||
Dex2jar.from(apk).to(jar.toPath())
|
Dex2jar.from(apk).to(jar.toPath())
|
||||||
} catch(e: Exception) {
|
} catch (e: Exception) {
|
||||||
jar.delete()
|
jar.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,8 +104,9 @@ data class InstalledPackage(val root: File) {
|
|||||||
fun NodeList.toList(): List<Node> {
|
fun NodeList.toList(): List<Node> {
|
||||||
val out = mutableListOf<Node>()
|
val out = mutableListOf<Node>()
|
||||||
|
|
||||||
for (i in 0 until length)
|
for (i in 0 until length) {
|
||||||
out += item(i)
|
out += item(i)
|
||||||
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package xyz.nulldev.androidcompat.pm
|
package xyz.nulldev.androidcompat.pm
|
||||||
|
|
||||||
import net.dongliu.apk.parser.ApkParsers
|
import net.dongliu.apk.parser.ApkParsers
|
||||||
import org.kodein.di.DI
|
import org.koin.mp.KoinPlatformTools
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import xyz.nulldev.androidcompat.io.AndroidFiles
|
import xyz.nulldev.androidcompat.io.AndroidFiles
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class PackageController {
|
class PackageController {
|
||||||
private val androidFiles by DI.global.instance<AndroidFiles>()
|
private val androidFiles: AndroidFiles by KoinPlatformTools.defaultContext().get().inject()
|
||||||
private val uninstallListeners = mutableListOf<(String) -> Unit>()
|
private val uninstallListeners = mutableListOf<(String) -> Unit>()
|
||||||
|
|
||||||
fun registerUninstallListener(listener: (String) -> Unit) {
|
fun registerUninstallListener(listener: (String) -> Unit) {
|
||||||
@@ -25,7 +23,10 @@ class PackageController {
|
|||||||
return File(androidFiles.packagesDir, pn)
|
return File(androidFiles.packagesDir, pn)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun installPackage(apk: File, allowReinstall: Boolean) {
|
fun installPackage(
|
||||||
|
apk: File,
|
||||||
|
allowReinstall: Boolean,
|
||||||
|
) {
|
||||||
val root = findRoot(apk)
|
val root = findRoot(apk)
|
||||||
|
|
||||||
if (root.exists()) {
|
if (root.exists()) {
|
||||||
@@ -48,22 +49,24 @@ class PackageController {
|
|||||||
if (!installed.jar.exists()) {
|
if (!installed.jar.exists()) {
|
||||||
throw IllegalStateException("Failed to translate APK dex!")
|
throw IllegalStateException("Failed to translate APK dex!")
|
||||||
}
|
}
|
||||||
} catch(t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
root.deleteRecursively()
|
root.deleteRecursively()
|
||||||
throw t
|
throw t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun listInstalled(): List<InstalledPackage> {
|
fun listInstalled(): List<InstalledPackage> =
|
||||||
return androidFiles.packagesDir.listFiles().orEmpty().filter {
|
androidFiles.packagesDir
|
||||||
it.isDirectory
|
.listFiles()
|
||||||
}.map {
|
.orEmpty()
|
||||||
InstalledPackage(it)
|
.filter {
|
||||||
}
|
it.isDirectory
|
||||||
}
|
}.map {
|
||||||
|
InstalledPackage(it)
|
||||||
|
}
|
||||||
|
|
||||||
fun deletePackage(pack: InstalledPackage) {
|
fun deletePackage(pack: InstalledPackage) {
|
||||||
if(!pack.root.exists()) error("Package was never installed!")
|
if (!pack.root.exists()) error("Package was never installed!")
|
||||||
|
|
||||||
val packageName = pack.info.packageName
|
val packageName = pack.info.packageName
|
||||||
pack.root.deleteRecursively()
|
pack.root.deleteRecursively()
|
||||||
@@ -74,10 +77,11 @@ class PackageController {
|
|||||||
|
|
||||||
fun findPackage(packageName: String): InstalledPackage? {
|
fun findPackage(packageName: String): InstalledPackage? {
|
||||||
val file = File(androidFiles.packagesDir, packageName)
|
val file = File(androidFiles.packagesDir, packageName)
|
||||||
return if(file.exists())
|
return if (file.exists()) {
|
||||||
InstalledPackage(file)
|
InstalledPackage(file)
|
||||||
else
|
} else {
|
||||||
null
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findJarFromApk(apkFile: File): File? {
|
fun findJarFromApk(apkFile: File): File? {
|
||||||
|
|||||||
@@ -6,22 +6,24 @@ import android.content.pm.PackageInfo
|
|||||||
import net.dongliu.apk.parser.bean.ApkMeta
|
import net.dongliu.apk.parser.bean.ApkMeta
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
fun ApkMeta.toPackageInfo(apk: File): PackageInfo =
|
||||||
return PackageInfo().also {
|
PackageInfo().also {
|
||||||
it.packageName = packageName
|
it.packageName = packageName
|
||||||
it.versionCode = versionCode.toInt()
|
it.versionCode = versionCode.toInt()
|
||||||
it.versionName = versionName
|
it.versionName = versionName
|
||||||
|
|
||||||
it.reqFeatures = usesFeatures.map {
|
it.reqFeatures =
|
||||||
FeatureInfo().apply {
|
usesFeatures
|
||||||
name = it.name
|
.map {
|
||||||
}
|
FeatureInfo().apply {
|
||||||
}.toTypedArray()
|
name = it.name
|
||||||
|
}
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
it.applicationInfo = ApplicationInfo().apply {
|
it.applicationInfo =
|
||||||
packageName = it.packageName
|
ApplicationInfo().apply {
|
||||||
nonLocalizedLabel = label
|
packageName = it.packageName
|
||||||
sourceDir = apk.absolutePath
|
nonLocalizedLabel = label
|
||||||
}
|
sourceDir = apk.absolutePath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user