Compare commits
601 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06e93fd7bd | |||
| 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 |
@@ -9,3 +9,6 @@ ij_kotlin_name_count_to_use_star_import_for_members=2147483647
|
|||||||
ktlint_standard_discouraged-comment-location=disabled
|
ktlint_standard_discouraged-comment-location=disabled
|
||||||
ktlint_standard_if-else-wrapping=disabled
|
ktlint_standard_if-else-wrapping=disabled
|
||||||
ktlint_standard_no-consecutive-comments=disabled
|
ktlint_standard_no-consecutive-comments=disabled
|
||||||
|
|
||||||
|
[**/generated/**]
|
||||||
|
ktlint=disabled
|
||||||
@@ -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 your extension repo.
|
|
||||||
- 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
|
|
||||||
- Suwayomi-Server version: (Example: v1.1.1-r1535-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 your extension repo.
|
|
||||||
- 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 Suwayomi?
|
|
||||||
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
|
||||||
|
-->
|
||||||
@@ -14,29 +14,44 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
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
|
||||||
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: Checkout pull request
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@v4
|
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@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -44,9 +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: Build Jar
|
- name: Build And Run Jar
|
||||||
uses: gradle/gradle-build-action@v2
|
working-directory: master
|
||||||
with:
|
run: |
|
||||||
build-root-directory: master
|
./gradlew ktlintCheck :server:shadowJar --stacktrace
|
||||||
arguments: ktlintCheck :server:shadowJar --stacktrace
|
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 -DcrashOnFailedMigration=true \
|
||||||
|
-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 -DcrashOnFailedMigration=true \
|
||||||
|
-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
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v3
|
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 Jar
|
name: Build Jar
|
||||||
@@ -26,17 +26,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout master branch
|
- name: Checkout master branch
|
||||||
uses: actions/checkout@v4
|
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@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -45,22 +48,20 @@ jobs:
|
|||||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build Jar
|
- name: Build Jar
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
env:
|
env:
|
||||||
ProductBuildType: "Preview"
|
ProductBuildType: "Preview"
|
||||||
with:
|
working-directory: master
|
||||||
build-root-directory: master
|
run: ./gradlew :server:shadowJar --stacktrace
|
||||||
arguments: :server:shadowJar --stacktrace
|
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -70,56 +71,102 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
if-no-files-found: error
|
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:
|
bundle:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
include:
|
||||||
- debian-all
|
- name: debian-all
|
||||||
- linux-assets
|
jre: linux-x64
|
||||||
- linux-x64
|
- name: appimage
|
||||||
- macOS-x64
|
jre: linux-x64
|
||||||
- macOS-arm64
|
- name: linux-assets
|
||||||
- windows-x64
|
jre: linux-assets
|
||||||
- windows-x86
|
- 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.os }} release
|
name: Make ${{ matrix.name }} release
|
||||||
needs: build
|
needs: [build, jlink]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
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
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
- name: Make ${{ matrix.os }} release
|
- name: Make ${{ matrix.name }} release
|
||||||
run: |
|
run: |
|
||||||
mkdir upload
|
mkdir upload
|
||||||
tar -xvpf scripts.tar.gz
|
tar -xvpf scripts.tar.gz
|
||||||
scripts/bundler.sh -o upload/ ${{ matrix.os }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.os }} release
|
- name: Upload ${{ matrix.name }} release
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.os }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
@@ -127,41 +174,41 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: appimage
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: windows-x86
|
|
||||||
path: release
|
|
||||||
|
|
||||||
- name: Checkout Preview branch
|
- name: Checkout Preview branch
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
ref: main
|
ref: main
|
||||||
@@ -193,7 +240,7 @@ jobs:
|
|||||||
git push origin $TAG
|
git push origin $TAG
|
||||||
|
|
||||||
- name: Upload Preview Release
|
- name: Upload Preview Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
|
||||||
repository: "Suwayomi/Suwayomi-Server-preview"
|
repository: "Suwayomi/Suwayomi-Server-preview"
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
name: Issue moderator
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
autoclose:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Moderate issues
|
|
||||||
uses: tachiyomiorg/issue-moderator-action@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ github.token }}
|
|
||||||
duplicate-check-enabled: true
|
|
||||||
duplicate-check-label: Source request
|
|
||||||
existing-check-enabled: true
|
|
||||||
existing-check-label: Source request
|
|
||||||
auto-close-rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "title",
|
|
||||||
"regex": ".*<short description>.*",
|
|
||||||
"message": "You did not fill out the description in the title"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "title",
|
|
||||||
"regex": ".*(<|>)+.*",
|
|
||||||
"message": "You did not remove Angle brackets(< and >) from 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": ".*(Suwayomi-Server 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."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -16,10 +16,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
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 Jar
|
name: Build Jar
|
||||||
@@ -27,17 +27,20 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ${{ github.ref }}
|
- name: Checkout ${{ github.ref }}
|
||||||
uses: actions/checkout@v4
|
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@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 25
|
||||||
distribution: 'temurin'
|
distribution: 'zulu'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v5
|
||||||
|
|
||||||
- name: Copy CI gradle.properties
|
- name: Copy CI gradle.properties
|
||||||
run: |
|
run: |
|
||||||
@@ -47,22 +50,20 @@ jobs:
|
|||||||
~/.gradle/gradle.properties
|
~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build and copy webUI, Build Jar
|
- name: Build and copy webUI, Build Jar
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
env:
|
env:
|
||||||
ProductBuildType: "Stable"
|
ProductBuildType: "Stable"
|
||||||
with:
|
working-directory: master
|
||||||
build-root-directory: master
|
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
|
||||||
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
|
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: Upload Jar
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: master/server/build/*.jar
|
path: master/server/build/*.jar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload icons
|
- name: Upload icons
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: master/server/src/main/resources/icon
|
path: master/server/src/main/resources/icon
|
||||||
@@ -72,56 +73,102 @@ jobs:
|
|||||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||||
|
|
||||||
- name: Upload scripts.tar.gz
|
- name: Upload scripts.tar.gz
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
path: scripts.tar.gz
|
path: scripts.tar.gz
|
||||||
if-no-files-found: error
|
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:
|
bundle:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
include:
|
||||||
- debian-all
|
- name: debian-all
|
||||||
- linux-assets
|
jre: linux-x64
|
||||||
- linux-x64
|
- name: appimage
|
||||||
- macOS-x64
|
jre: linux-x64
|
||||||
- macOS-arm64
|
- name: linux-assets
|
||||||
- windows-x64
|
jre: linux-assets
|
||||||
- windows-x86
|
- 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.os }} release
|
name: Make ${{ matrix.name }} release
|
||||||
needs: build
|
needs: [build, jlink]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download Jar
|
- name: Download Jar
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: server/build
|
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
|
- name: Download icons
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: icon
|
name: icon
|
||||||
path: server/src/main/resources/icon
|
path: server/src/main/resources/icon
|
||||||
|
|
||||||
- name: Download scripts.tar.gz
|
- name: Download scripts.tar.gz
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: scripts
|
name: scripts
|
||||||
|
|
||||||
- name: Make ${{ matrix.os }} release
|
- name: Make ${{ matrix.name }} release
|
||||||
run: |
|
run: |
|
||||||
mkdir upload/
|
mkdir upload/
|
||||||
tar -xvpf scripts.tar.gz
|
tar -xvpf scripts.tar.gz
|
||||||
scripts/bundler.sh -o upload/ ${{ matrix.os }}
|
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||||
|
|
||||||
- name: Upload ${{ matrix.os }} files
|
- name: Upload ${{ matrix.name }} files
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.os }}
|
name: ${{ matrix.name }}
|
||||||
path: upload/*
|
path: upload/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
@@ -130,44 +177,44 @@ jobs:
|
|||||||
needs: bundle
|
needs: bundle
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: jar
|
name: jar
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: debian-all
|
name: debian-all
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: appimage
|
||||||
|
path: release
|
||||||
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-assets
|
name: linux-assets
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: linux-x64
|
name: linux-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-x64
|
name: macOS-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: macOS-arm64
|
name: macOS-arm64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: windows-x64
|
name: windows-x64
|
||||||
path: release
|
path: release
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-x86
|
|
||||||
path: release
|
|
||||||
|
|
||||||
- name: Generate checksums
|
- name: Generate checksums
|
||||||
run: cd release && sha256sum * > Checksums.sha256
|
run: cd release && sha256sum * > Checksums.sha256
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
name: Publish to WinGet
|
name: Publish to WinGet
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_dispatch:
|
||||||
workflows: ["CI Publish"]
|
inputs:
|
||||||
types:
|
version:
|
||||||
- completed
|
description: Version
|
||||||
|
required: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest # action can only be run on windows
|
runs-on: windows-latest # action can only be run on windows
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
identifier: Suwayomi.Tachidesk-Server
|
identifier: Suwayomi.Suwayomi-Server
|
||||||
installers-regex: '.*x64.msi$'
|
installers-regex: '.*x64.msi$'
|
||||||
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
||||||
|
version: ${{ inputs.version || github.ref_name }}
|
||||||
|
release-tag: ${{ inputs.version || github.ref_name }}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ gradle.properties
|
|||||||
.fleet
|
.fleet
|
||||||
# But we need these
|
# But we need these
|
||||||
!.idea/runConfigurations
|
!.idea/runConfigurations
|
||||||
|
.kotlin
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Ignore Gradle build output directory
|
||||||
build
|
build
|
||||||
@@ -19,3 +20,4 @@ scripts/OpenJDK*
|
|||||||
scripts/zulu*
|
scripts/zulu*
|
||||||
scripts/electron-*
|
scripts/electron-*
|
||||||
scripts/rcedit-*
|
scripts/rcedit-*
|
||||||
|
scripts/resources/*.so
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,19 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.kotlin.jvm.get().pluginId)
|
id(
|
||||||
id(libs.plugins.kotlin.serialization.get().pluginId)
|
libs.plugins.kotlin.jvm
|
||||||
id(libs.plugins.ktlint.get().pluginId)
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.serialization
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.ktlint
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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"
|
||||||
|
|
||||||
@@ -15,6 +15,6 @@ 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,13 +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,13 +10,14 @@ 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.ConfigObject
|
||||||
import com.typesafe.config.ConfigValue
|
import com.typesafe.config.ConfigValue
|
||||||
import com.typesafe.config.ConfigValueFactory
|
|
||||||
import com.typesafe.config.parser.ConfigDocument
|
import com.typesafe.config.parser.ConfigDocument
|
||||||
import com.typesafe.config.parser.ConfigDocumentFactory
|
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.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import mu.KotlinLogging
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,11 +48,10 @@ 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 {
|
private fun getUserConfig(): Config =
|
||||||
return userConfigFile.let {
|
userConfigFile.let {
|
||||||
ConfigFactory.parseFile(it)
|
ConfigFactory.parseFile(it)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configs
|
* Load configs
|
||||||
@@ -72,7 +72,8 @@ open class ConfigManager {
|
|||||||
val userConfig = getUserConfig()
|
val userConfig = getUserConfig()
|
||||||
|
|
||||||
val config =
|
val config =
|
||||||
ConfigFactory.empty()
|
ConfigFactory
|
||||||
|
.empty()
|
||||||
.withFallback(baseConfig)
|
.withFallback(baseConfig)
|
||||||
.withFallback(userConfig)
|
.withFallback(userConfig)
|
||||||
.withFallback(compatConfig)
|
.withFallback(compatConfig)
|
||||||
@@ -112,54 +113,87 @@ open class ConfigManager {
|
|||||||
value: Any,
|
value: Any,
|
||||||
) {
|
) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val configValue = ConfigValueFactory.fromAnyRef(value)
|
val configValue = value.toConfig("internal").getValue("internal")
|
||||||
|
|
||||||
updateUserConfigFile(path, configValue)
|
updateUserConfigFile(path, configValue)
|
||||||
internalConfig = internalConfig.withValue(path, configValue)
|
internalConfig = internalConfig.withValue(path, configValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetUserConfig(updateInternalConfig: Boolean = true): ConfigDocument {
|
private fun createConfigDocumentFromReference(): ConfigDocument {
|
||||||
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
|
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
|
||||||
val serverConfigDoc = ConfigDocumentFactory.parseString(serverConfigFileContent)
|
return ConfigDocumentFactory.parseString(serverConfigFileContent)
|
||||||
userConfigFile.writeText(serverConfigDoc.render())
|
|
||||||
|
|
||||||
if (updateInternalConfig) {
|
|
||||||
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetUserConfig(): ConfigDocument {
|
||||||
|
val serverConfigDoc = createConfigDocumentFromReference()
|
||||||
|
|
||||||
|
userConfigFile.writeText(serverConfigDoc.render())
|
||||||
|
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||||
|
|
||||||
return serverConfigDoc
|
return serverConfigDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure the "UserConfig" is up-to-date.
|
* Makes sure the "UserConfig" is up-to-date.
|
||||||
*
|
*
|
||||||
* - adds missing settings
|
* - Adds missing settings
|
||||||
* - removes outdated settings
|
* - Migrates deprecated settings
|
||||||
|
* - Removes outdated settings
|
||||||
*/
|
*/
|
||||||
fun updateUserConfig() {
|
fun updateUserConfig(migrate: ConfigDocument.(Config) -> ConfigDocument) {
|
||||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||||
val userConfig = getUserConfig()
|
val userConfig = getUserConfig()
|
||||||
|
|
||||||
val hasMissingSettings = serverConfig.entrySet().any { !userConfig.hasPath(it.key) }
|
// NOTE: if more than 1 dot is included, that's a nested setting, which we need to filter out here
|
||||||
val hasOutdatedSettings = userConfig.entrySet().any { !serverConfig.hasPath(it.key) }
|
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
|
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
|
||||||
if (!isUserConfigOutdated) {
|
if (!isUserConfigOutdated) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug {
|
logger.debug {
|
||||||
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings"
|
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var newUserConfigDoc: ConfigDocument = resetUserConfig(false)
|
var newUserConfigDoc: ConfigDocument = createConfigDocumentFromReference()
|
||||||
userConfig.entrySet().filter {
|
userConfig
|
||||||
|
.entrySet()
|
||||||
|
.filter {
|
||||||
serverConfig.hasPath(
|
serverConfig.hasPath(
|
||||||
it.key,
|
it.key,
|
||||||
)
|
) ||
|
||||||
|
it.key.count { c -> c == '.' } > 1
|
||||||
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
|
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
|
||||||
|
|
||||||
|
newUserConfigDoc =
|
||||||
|
migrate(newUserConfigDoc, internalConfig)
|
||||||
|
|
||||||
userConfigFile.writeText(newUserConfigDoc.render())
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,50 +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 com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigValueFactory
|
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(getConfig: () -> Config)
|
abstract class ConfigModule(
|
||||||
|
getConfig: () -> Config,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract jvm-commandline-argument-overridable config module.
|
* Abstract jvm-commandline-argument-overridable config module.
|
||||||
*/
|
*/
|
||||||
abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, moduleName: String) : ConfigModule(getConfig) {
|
abstract class SystemPropertyOverridableConfigModule(
|
||||||
|
getConfig: () -> Config,
|
||||||
|
val moduleName: String,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
|
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 getConfig: () -> Config, val moduleName: String) {
|
class SystemPropertyOverrideDelegate(
|
||||||
|
val getConfig: () -> Config,
|
||||||
|
val moduleName: String,
|
||||||
|
) {
|
||||||
inline operator fun <R, reified T> getValue(
|
inline operator fun <R, reified T> getValue(
|
||||||
thisRef: R,
|
thisRef: R,
|
||||||
property: KProperty<*>,
|
property: KProperty<*>,
|
||||||
): T {
|
): T {
|
||||||
val config = getConfig()
|
val config = getConfig()
|
||||||
|
|
||||||
|
val systemProperty =
|
||||||
|
System.getProperty("$CONFIG_PREFIX.$moduleName.${property.name}")
|
||||||
|
if (systemProperty == null) {
|
||||||
val configValue: T = config.getValue(thisRef, property)
|
val configValue: T = config.getValue(thisRef, property)
|
||||||
|
return configValue
|
||||||
|
}
|
||||||
|
|
||||||
val combined =
|
val systemPropertyConfig =
|
||||||
System.getProperty(
|
try {
|
||||||
"$CONFIG_PREFIX.$moduleName.${property.name}",
|
ConfigFactory.parseString("internal=$systemProperty")
|
||||||
if (T::class.simpleName == "List") {
|
} catch (_: ConfigException) {
|
||||||
ConfigValueFactory.fromAnyRef(configValue).render()
|
ConfigFactory.parseMap(mapOf("internal" to systemProperty))
|
||||||
} else {
|
}
|
||||||
configValue.toString()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return when (T::class.simpleName) {
|
val genericType = object : TypeReference<T>() {}.genericType()
|
||||||
"Int" -> combined.toInt()
|
val clazz = ClassContainer(T::class, genericType)
|
||||||
"Boolean" -> combined.toBoolean()
|
val reader = SelectReader.getReader(clazz)
|
||||||
"Double" -> combined.toDouble()
|
val path = property.name
|
||||||
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
|
|
||||||
// add more types as needed
|
val result = reader(systemPropertyConfig, "internal")
|
||||||
else -> combined // covers String
|
return try {
|
||||||
} as T
|
result as T
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw result
|
||||||
|
?.let { e }
|
||||||
|
?: ConfigException.BadPath(path, "take a look at your config")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,29 @@ import ch.qos.logback.core.rolling.RollingFileAppender
|
|||||||
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
|
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
|
||||||
import ch.qos.logback.core.util.FileSize
|
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
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
private fun fileSizeValueOfOrDefault(
|
||||||
|
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(
|
private fun createRollingFileAppender(
|
||||||
logContext: LoggerContext,
|
logContext: LoggerContext,
|
||||||
logDirPath: String,
|
logDirPath: String,
|
||||||
|
maxFiles: Int,
|
||||||
|
maxFileSize: String,
|
||||||
|
maxTotalSize: String,
|
||||||
): RollingFileAppender<ILoggingEvent> {
|
): RollingFileAppender<ILoggingEvent> {
|
||||||
val logFilename = "application"
|
val logFilename = "application"
|
||||||
|
|
||||||
@@ -34,7 +50,7 @@ private fun createRollingFileAppender(
|
|||||||
|
|
||||||
val appender =
|
val appender =
|
||||||
RollingFileAppender<ILoggingEvent>().apply {
|
RollingFileAppender<ILoggingEvent>().apply {
|
||||||
name = "FILE"
|
name = FILE_APPENDER_NAME
|
||||||
context = logContext
|
context = logContext
|
||||||
encoder = logEncoder
|
encoder = logEncoder
|
||||||
file = "$logDirPath/$logFilename.log"
|
file = "$logDirPath/$logFilename.log"
|
||||||
@@ -45,9 +61,9 @@ private fun createRollingFileAppender(
|
|||||||
context = logContext
|
context = logContext
|
||||||
setParent(appender)
|
setParent(appender)
|
||||||
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
|
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
|
||||||
setMaxFileSize(FileSize.valueOf("10mb"))
|
maxHistory = maxFiles.coerceAtLeast(0)
|
||||||
maxHistory = 14
|
setMaxFileSize(fileSizeValueOfOrDefault(maxFileSize, "10mb"))
|
||||||
setTotalSizeCap(FileSize.valueOf("1gb"))
|
setTotalSizeCap(fileSizeValueOfOrDefault(maxTotalSize, "100mb"))
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,25 +73,52 @@ private fun createRollingFileAppender(
|
|||||||
return appender
|
return appender
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBaseLogger(): ch.qos.logback.classic.Logger {
|
private fun getBaseLogger(): ch.qos.logback.classic.Logger =
|
||||||
return (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger)
|
((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 {
|
private fun getLogger(name: String): ch.qos.logback.classic.Logger {
|
||||||
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
||||||
return context.getLogger(name)
|
return context.getLogger(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initLoggerConfig(appRootPath: String) {
|
fun initLoggerConfig(
|
||||||
|
appRootPath: String,
|
||||||
|
maxFiles: Int,
|
||||||
|
maxFileSize: String,
|
||||||
|
maxTotalSize: String,
|
||||||
|
) {
|
||||||
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
val context = LoggerFactory.getILoggerFactory() as LoggerContext
|
||||||
val logger = getBaseLogger()
|
val logger = getBaseLogger()
|
||||||
|
|
||||||
// logback logs to the console by default (at least when adding a console appender logs in the console are duplicated)
|
// 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"))
|
logger.addAppender(createRollingFileAppender(context, "$appRootPath/logs", maxFiles, maxFileSize, maxTotalSize))
|
||||||
|
|
||||||
// set "kotlin exposed" log level
|
// set "kotlin exposed" log level
|
||||||
setLogLevelFor("Exposed", Level.ERROR)
|
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"
|
const val BASE_LOGGER_NAME = "_BaseLogger"
|
||||||
|
|
||||||
fun setLogLevelFor(
|
fun setLogLevelFor(
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.kotlin.jvm.get().pluginId)
|
id(
|
||||||
id(libs.plugins.kotlin.serialization.get().pluginId)
|
libs.plugins.kotlin.jvm
|
||||||
id(libs.plugins.ktlint.get().pluginId)
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.kotlin.serialization
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
|
id(
|
||||||
|
libs.plugins.ktlint
|
||||||
|
.get()
|
||||||
|
.pluginId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -24,8 +36,8 @@ dependencies {
|
|||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly(libs.android.annotations)
|
compileOnly(libs.android.annotations)
|
||||||
|
|
||||||
// substitute for duktape-android
|
// substitute for duktape-android/quickjs
|
||||||
implementation(libs.bundles.rhino)
|
implementation(libs.bundles.polyglot)
|
||||||
|
|
||||||
// Kotlin wrapper around Java Preferences, makes certain things easier
|
// Kotlin wrapper around Java Preferences, makes certain things easier
|
||||||
implementation(libs.bundles.settings)
|
implementation(libs.bundles.settings)
|
||||||
@@ -35,4 +47,5 @@ dependencies {
|
|||||||
|
|
||||||
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
||||||
implementation(libs.bundles.twelvemonkeys)
|
implementation(libs.bundles.twelvemonkeys)
|
||||||
|
implementation(libs.imageio.webp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ package android.graphics;
|
|||||||
|
|
||||||
import android.annotation.ColorInt;
|
import android.annotation.ColorInt;
|
||||||
import android.annotation.NonNull;
|
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.IIOImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.Raster;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
public final class Bitmap {
|
public final class Bitmap {
|
||||||
private final int width;
|
private final int width;
|
||||||
@@ -58,7 +60,23 @@ public final class Bitmap {
|
|||||||
ARGB_8888(5),
|
ARGB_8888(5),
|
||||||
RGBA_F16(6),
|
RGBA_F16(6),
|
||||||
HARDWARE(7),
|
HARDWARE(7),
|
||||||
RGBA_1010102(8);
|
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;
|
final int nativeInt;
|
||||||
|
|
||||||
@@ -75,6 +93,70 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* Common code for checking that x and y are >= 0
|
||||||
*
|
*
|
||||||
@@ -106,7 +188,7 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap createBitmap(int width, int height, Config config) {
|
public static Bitmap createBitmap(int width, int height, Config config) {
|
||||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
BufferedImage image = new BufferedImage(width, height, configToBufferedImageType(config));
|
||||||
return new Bitmap(image);
|
return new Bitmap(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +226,10 @@ public final class Bitmap {
|
|||||||
formatString = "png";
|
formatString = "png";
|
||||||
} else if (format == Bitmap.CompressFormat.JPEG) {
|
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||||
formatString = "jpg";
|
formatString = "jpg";
|
||||||
|
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||||
|
formatString = "webp";
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported compression format!");
|
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||||
@@ -162,14 +246,19 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
writer.setOutput(ios);
|
writer.setOutput(ios);
|
||||||
|
|
||||||
|
BufferedImage img = image;
|
||||||
|
|
||||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
if ("jpg".equals(formatString)) {
|
if ("jpg".equals(formatString)) {
|
||||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
param.setCompressionQuality(qualityFloat);
|
param.setCompressionQuality(qualityFloat);
|
||||||
|
|
||||||
|
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
img.getGraphics().drawImage(image, 0, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writer.write(null, new IIOImage(image, null, null), param);
|
writer.write(null, new IIOImage(img, null, null), param);
|
||||||
ios.close();
|
ios.close();
|
||||||
writer.dispose();
|
writer.dispose();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@@ -179,6 +268,12 @@ public final class Bitmap {
|
|||||||
return true;
|
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()
|
* Shared code to check for illegal arguments passed to getPixels()
|
||||||
* or setPixels()
|
* or setPixels()
|
||||||
@@ -220,16 +315,102 @@ public final class Bitmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
|
||||||
int x, int y, int width, int height) {
|
int x, int y, int width, int height) {
|
||||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||||
|
|
||||||
Raster raster = image.getData();
|
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||||
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
|
|
||||||
|
|
||||||
for (int ht = 0; ht < height; ht++) {
|
|
||||||
int rowOffset = offset + stride * ht;
|
|
||||||
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,462 @@ import java.util.Iterator;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public class BitmapFactory {
|
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) {
|
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;
|
Bitmap bitmap = null;
|
||||||
|
// TODO: Support options with in parameters
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
|
ImageInputStream imageInputStream = ImageIO.createImageInputStream(is);
|
||||||
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
|
||||||
|
|
||||||
if (!imageReaders.hasNext()) {
|
if (!imageReaders.hasNext()) {
|
||||||
@@ -24,8 +473,18 @@ public class BitmapFactory {
|
|||||||
ImageReader imageReader = imageReaders.next();
|
ImageReader imageReader = imageReaders.next();
|
||||||
imageReader.setInput(imageInputStream);
|
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());
|
BufferedImage image = imageReader.read(0, imageReader.getDefaultReadParam());
|
||||||
bitmap = new Bitmap(image);
|
bitmap = new Bitmap(image);
|
||||||
|
}
|
||||||
|
|
||||||
imageReader.dispose();
|
imageReader.dispose();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@@ -36,16 +495,11 @@ public class BitmapFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length) {
|
||||||
Bitmap bitmap = null;
|
return decodeByteArray(data, offset, length, null);
|
||||||
|
|
||||||
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data);
|
|
||||||
try {
|
|
||||||
BufferedImage image = ImageIO.read(byteArrayStream);
|
|
||||||
bitmap = new Bitmap(image);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap;
|
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
|
||||||
|
ByteArrayInputStream byteArrayStream = new ByteArrayInputStream(data, offset, length);
|
||||||
|
return decodeStream(byteArrayStream, null, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
package android.graphics;
|
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.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.awt.image.BufferedImage;
|
||||||
import javax.imageio.ImageIO;
|
import java.text.AttributedString;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class Canvas {
|
public final class Canvas {
|
||||||
private BufferedImage canvasImage;
|
private BufferedImage canvasImage;
|
||||||
private Graphics2D canvas;
|
private Graphics2D canvas;
|
||||||
|
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||||
|
|
||||||
|
private static final String TAG = "Canvas";
|
||||||
|
|
||||||
public Canvas(Bitmap bitmap) {
|
public Canvas(Bitmap bitmap) {
|
||||||
canvasImage = bitmap.getImage();
|
canvasImage = bitmap.getImage();
|
||||||
@@ -16,6 +34,210 @@ public final class Canvas {
|
|||||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||||
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
||||||
canvas.drawImage(sourceImageCropped, null, dst.left, dst.top);
|
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
@@ -1,15 +1,15 @@
|
|||||||
package android.graphics;
|
package android.graphics;
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
public final class Rect {
|
public final class Rect {
|
||||||
int left;
|
public int left;
|
||||||
int top;
|
public int top;
|
||||||
int right;
|
public int right;
|
||||||
int bottom;
|
public int bottom;
|
||||||
|
|
||||||
private static final class UnflattenHelper {
|
private static final class UnflattenHelper {
|
||||||
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
||||||
@@ -37,11 +37,25 @@ public final class Rect {
|
|||||||
this.right = 0;
|
this.right = 0;
|
||||||
this.bottom = 0;
|
this.bottom = 0;
|
||||||
} else {
|
} 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.left = left;
|
||||||
this.top = top;
|
this.top = top;
|
||||||
this.right = right;
|
this.right = right;
|
||||||
this.bottom = bottom;
|
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() {
|
public final int getWidth() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,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!"); }
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ package androidx.preference;
|
|||||||
import android.content.Context;
|
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;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +25,8 @@ public class Preference {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
protected Context context;
|
protected Context context;
|
||||||
|
|
||||||
private boolean isVisible;
|
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;
|
||||||
@@ -68,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() {
|
||||||
@@ -125,33 +133,34 @@ public class Preference {
|
|||||||
/** Tachidesk specific API */
|
/** Tachidesk specific API */
|
||||||
@SuppressWarnings("unchecked")
|
@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<>();
|
||||||
case "Set<String>":
|
default -> throw new RuntimeException("Unsupported type");
|
||||||
return sharedPreferences.getStringSet(key, (Set<String>)defaultValue);
|
});
|
||||||
default:
|
|
||||||
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")
|
@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;
|
|
||||||
case "Set<String>":
|
|
||||||
sharedPreferences.edit().putStringSet(key, (Set<String>)value).apply();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unsupported type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ 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) {
|
public TwoStatePreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -25,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!"); }
|
||||||
|
|||||||
@@ -1,69 +1,99 @@
|
|||||||
package app.cash.quickjs;
|
package app.cash.quickjs;
|
||||||
|
|
||||||
import org.mozilla.javascript.ConsString;
|
import org.graalvm.polyglot.*;
|
||||||
import org.mozilla.javascript.NativeArray;
|
|
||||||
|
|
||||||
import javax.script.ScriptEngine;
|
|
||||||
import javax.script.ScriptEngineManager;
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public final class QuickJs implements Closeable {
|
public final class QuickJs implements Closeable {
|
||||||
private ScriptEngine engine;
|
private Context context;
|
||||||
|
|
||||||
public static QuickJs create() {
|
public static QuickJs create() {
|
||||||
return new QuickJs(new ScriptEngineManager());
|
return new QuickJs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuickJs(ScriptEngineManager manager) {
|
public QuickJs() {
|
||||||
this.engine = manager.getEngineByName("rhino");
|
this.context = Context
|
||||||
|
.newBuilder("js")
|
||||||
|
.allowHostAccess(HostAccess.ALL)
|
||||||
|
.allowPolyglotAccess(PolyglotAccess.NONE)
|
||||||
|
.allowHostClassLoading(false)
|
||||||
|
.build();
|
||||||
|
context.enter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object evaluate(String script, String fileName) {
|
public Object evaluate(String script, String ignoredFileName) {
|
||||||
return this.evaluate(script);
|
return this.evaluate(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object evaluate(String script) {
|
public Object evaluate(String script) {
|
||||||
try {
|
try {
|
||||||
Object value = engine.eval(script);
|
Value value = context.eval("js", script);
|
||||||
return translateType(value);
|
return translateType(value);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new QuickJsException(exception.getMessage(), exception);
|
throw new QuickJsException(exception.getMessage(), exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object translateType(Object obj) {
|
private Object translateType(Value obj) {
|
||||||
if (obj instanceof NativeArray) {
|
if (obj.isBoolean()) {
|
||||||
NativeArray array = (NativeArray) obj;
|
return obj.asBoolean();
|
||||||
long length = array.getLength();
|
} else if (obj.hasArrayElements()) {
|
||||||
Object[] objects = new Object[(int) length];
|
if (obj.getArraySize() == 0) {
|
||||||
for (int i = 0; i < (int) length; i++) {
|
return new int[0];
|
||||||
objects[i] = translateType(array.get(i));
|
} 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);
|
||||||
}
|
}
|
||||||
return objects;
|
} else if (element.isHostObject()) {
|
||||||
|
return obj.as(Object[].class);
|
||||||
|
} else if (element.isString()) {
|
||||||
|
return obj.as(String[].class);
|
||||||
}
|
}
|
||||||
if (obj instanceof ConsString) {
|
|
||||||
ConsString consString = (ConsString) obj;
|
|
||||||
return consString.toString();
|
|
||||||
}
|
}
|
||||||
if (obj instanceof Long) {
|
} else if (obj.isNumber()) {
|
||||||
Long value = (Long) obj;
|
if (obj.fitsInInt()) {
|
||||||
return value.intValue();
|
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;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] compile(String sourceCode, String fileName) {
|
public byte[] compile(String sourceCode, String ignoredFileName) {
|
||||||
return sourceCode.getBytes();
|
return sourceCode.getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Object execute(byte[] bytecode) {
|
public Object execute(byte[] bytecode) {
|
||||||
return this.evaluate(new String(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
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
this.engine = null;
|
if (this.context != null) {
|
||||||
|
this.context.leave();
|
||||||
|
this.context.close();
|
||||||
|
this.context = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {}
|
||||||
|
}
|
||||||
@@ -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,13 +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 DI.global.instance()
|
val context: CustomContext by KoinPlatformTools.defaultContext().get().inject()
|
||||||
|
|
||||||
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,8 +12,6 @@ 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),
|
||||||
@@ -21,6 +19,8 @@ class AndroidCompatInitializer {
|
|||||||
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(
|
System.setProperty(
|
||||||
"http.agent",
|
"http.agent",
|
||||||
|
|||||||
@@ -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,25 +14,19 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
|
|||||||
* AndroidCompatModule
|
* AndroidCompatModule
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AndroidCompatModule {
|
fun androidCompatModule(): Module =
|
||||||
fun create() =
|
module {
|
||||||
DI.Module("AndroidCompat") {
|
single { AndroidFiles() }
|
||||||
bind<AndroidFiles>() with singleton { 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
|
single<Context> { get<CustomContext>() }
|
||||||
singleton {
|
|
||||||
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 {
|
||||||
|
|||||||
+3
-1
@@ -8,7 +8,9 @@ import xyz.nulldev.ts.config.ConfigModule
|
|||||||
* Application info config.
|
* Application info config.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
|
class ApplicationInfoConfigModule(
|
||||||
|
getConfig: () -> Config,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
val packageName: String by getConfig()
|
val packageName: String by getConfig()
|
||||||
val debug: Boolean by getConfig()
|
val debug: Boolean by getConfig()
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ 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(getConfig: () -> Config) : ConfigModule(getConfig) {
|
class FilesConfigModule(
|
||||||
|
getConfig: () -> Config,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
val dataDir: String by getConfig()
|
val dataDir: String by getConfig()
|
||||||
val filesDir: String by getConfig()
|
val filesDir: String by getConfig()
|
||||||
val noBackupFilesDir: String by getConfig()
|
val noBackupFilesDir: String by getConfig()
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import com.typesafe.config.Config
|
|||||||
import io.github.config4k.getValue
|
import io.github.config4k.getValue
|
||||||
import xyz.nulldev.ts.config.ConfigModule
|
import xyz.nulldev.ts.config.ConfigModule
|
||||||
|
|
||||||
class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig) {
|
class SystemConfigModule(
|
||||||
|
val getConfig: () -> Config,
|
||||||
|
) : ConfigModule(getConfig) {
|
||||||
val isDebuggable: Boolean by getConfig()
|
val isDebuggable: Boolean by getConfig()
|
||||||
|
|
||||||
val propertyPrefix = "properties."
|
val propertyPrefix = "properties."
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import java.sql.Timestamp
|
|||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
class ScrollableResultSet(
|
||||||
|
val parent: ResultSet,
|
||||||
|
) : ResultSet by parent {
|
||||||
private val cachedContent = mutableListOf<ResultSetEntry>()
|
private val cachedContent = mutableListOf<ResultSetEntry>()
|
||||||
private val columnCache = mutableMapOf<String, Int>()
|
private val columnCache = mutableMapOf<String, Int>()
|
||||||
private var lastReturnWasNull = false
|
private var lastReturnWasNull = false
|
||||||
@@ -29,7 +31,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
val parentMetadata = parent.metaData
|
val parentMetadata = parent.metaData
|
||||||
val columnCount = parentMetadata.columnCount
|
val columnCount = parentMetadata.columnCount
|
||||||
val columnLabels =
|
val columnLabels =
|
||||||
(1..columnCount).map {
|
(1..columnCount)
|
||||||
|
.map {
|
||||||
parentMetadata.getColumnLabel(it)
|
parentMetadata.getColumnLabel(it)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
@@ -45,20 +48,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
while (parent.next()) {
|
while (parent.next()) {
|
||||||
cachedContent +=
|
cachedContent +=
|
||||||
ResultSetEntry().apply {
|
ResultSetEntry().apply {
|
||||||
for (i in 1..columnCount)
|
for (i in 1..columnCount) {
|
||||||
data += parent.getObject(i)
|
data += parent.getObject(i)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resultSetLength++
|
resultSetLength++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notImplemented(): Nothing {
|
private fun notImplemented(): Nothing = throw UnsupportedOperationException("This class currently does not support this operation!")
|
||||||
throw UnsupportedOperationException("This class currently does not support this operation!")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cursorValid(): Boolean {
|
private fun cursorValid(): Boolean = isAfterLast || isBeforeFirst
|
||||||
return isAfterLast || isBeforeFirst
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun internalMove(row: Int) {
|
private fun internalMove(row: Int) {
|
||||||
if (cursor < 0) {
|
if (cursor < 0) {
|
||||||
@@ -76,22 +76,16 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun obj(column: String?): Any? {
|
private fun obj(column: String?): Any? = obj(cachedFindColumn(column))
|
||||||
return obj(cachedFindColumn(column))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cachedFindColumn(column: String?) =
|
private fun cachedFindColumn(column: String?) =
|
||||||
columnCache.getOrPut(column!!, {
|
columnCache.getOrPut(column!!, {
|
||||||
findColumn(column)
|
findColumn(column)
|
||||||
})
|
})
|
||||||
|
|
||||||
override fun getNClob(columnIndex: Int): NClob {
|
override fun getNClob(columnIndex: Int): NClob = obj(columnIndex) as NClob
|
||||||
return obj(columnIndex) as NClob
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNClob(columnLabel: String?): NClob {
|
override fun getNClob(columnLabel: String?): NClob = obj(columnLabel) as NClob
|
||||||
return obj(columnLabel) as NClob
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateNString(
|
override fun updateNString(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -260,17 +254,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBoolean(columnIndex: Int): Boolean {
|
override fun getBoolean(columnIndex: Int): Boolean = obj(columnIndex) as Boolean
|
||||||
return obj(columnIndex) as Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBoolean(columnLabel: String?): Boolean {
|
override fun getBoolean(columnLabel: String?): Boolean = obj(columnLabel) as Boolean
|
||||||
return obj(columnLabel) as Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isFirst(): Boolean {
|
override fun isFirst(): Boolean = cursor - 1 < resultSetLength
|
||||||
return cursor - 1 < resultSetLength
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBigDecimal(
|
override fun getBigDecimal(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -288,13 +276,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBigDecimal(columnIndex: Int): BigDecimal {
|
override fun getBigDecimal(columnIndex: Int): BigDecimal = obj(columnIndex) as BigDecimal
|
||||||
return obj(columnIndex) as BigDecimal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBigDecimal(columnLabel: String?): BigDecimal {
|
override fun getBigDecimal(columnLabel: String?): BigDecimal = obj(columnLabel) as BigDecimal
|
||||||
return obj(columnLabel) as BigDecimal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateBytes(
|
override fun updateBytes(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -310,9 +294,7 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isLast(): Boolean {
|
override fun isLast(): Boolean = cursor == resultSetLength
|
||||||
return cursor == resultSetLength
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun insertRow() {
|
override fun insertRow() {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
@@ -351,9 +333,7 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
return cursorValid()
|
return cursorValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAfterLast(): Boolean {
|
override fun isAfterLast(): Boolean = cursor > resultSetLength
|
||||||
return cursor > resultSetLength
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun relative(rows: Int): Boolean {
|
override fun relative(rows: Int): Boolean {
|
||||||
internalMove(cursor + rows)
|
internalMove(cursor + rows)
|
||||||
@@ -365,9 +345,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
internalMove(row)
|
internalMove(row)
|
||||||
} else {
|
} else {
|
||||||
last()
|
last()
|
||||||
for (i in 1..row)
|
for (i in 1..row) {
|
||||||
previous()
|
previous()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return cursorValid()
|
return cursorValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,19 +375,13 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
return cursorValid()
|
return cursorValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFloat(columnIndex: Int): Float {
|
override fun getFloat(columnIndex: Int): Float = obj(columnIndex) as Float
|
||||||
return obj(columnIndex) as Float
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFloat(columnLabel: String?): Float {
|
override fun getFloat(columnLabel: String?): Float = obj(columnLabel) as Float
|
||||||
return obj(columnLabel) as Float
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun wasNull() = lastReturnWasNull
|
override fun wasNull() = lastReturnWasNull
|
||||||
|
|
||||||
override fun getRow(): Int {
|
override fun getRow(): Int = cursor
|
||||||
return cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun first(): Boolean {
|
override fun first(): Boolean {
|
||||||
internalMove(1)
|
internalMove(1)
|
||||||
@@ -459,13 +434,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getURL(columnIndex: Int): URL {
|
override fun getURL(columnIndex: Int): URL = obj(columnIndex) as URL
|
||||||
return obj(columnIndex) as URL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getURL(columnLabel: String?): URL {
|
override fun getURL(columnLabel: String?): URL = obj(columnLabel) as URL
|
||||||
return obj(columnLabel) as URL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateShort(
|
override fun updateShort(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -643,21 +614,13 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getByte(columnIndex: Int): Byte {
|
override fun getByte(columnIndex: Int): Byte = obj(columnIndex) as Byte
|
||||||
return obj(columnIndex) as Byte
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getByte(columnLabel: String?): Byte {
|
override fun getByte(columnLabel: String?): Byte = obj(columnLabel) as Byte
|
||||||
return obj(columnLabel) as Byte
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getString(columnIndex: Int): String? {
|
override fun getString(columnIndex: Int): String? = obj(columnIndex) as String?
|
||||||
return obj(columnIndex) as String?
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getString(columnLabel: String?): String? {
|
override fun getString(columnLabel: String?): String? = obj(columnLabel) as String?
|
||||||
return obj(columnLabel) as String?
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateSQLXML(
|
override fun updateSQLXML(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -687,13 +650,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getObject(columnIndex: Int): Any? {
|
override fun getObject(columnIndex: Int): Any? = obj(columnIndex)
|
||||||
return obj(columnIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getObject(columnLabel: String?): Any? {
|
override fun getObject(columnLabel: String?): Any? = obj(columnLabel)
|
||||||
return obj(columnLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getObject(
|
override fun getObject(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -714,16 +673,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
override fun <T : Any?> getObject(
|
override fun <T : Any?> getObject(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
type: Class<T>?,
|
type: Class<T>?,
|
||||||
): T {
|
): T = obj(columnIndex) as T
|
||||||
return obj(columnIndex) as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any?> getObject(
|
override fun <T : Any?> getObject(
|
||||||
columnLabel: String?,
|
columnLabel: String?,
|
||||||
type: Class<T>?,
|
type: Class<T>?,
|
||||||
): T {
|
): T = obj(columnLabel) as T
|
||||||
return obj(columnLabel) as T
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun previous(): Boolean {
|
override fun previous(): Boolean {
|
||||||
internalMove(cursor - 1)
|
internalMove(cursor - 1)
|
||||||
@@ -756,13 +711,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLong(columnIndex: Int): Long {
|
override fun getLong(columnIndex: Int): Long = castToLong(obj(columnIndex))
|
||||||
return castToLong(obj(columnIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLong(columnLabel: String?): Long {
|
override fun getLong(columnLabel: String?): Long = castToLong(obj(columnLabel))
|
||||||
return castToLong(obj(columnLabel))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getClob(columnIndex: Int): Clob {
|
override fun getClob(columnIndex: Int): Clob {
|
||||||
// TODO Maybe?
|
// TODO Maybe?
|
||||||
@@ -840,13 +791,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNString(columnIndex: Int): String {
|
override fun getNString(columnIndex: Int): String = obj(columnIndex) as String
|
||||||
return obj(columnIndex) as String
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNString(columnLabel: String?): String {
|
override fun getNString(columnLabel: String?): String = obj(columnLabel) as String
|
||||||
return obj(columnLabel) as String
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getArray(columnIndex: Int): Array {
|
override fun getArray(columnIndex: Int): Array {
|
||||||
// TODO Maybe?
|
// TODO Maybe?
|
||||||
@@ -880,17 +827,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCharacterStream(columnIndex: Int): Reader {
|
override fun getCharacterStream(columnIndex: Int): Reader = getNCharacterStream(columnIndex)
|
||||||
return getNCharacterStream(columnIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCharacterStream(columnLabel: String?): Reader {
|
override fun getCharacterStream(columnLabel: String?): Reader = getNCharacterStream(columnLabel)
|
||||||
return getNCharacterStream(columnLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isBeforeFirst(): Boolean {
|
override fun isBeforeFirst(): Boolean = cursor - 1 < resultSetLength
|
||||||
return cursor - 1 < resultSetLength
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateBoolean(
|
override fun updateBoolean(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -926,21 +867,13 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getShort(columnIndex: Int): Short {
|
override fun getShort(columnIndex: Int): Short = obj(columnIndex) as Short
|
||||||
return obj(columnIndex) as Short
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getShort(columnLabel: String?): Short {
|
override fun getShort(columnLabel: String?): Short = obj(columnLabel) as Short
|
||||||
return obj(columnLabel) as Short
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAsciiStream(columnIndex: Int): InputStream {
|
override fun getAsciiStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)
|
||||||
return getBinaryStream(columnIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAsciiStream(columnLabel: String?): InputStream {
|
override fun getAsciiStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)
|
||||||
return getBinaryStream(columnLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateTime(
|
override fun updateTime(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -1008,13 +941,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNCharacterStream(columnIndex: Int): Reader {
|
override fun getNCharacterStream(columnIndex: Int): Reader = getBinaryStream(columnIndex).reader()
|
||||||
return getBinaryStream(columnIndex).reader()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNCharacterStream(columnLabel: String?): Reader {
|
override fun getNCharacterStream(columnLabel: String?): Reader = getBinaryStream(columnLabel).reader()
|
||||||
return getBinaryStream(columnLabel).reader()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateArray(
|
override fun updateArray(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
@@ -1030,45 +959,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBytes(columnIndex: Int): ByteArray {
|
override fun getBytes(columnIndex: Int): ByteArray = obj(columnIndex) as ByteArray
|
||||||
return obj(columnIndex) as ByteArray
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBytes(columnLabel: String?): ByteArray {
|
override fun getBytes(columnLabel: String?): ByteArray = obj(columnLabel) as ByteArray
|
||||||
return obj(columnLabel) as ByteArray
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDouble(columnIndex: Int): Double {
|
override fun getDouble(columnIndex: Int): Double = obj(columnIndex) as Double
|
||||||
return obj(columnIndex) as Double
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDouble(columnLabel: String?): Double {
|
override fun getDouble(columnLabel: String?): Double = obj(columnLabel) as Double
|
||||||
return obj(columnLabel) as Double
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUnicodeStream(columnIndex: Int): InputStream {
|
override fun getUnicodeStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)
|
||||||
return getBinaryStream(columnIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUnicodeStream(columnLabel: String?): InputStream {
|
override fun getUnicodeStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)
|
||||||
return getBinaryStream(columnLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rowInserted() = false
|
override fun rowInserted() = false
|
||||||
|
|
||||||
private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface)
|
private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface)
|
||||||
|
|
||||||
override fun isWrapperFor(iface: Class<*>?): Boolean {
|
override fun isWrapperFor(iface: Class<*>?): Boolean = thisIsWrapperFor(iface) || parent.isWrapperFor(iface)
|
||||||
return thisIsWrapperFor(iface) || parent.isWrapperFor(iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getInt(columnIndex: Int): Int {
|
override fun getInt(columnIndex: Int): Int = obj(columnIndex) as Int
|
||||||
return obj(columnIndex) as Int
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getInt(columnLabel: String?): Int {
|
override fun getInt(columnLabel: String?): Int = obj(columnLabel) as Int
|
||||||
return obj(columnLabel) as Int
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateNull(columnIndex: Int) {
|
override fun updateNull(columnIndex: Int) {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
@@ -1088,8 +999,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMetaData(): ResultSetMetaData {
|
override fun getMetaData(): ResultSetMetaData =
|
||||||
return object : ResultSetMetaData by parentMetadata {
|
object : ResultSetMetaData by parentMetadata {
|
||||||
override fun isReadOnly(column: Int) = true
|
override fun isReadOnly(column: Int) = true
|
||||||
|
|
||||||
override fun isWritable(column: Int) = false
|
override fun isWritable(column: Int) = false
|
||||||
@@ -1098,19 +1009,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
|||||||
|
|
||||||
override fun getColumnCount() = this@ScrollableResultSet.columnCount
|
override fun getColumnCount() = this@ScrollableResultSet.columnCount
|
||||||
|
|
||||||
override fun getColumnLabel(column: Int): String {
|
override fun getColumnLabel(column: Int): String = columnLabels[column - 1]
|
||||||
return columnLabels[column - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBinaryStream(columnIndex: Int): InputStream {
|
override fun getBinaryStream(columnIndex: Int): InputStream = (obj(columnIndex) as ByteArray).inputStream()
|
||||||
return (obj(columnIndex) as ByteArray).inputStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBinaryStream(columnLabel: String?): InputStream {
|
override fun getBinaryStream(columnLabel: String?): InputStream = (obj(columnLabel) as ByteArray).inputStream()
|
||||||
return (obj(columnLabel) as ByteArray).inputStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateCharacterStream(
|
override fun updateCharacterStream(
|
||||||
columnIndex: Int,
|
columnIndex: Int,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-40
@@ -14,11 +14,11 @@ 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.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.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import mu.KotlinLogging
|
|
||||||
import xyz.nulldev.androidcompat.util.SafePath
|
import xyz.nulldev.androidcompat.util.SafePath
|
||||||
import xyz.nulldev.ts.config.ApplicationRootDir
|
import xyz.nulldev.ts.config.ApplicationRootDir
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
@@ -30,7 +30,9 @@ import kotlin.io.path.inputStream
|
|||||||
import kotlin.io.path.outputStream
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
|
||||||
class JavaSharedPreferences(key: String) : SharedPreferences {
|
class JavaSharedPreferences(
|
||||||
|
key: String,
|
||||||
|
) : SharedPreferences {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
}
|
}
|
||||||
@@ -72,20 +74,17 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
|
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
|
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
|
||||||
override fun getAll(): MutableMap<String, *> {
|
override fun getAll(): MutableMap<String, *> = preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
|
||||||
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getString(
|
override fun getString(
|
||||||
key: String,
|
key: String,
|
||||||
defValue: String?,
|
defValue: String?,
|
||||||
): String? {
|
): String? =
|
||||||
return if (defValue != null) {
|
if (defValue != null) {
|
||||||
preferences.getString(key, defValue)
|
preferences.getString(key, defValue)
|
||||||
} else {
|
} else {
|
||||||
preferences.getStringOrNull(key)
|
preferences.getStringOrNull(key)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStringSet(
|
override fun getStringSet(
|
||||||
key: String,
|
key: String,
|
||||||
@@ -97,7 +96,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
} else {
|
} else {
|
||||||
preferences.decodeValueOrNull(SetSerializer(String.serializer()), key)
|
preferences.decodeValueOrNull(SetSerializer(String.serializer()), key)
|
||||||
}
|
}
|
||||||
} catch (e: SerializationException) {
|
} catch (_: SerializationException) {
|
||||||
throw ClassCastException("$key was not a StringSet")
|
throw ClassCastException("$key was not a StringSet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,50 +104,48 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
override fun getInt(
|
override fun getInt(
|
||||||
key: String,
|
key: String,
|
||||||
defValue: Int,
|
defValue: Int,
|
||||||
): Int {
|
): Int = preferences.getInt(key, defValue)
|
||||||
return preferences.getInt(key, defValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLong(
|
override fun getLong(
|
||||||
key: String,
|
key: String,
|
||||||
defValue: Long,
|
defValue: Long,
|
||||||
): Long {
|
): Long = preferences.getLong(key, defValue)
|
||||||
return preferences.getLong(key, defValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFloat(
|
override fun getFloat(
|
||||||
key: String,
|
key: String,
|
||||||
defValue: Float,
|
defValue: Float,
|
||||||
): Float {
|
): Float = preferences.getFloat(key, defValue)
|
||||||
return preferences.getFloat(key, defValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBoolean(
|
override fun getBoolean(
|
||||||
key: String,
|
key: String,
|
||||||
defValue: Boolean,
|
defValue: Boolean,
|
||||||
): Boolean {
|
): Boolean = preferences.getBoolean(key, defValue)
|
||||||
return 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) { key ->
|
Editor(preferences) { key ->
|
||||||
listeners.forEach { (_, listener) ->
|
listeners.forEach { (_, listener) ->
|
||||||
listener(key)
|
listener(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class Editor(private val preferences: Settings, private val notify: (String) -> Unit) : SharedPreferences.Editor {
|
class Editor(
|
||||||
|
private val preferences: Settings,
|
||||||
|
private val notify: (String) -> Unit,
|
||||||
|
) : SharedPreferences.Editor {
|
||||||
private val actions = mutableListOf<Action>()
|
private val actions = mutableListOf<Action>()
|
||||||
|
|
||||||
private sealed class Action {
|
private sealed class Action {
|
||||||
data class Add(val key: String, val value: Any) : Action()
|
data class Add(
|
||||||
|
val key: String,
|
||||||
|
val value: Any,
|
||||||
|
) : Action()
|
||||||
|
|
||||||
|
data class Remove(
|
||||||
|
val key: String,
|
||||||
|
) : Action()
|
||||||
|
|
||||||
data class Remove(val key: String) : Action()
|
|
||||||
data object Clear : Action()
|
data object Clear : Action()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,10 +153,11 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
key: String,
|
key: String,
|
||||||
value: String?,
|
value: String?,
|
||||||
): SharedPreferences.Editor {
|
): SharedPreferences.Editor {
|
||||||
|
actions +=
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
actions += Action.Add(key, value)
|
Action.Add(key, value)
|
||||||
} else {
|
} else {
|
||||||
actions += Action.Remove(key)
|
Action.Remove(key)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -168,10 +166,11 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
key: String,
|
key: String,
|
||||||
values: MutableSet<String>?,
|
values: MutableSet<String>?,
|
||||||
): SharedPreferences.Editor {
|
): SharedPreferences.Editor {
|
||||||
|
actions +=
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
actions += Action.Add(key, values)
|
Action.Add(key, values)
|
||||||
} else {
|
} else {
|
||||||
actions += Action.Remove(key)
|
Action.Remove(key)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -243,13 +242,14 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
}
|
}
|
||||||
notify(it.key)
|
notify(it.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Action.Remove -> {
|
is Action.Remove -> {
|
||||||
preferences.remove(it.key)
|
preferences.remove(it.key)
|
||||||
/**
|
/*
|
||||||
* Set<String> are stored like
|
Set<String> are stored like
|
||||||
* key.0 = value1
|
key.0 = value1
|
||||||
* key.1 = value2
|
key.1 = value2
|
||||||
* key.size = 2
|
key.size = 2
|
||||||
*/
|
*/
|
||||||
preferences.keys.forEach { key ->
|
preferences.keys.forEach { key ->
|
||||||
if (key.startsWith(it.key + ".")) {
|
if (key.startsWith(it.key + ".")) {
|
||||||
@@ -259,7 +259,10 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
|||||||
|
|
||||||
notify(it.key)
|
notify(it.key)
|
||||||
}
|
}
|
||||||
Action.Clear -> preferences.clear()
|
|
||||||
|
Action.Clear -> {
|
||||||
|
preferences.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ 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")
|
||||||
@@ -34,7 +36,10 @@ data class InstalledPackage(val root: File) {
|
|||||||
Bundle().apply {
|
Bundle().apply {
|
||||||
val appTag = doc.getElementsByTagName("application").item(0)
|
val appTag = doc.getElementsByTagName("application").item(0)
|
||||||
|
|
||||||
appTag?.childNodes?.toList()?.filter {
|
appTag
|
||||||
|
?.childNodes
|
||||||
|
?.toList()
|
||||||
|
?.filter {
|
||||||
it.nodeType == Node.ELEMENT_NODE
|
it.nodeType == Node.ELEMENT_NODE
|
||||||
}?.map {
|
}?.map {
|
||||||
it as Element
|
it as Element
|
||||||
@@ -53,12 +58,14 @@ data class InstalledPackage(val root: File) {
|
|||||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||||
// + parsed.apkV2Singers.flatMap { it.certificateMetas }
|
// + parsed.apkV2Singers.flatMap { it.certificateMetas }
|
||||||
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||||
.map { Signature(it.data) }.toTypedArray()
|
.map { Signature(it.data) }
|
||||||
|
.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verify(): Boolean {
|
fun verify(): Boolean {
|
||||||
val res =
|
val res =
|
||||||
ApkVerifier.Builder(apk)
|
ApkVerifier
|
||||||
|
.Builder(apk)
|
||||||
.build()
|
.build()
|
||||||
.verify()
|
.verify()
|
||||||
|
|
||||||
@@ -70,11 +77,14 @@ data class InstalledPackage(val root: File) {
|
|||||||
val icons = ApkFile(apk).allIcons
|
val icons = ApkFile(apk).allIcons
|
||||||
|
|
||||||
val read =
|
val read =
|
||||||
icons.filter { it.isFile }.map {
|
icons
|
||||||
|
.filter { it.isFile }
|
||||||
|
.map {
|
||||||
it.data.inputStream().use {
|
it.data.inputStream().use {
|
||||||
ImageIO.read(it)
|
ImageIO.read(it)
|
||||||
}
|
}
|
||||||
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
|
}.sortedByDescending { it.width * it.height }
|
||||||
|
.firstOrNull() ?: return
|
||||||
|
|
||||||
ImageIO.write(read, "png", icon)
|
ImageIO.write(read, "png", icon)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -94,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) {
|
||||||
@@ -57,13 +55,15 @@ class PackageController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun listInstalled(): List<InstalledPackage> {
|
fun listInstalled(): List<InstalledPackage> =
|
||||||
return androidFiles.packagesDir.listFiles().orEmpty().filter {
|
androidFiles.packagesDir
|
||||||
|
.listFiles()
|
||||||
|
.orEmpty()
|
||||||
|
.filter {
|
||||||
it.isDirectory
|
it.isDirectory
|
||||||
}.map {
|
}.map {
|
||||||
InstalledPackage(it)
|
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!")
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ 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 =
|
it.reqFeatures =
|
||||||
usesFeatures.map {
|
usesFeatures
|
||||||
|
.map {
|
||||||
FeatureInfo().apply {
|
FeatureInfo().apply {
|
||||||
name = it.name
|
name = it.name
|
||||||
}
|
}
|
||||||
@@ -26,4 +27,3 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
|||||||
sourceDir = apk.absolutePath
|
sourceDir = apk.absolutePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package xyz.nulldev.androidcompat.res;
|
package xyz.nulldev.androidcompat.res;
|
||||||
|
|
||||||
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
|
||||||
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
|
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@@ -10,7 +10,7 @@ import java.util.Calendar;
|
|||||||
* BuildConfig compat class.
|
* BuildConfig compat class.
|
||||||
*/
|
*/
|
||||||
public class BuildConfigCompat {
|
public class BuildConfigCompat {
|
||||||
private static ApplicationInfoImpl applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class);
|
private static ApplicationInfoImpl applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class);
|
||||||
|
|
||||||
public static final boolean DEBUG = applicationInfo.getDebug();
|
public static final boolean DEBUG = applicationInfo.getDebug();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package xyz.nulldev.androidcompat.res
|
package xyz.nulldev.androidcompat.res
|
||||||
|
|
||||||
class DrawableResource(val location: String) : Resource {
|
class DrawableResource(
|
||||||
|
val location: String,
|
||||||
|
) : Resource {
|
||||||
override fun getType() = DrawableResource::class.java
|
override fun getType() = DrawableResource::class.java
|
||||||
|
|
||||||
override fun getValue() = javaClass.getResourceAsStream(location)
|
override fun getValue() = javaClass.getResourceAsStream(location)
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package xyz.nulldev.androidcompat.res
|
|||||||
/**
|
/**
|
||||||
* String resource.
|
* String resource.
|
||||||
*/
|
*/
|
||||||
class StringResource(val string: String) : Resource {
|
class StringResource(
|
||||||
|
val string: String,
|
||||||
|
) : Resource {
|
||||||
override fun getValue() = string
|
override fun getValue() = string
|
||||||
|
|
||||||
override fun getType() = StringResource::class.java
|
override fun getType() = StringResource::class.java
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package xyz.nulldev.androidcompat.service
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import mu.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
package xyz.nulldev.androidcompat.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.kodein.di.DI
|
|
||||||
import org.kodein.di.conf.global
|
|
||||||
import org.kodein.di.instance
|
|
||||||
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
|
||||||
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
|
|
||||||
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
|
|
||||||
import xyz.nulldev.androidcompat.io.AndroidFiles
|
|
||||||
import xyz.nulldev.androidcompat.pm.PackageController
|
|
||||||
import xyz.nulldev.androidcompat.service.ServiceSupport
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to allow access to Kodein from Java
|
|
||||||
*/
|
|
||||||
object KodeinGlobalHelper {
|
|
||||||
/**
|
|
||||||
* Get the Kodein object
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun kodein() = DI.global
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a dependency
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T : Any> instance(
|
|
||||||
type: Class<T>,
|
|
||||||
kodein: DI? = null,
|
|
||||||
): T {
|
|
||||||
return when (type) {
|
|
||||||
AndroidFiles::class.java -> {
|
|
||||||
val instance: AndroidFiles by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
ApplicationInfoImpl::class.java -> {
|
|
||||||
val instance: ApplicationInfoImpl by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
ServiceSupport::class.java -> {
|
|
||||||
val instance: ServiceSupport by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
FakePackageManager::class.java -> {
|
|
||||||
val instance: FakePackageManager by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
PackageController::class.java -> {
|
|
||||||
val instance: PackageController by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
CustomContext::class.java -> {
|
|
||||||
val instance: CustomContext by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
Context::class.java -> {
|
|
||||||
val instance: Context by (kodein ?: kodein()).instance()
|
|
||||||
instance as T
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("Kodein instance not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun <T : Any> instance(type: Class<T>): T {
|
|
||||||
return instance(type, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package xyz.nulldev.androidcompat.util
|
||||||
|
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.mp.KoinPlatformTools
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to allow access to Kodein from Java
|
||||||
|
*/
|
||||||
|
object KoinGlobalHelper {
|
||||||
|
/**
|
||||||
|
* Get the Kodein object
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun koin() = KoinPlatformTools.defaultContext().get()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a dependency
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun <T : Any> instance(
|
||||||
|
type: Class<T>,
|
||||||
|
koin: Koin? = null,
|
||||||
|
): T = (koin ?: koin()).get(type.kotlin)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun <T : Any> instance(type: Class<T>): T = instance(type, null)
|
||||||
|
}
|
||||||
@@ -18,9 +18,7 @@ class CookieManagerImpl : CookieManager() {
|
|||||||
acceptCookie = accept
|
acceptCookie = accept
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptCookie(): Boolean {
|
override fun acceptCookie(): Boolean = acceptCookie
|
||||||
return acceptCookie
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAcceptThirdPartyCookies(
|
override fun setAcceptThirdPartyCookies(
|
||||||
webview: WebView?,
|
webview: WebView?,
|
||||||
@@ -29,9 +27,7 @@ class CookieManagerImpl : CookieManager() {
|
|||||||
acceptThirdPartyCookies = accept
|
acceptThirdPartyCookies = accept
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptThirdPartyCookies(webview: WebView?): Boolean {
|
override fun acceptThirdPartyCookies(webview: WebView?): Boolean = acceptThirdPartyCookies
|
||||||
return acceptThirdPartyCookies
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setCookie(
|
override fun setCookie(
|
||||||
url: String,
|
url: String,
|
||||||
@@ -65,7 +61,8 @@ class CookieManagerImpl : CookieManager() {
|
|||||||
} else {
|
} else {
|
||||||
URI("http://$url")
|
URI("http://$url")
|
||||||
}
|
}
|
||||||
return cookieHandler.cookieStore.get(uri)
|
return cookieHandler.cookieStore
|
||||||
|
.get(uri)
|
||||||
.joinToString("; ") { "${it.name}=${it.value}" }
|
.joinToString("; ") { "${it.name}=${it.value}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,15 +84,11 @@ class CookieManagerImpl : CookieManager() {
|
|||||||
callback?.onReceiveValue(removedCookies)
|
callback?.onReceiveValue(removedCookies)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasCookies(): Boolean {
|
override fun hasCookies(): Boolean = cookieHandler.cookieStore.cookies.isNotEmpty()
|
||||||
return cookieHandler.cookieStore.cookies.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun flush() {}
|
override fun flush() {}
|
||||||
|
|
||||||
override fun allowFileSchemeCookiesImpl(): Boolean {
|
override fun allowFileSchemeCookiesImpl(): Boolean = allowFileSchemeCookies
|
||||||
return allowFileSchemeCookies
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
|
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
|
||||||
allowFileSchemeCookies = acceptCookie
|
allowFileSchemeCookies = acceptCookie
|
||||||
|
|||||||
@@ -0,0 +1,429 @@
|
|||||||
|
package xyz.nulldev.androidcompat.webkit
|
||||||
|
|
||||||
|
import android.webkit.WebSettings
|
||||||
|
|
||||||
|
class KcefWebSettings : WebSettings() {
|
||||||
|
// Boolean settings
|
||||||
|
private var navDumps = false
|
||||||
|
private var mediaPlaybackRequiresUserGesture = true
|
||||||
|
private var builtInZoomControls = false
|
||||||
|
private var displayZoomControls = true
|
||||||
|
private var allowFileAccess = true
|
||||||
|
private var allowContentAccess = true
|
||||||
|
private var pluginsEnabled = false
|
||||||
|
private var loadWithOverviewMode = false
|
||||||
|
private var enableSmoothTransition = false
|
||||||
|
private var useWebViewBackgroundForOverscrollBackground = false
|
||||||
|
private var saveFormData = true
|
||||||
|
private var savePassword = false
|
||||||
|
private var acceptThirdPartyCookies = true
|
||||||
|
private var lightTouchEnabled = false
|
||||||
|
private var useWideViewPort = true
|
||||||
|
private var supportMultipleWindows = false
|
||||||
|
private var loadsImagesAutomatically = true
|
||||||
|
private var blockNetworkImage = false
|
||||||
|
private var blockNetworkLoads = false
|
||||||
|
private var javaScriptEnabled = true
|
||||||
|
private var allowUniversalAccessFromFileURLs = false
|
||||||
|
private var allowFileAccessFromFileURLs = false
|
||||||
|
private var domStorageEnabled = true
|
||||||
|
private var geolocationEnabled = true
|
||||||
|
private var javaScriptCanOpenWindowsAutomatically = false
|
||||||
|
private var needInitialFocus = false
|
||||||
|
private var offscreenPreRaster = false
|
||||||
|
private var videoOverlayForEmbeddedEncryptedVideoEnabled = false
|
||||||
|
private var safeBrowsingEnabled = true
|
||||||
|
|
||||||
|
// Integer settings
|
||||||
|
private var textZoom = 100
|
||||||
|
private var minimumFontSize = 8
|
||||||
|
private var minimumLogicalFontSize = 8
|
||||||
|
private var defaultFontSize = 16
|
||||||
|
private var defaultFixedFontSize = 13
|
||||||
|
private var cacheMode = 0
|
||||||
|
private var mixedContentMode = 0
|
||||||
|
private var disabledActionModeMenuItems = 0
|
||||||
|
|
||||||
|
// String settings
|
||||||
|
private var databasePath: String? = null
|
||||||
|
private var geolocationDatabasePath: String? = null
|
||||||
|
private var appCachePath: String? = null
|
||||||
|
private var defaultTextEncodingName: String? = null
|
||||||
|
private var userAgentString: String? = null
|
||||||
|
private var standardFontFamily: String? = null
|
||||||
|
private var fixedFontFamily: String? = null
|
||||||
|
private var sansSerifFontFamily: String? = null
|
||||||
|
private var serifFontFamily: String? = null
|
||||||
|
private var cursiveFontFamily: String? = null
|
||||||
|
private var fantasyFontFamily: String? = null
|
||||||
|
|
||||||
|
// Enum settings
|
||||||
|
private var defaultZoom: ZoomDensity? = null
|
||||||
|
private var layoutAlgorithm: LayoutAlgorithm? = null
|
||||||
|
private var pluginState: PluginState? = null
|
||||||
|
private var renderPriority: RenderPriority? = null
|
||||||
|
|
||||||
|
// Long settings
|
||||||
|
private var appCacheMaxSize: Long = 0L
|
||||||
|
|
||||||
|
// Implementations
|
||||||
|
|
||||||
|
@SuppressWarnings("HiddenAbstractMethod")
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setNavDump(p0: Boolean) {
|
||||||
|
navDumps = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
@SuppressWarnings("HiddenAbstractMethod")
|
||||||
|
override fun getNavDump(): Boolean = navDumps
|
||||||
|
|
||||||
|
override fun setSupportZoom(p0: Boolean) {
|
||||||
|
mediaPlaybackRequiresUserGesture = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportZoom() = mediaPlaybackRequiresUserGesture
|
||||||
|
|
||||||
|
override fun setMediaPlaybackRequiresUserGesture(p0: Boolean) {
|
||||||
|
mediaPlaybackRequiresUserGesture = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMediaPlaybackRequiresUserGesture() = mediaPlaybackRequiresUserGesture
|
||||||
|
|
||||||
|
override fun setBuiltInZoomControls(p0: Boolean) {
|
||||||
|
builtInZoomControls = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBuiltInZoomControls() = builtInZoomControls
|
||||||
|
|
||||||
|
override fun setDisplayZoomControls(p0: Boolean) {
|
||||||
|
displayZoomControls = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayZoomControls() = displayZoomControls
|
||||||
|
|
||||||
|
override fun setAllowFileAccess(p0: Boolean) {
|
||||||
|
allowFileAccess = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllowFileAccess() = allowFileAccess
|
||||||
|
|
||||||
|
override fun setAllowContentAccess(p0: Boolean) {
|
||||||
|
allowContentAccess = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllowContentAccess() = allowContentAccess
|
||||||
|
|
||||||
|
override fun setLoadWithOverviewMode(p0: Boolean) {
|
||||||
|
loadWithOverviewMode = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLoadWithOverviewMode() = loadWithOverviewMode
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setPluginsEnabled(p0: Boolean) {
|
||||||
|
pluginsEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getPluginsEnabled() = pluginsEnabled
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setEnableSmoothTransition(p0: Boolean) {
|
||||||
|
enableSmoothTransition = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun enableSmoothTransition() = enableSmoothTransition
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setUseWebViewBackgroundForOverscrollBackground(p0: Boolean) {
|
||||||
|
useWebViewBackgroundForOverscrollBackground = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getUseWebViewBackgroundForOverscrollBackground() = useWebViewBackgroundForOverscrollBackground
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setSaveFormData(p0: Boolean) {
|
||||||
|
saveFormData = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getSaveFormData() = saveFormData
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setSavePassword(p0: Boolean) {
|
||||||
|
savePassword = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getSavePassword() = savePassword
|
||||||
|
|
||||||
|
override fun setTextZoom(p0: Int) {
|
||||||
|
textZoom = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTextZoom() = textZoom
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setDefaultZoom(p0: ZoomDensity?) {
|
||||||
|
defaultZoom = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAcceptThirdPartyCookies(p0: Boolean) {
|
||||||
|
acceptThirdPartyCookies = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAcceptThirdPartyCookies() = acceptThirdPartyCookies
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getDefaultZoom() = defaultZoom
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setLightTouchEnabled(p0: Boolean) {
|
||||||
|
lightTouchEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getLightTouchEnabled() = lightTouchEnabled
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setUserAgent(ua: Int) = throw RuntimeException("Stub!")
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getUserAgent(): Int = throw RuntimeException("Stub!")
|
||||||
|
|
||||||
|
override fun setUseWideViewPort(p0: Boolean) {
|
||||||
|
useWideViewPort = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUseWideViewPort() = useWideViewPort
|
||||||
|
|
||||||
|
override fun setSupportMultipleWindows(p0: Boolean) {
|
||||||
|
supportMultipleWindows = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportMultipleWindows() = supportMultipleWindows
|
||||||
|
|
||||||
|
override fun setLayoutAlgorithm(p0: LayoutAlgorithm?) {
|
||||||
|
layoutAlgorithm = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutAlgorithm() = layoutAlgorithm
|
||||||
|
|
||||||
|
override fun setStandardFontFamily(p0: String?) {
|
||||||
|
standardFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStandardFontFamily() = standardFontFamily
|
||||||
|
|
||||||
|
override fun setFixedFontFamily(p0: String?) {
|
||||||
|
fixedFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFixedFontFamily() = fixedFontFamily
|
||||||
|
|
||||||
|
override fun setSansSerifFontFamily(p0: String?) {
|
||||||
|
sansSerifFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSansSerifFontFamily() = sansSerifFontFamily
|
||||||
|
|
||||||
|
override fun setSerifFontFamily(p0: String?) {
|
||||||
|
serifFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSerifFontFamily() = serifFontFamily
|
||||||
|
|
||||||
|
override fun setCursiveFontFamily(p0: String?) {
|
||||||
|
cursiveFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCursiveFontFamily() = cursiveFontFamily
|
||||||
|
|
||||||
|
override fun setFantasyFontFamily(p0: String?) {
|
||||||
|
fantasyFontFamily = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFantasyFontFamily() = fantasyFontFamily
|
||||||
|
|
||||||
|
override fun setMinimumFontSize(p0: Int) {
|
||||||
|
minimumFontSize = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinimumFontSize() = minimumFontSize
|
||||||
|
|
||||||
|
override fun setMinimumLogicalFontSize(p0: Int) {
|
||||||
|
minimumLogicalFontSize = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinimumLogicalFontSize() = minimumLogicalFontSize
|
||||||
|
|
||||||
|
override fun setDefaultFontSize(p0: Int) {
|
||||||
|
defaultFontSize = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultFontSize() = defaultFontSize
|
||||||
|
|
||||||
|
override fun setDefaultFixedFontSize(p0: Int) {
|
||||||
|
defaultFixedFontSize = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultFixedFontSize() = defaultFixedFontSize
|
||||||
|
|
||||||
|
override fun setLoadsImagesAutomatically(p0: Boolean) {
|
||||||
|
loadsImagesAutomatically = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLoadsImagesAutomatically() = loadsImagesAutomatically
|
||||||
|
|
||||||
|
override fun setBlockNetworkImage(p0: Boolean) {
|
||||||
|
blockNetworkImage = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBlockNetworkImage() = blockNetworkImage
|
||||||
|
|
||||||
|
override fun setBlockNetworkLoads(p0: Boolean) {
|
||||||
|
blockNetworkLoads = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBlockNetworkLoads() = blockNetworkLoads
|
||||||
|
|
||||||
|
override fun setJavaScriptEnabled(p0: Boolean) {
|
||||||
|
javaScriptEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getJavaScriptEnabled() = javaScriptEnabled
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setAllowUniversalAccessFromFileURLs(p0: Boolean) {
|
||||||
|
allowUniversalAccessFromFileURLs = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllowUniversalAccessFromFileURLs() = allowUniversalAccessFromFileURLs
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setAllowFileAccessFromFileURLs(p0: Boolean) {
|
||||||
|
allowFileAccessFromFileURLs = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllowFileAccessFromFileURLs() = allowFileAccessFromFileURLs
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setPluginState(p0: PluginState?) {
|
||||||
|
pluginState = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getPluginState() = pluginState
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setDatabasePath(p0: String?) {
|
||||||
|
databasePath = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getDatabasePath() = databasePath ?: ""
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setGeolocationDatabasePath(p0: String?) {
|
||||||
|
geolocationDatabasePath = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setAppCacheEnabled(p0: Boolean) {}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setAppCachePath(p0: String?) {
|
||||||
|
appCachePath = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setAppCacheMaxSize(p0: Long) {
|
||||||
|
appCacheMaxSize = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setDatabaseEnabled(p0: Boolean) {}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun getDatabaseEnabled() = true
|
||||||
|
|
||||||
|
override fun setDomStorageEnabled(p0: Boolean) {
|
||||||
|
domStorageEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDomStorageEnabled() = domStorageEnabled
|
||||||
|
|
||||||
|
override fun setGeolocationEnabled(p0: Boolean) {
|
||||||
|
geolocationEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setJavaScriptCanOpenWindowsAutomatically(p0: Boolean) {
|
||||||
|
javaScriptCanOpenWindowsAutomatically = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getJavaScriptCanOpenWindowsAutomatically() = javaScriptCanOpenWindowsAutomatically
|
||||||
|
|
||||||
|
override fun setDefaultTextEncodingName(p0: String?) {
|
||||||
|
defaultTextEncodingName = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultTextEncodingName() = defaultTextEncodingName ?: ""
|
||||||
|
|
||||||
|
override fun setUserAgentString(p0: String?) {
|
||||||
|
userAgentString = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserAgentString() = userAgentString ?: defaultUserAgent()
|
||||||
|
|
||||||
|
override fun setNeedInitialFocus(p0: Boolean) {
|
||||||
|
needInitialFocus = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("inherit")
|
||||||
|
override fun setRenderPriority(p0: RenderPriority?) {
|
||||||
|
renderPriority = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCacheMode(p0: Int) {
|
||||||
|
cacheMode = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCacheMode() = cacheMode
|
||||||
|
|
||||||
|
override fun setMixedContentMode(p0: Int) {
|
||||||
|
mixedContentMode = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMixedContentMode() = mixedContentMode
|
||||||
|
|
||||||
|
override fun setOffscreenPreRaster(p0: Boolean) {
|
||||||
|
offscreenPreRaster = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOffscreenPreRaster() = offscreenPreRaster
|
||||||
|
|
||||||
|
override fun setVideoOverlayForEmbeddedEncryptedVideoEnabled(p0: Boolean) {
|
||||||
|
videoOverlayForEmbeddedEncryptedVideoEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVideoOverlayForEmbeddedEncryptedVideoEnabled() = videoOverlayForEmbeddedEncryptedVideoEnabled
|
||||||
|
|
||||||
|
override fun setSafeBrowsingEnabled(p0: Boolean) {
|
||||||
|
safeBrowsingEnabled = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSafeBrowsingEnabled() = safeBrowsingEnabled
|
||||||
|
|
||||||
|
override fun setDisabledActionModeMenuItems(p0: Int) {
|
||||||
|
disabledActionModeMenuItems = p0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisabledActionModeMenuItems() = disabledActionModeMenuItems
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun defaultUserAgent() = System.getProperty("http.agent")
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user