Compare commits
1904 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f02e2d23d0 | |||
| ef1c25c3d3 | |||
| 152cc27394 | |||
| c582aca465 | |||
| 80e85fb49a | |||
| d660e22e61 | |||
| 51856c4f06 | |||
| fd37da42f9 | |||
| 11df2bc51f | |||
| 6770d21cf7 | |||
| f490d1f6d2 | |||
| f890ae8ddc | |||
| 5d5d61d8ed | |||
| 75589f1b2d | |||
| 6225c676e2 | |||
| 9b18668f49 | |||
| 2f80e7f1ff | |||
| 790413680d | |||
| 47e9a4ec29 | |||
| defd5e8047 | |||
| 8c6a88374b | |||
| 7343613bea | |||
| 155dda1fa4 | |||
| 3c74306c8d | |||
| 13ecd9eee6 | |||
| c48f3b4582 | |||
| 30c007194d | |||
| ef5b68eb35 | |||
| c47dcd5720 | |||
| ed3c5ab479 | |||
| a697b6c3d4 | |||
| 3965df78c9 | |||
| 64ebf20c1b | |||
| 797bed6701 | |||
| e84e021127 | |||
| 0b9515b58b | |||
| 81ec9e96c7 | |||
| ee09793ef2 | |||
| 61a130e645 | |||
| 19d342749a | |||
| 94adcf04f5 | |||
| 53e1da0f43 | |||
| b41989de03 | |||
| 6c7848b750 | |||
| 07bd9ad840 | |||
| 14236d3062 | |||
| 6c4df30252 | |||
| 45218470af | |||
| 417ee1e047 | |||
| 08a3bc457e | |||
| 0cc2cba883 | |||
| 24d461c8b2 | |||
| 4d472fccd2 | |||
| 45d010bdb6 | |||
| 70db617229 | |||
| d8256013a3 | |||
| 6d2c22addc | |||
| 9640f3f215 | |||
| 80c911e118 | |||
| f2d5ea0391 | |||
| a94d77d81e | |||
| 2d2de1a652 | |||
| 01f8823fb2 | |||
| 260575d139 | |||
| 1fb3290038 | |||
| 37596320e8 | |||
| 7a959c2c3e | |||
| 877c03e6a1 | |||
| d3431d227b | |||
| fbf307bf01 | |||
| d672857e82 | |||
| dd934e0095 | |||
| 8c9df8d3be | |||
| b3aec58e69 | |||
| b4111cffef | |||
| ecc8d1738e | |||
| d1982cbc0a | |||
| 03b65ce6dc | |||
| 56ea11cdff | |||
| 7a02404f7b | |||
| b9a960a7c8 | |||
| 02c87a4d7b | |||
| 0dd9cd82f8 | |||
| ec486d66f7 | |||
| 69cd7eb449 | |||
| 1427de7c65 | |||
| 8ad66e1e5e | |||
| a2e31e97db | |||
| 1f3e131690 | |||
| 276b757e2d | |||
| 093df70602 | |||
| fe9ab66f31 | |||
| 138f9476ac | |||
| cb9ab61b6b | |||
| bcbd365326 | |||
| afdf4e365f | |||
| 553b7522aa | |||
| 13f38dd594 | |||
| 31e1c6f7aa | |||
| 02d060ca0a | |||
| 5e2a3ac644 | |||
| 2fc461b85f | |||
| 29a0b86411 | |||
| efc3e7b25d | |||
| 6c2adfeec2 | |||
| 3124d6d43e | |||
| e5a6b7d47d | |||
| add65cf592 | |||
| 129effd0ec | |||
| 2aad00df85 | |||
| 85e0e74bc6 | |||
| 7fa200401c | |||
| 1a452efbb9 | |||
| eb4bdf1db2 | |||
| d58f68cb44 | |||
| 2f30d29351 | |||
| dc6dc192dc | |||
| 751afadebd | |||
| ac71c02dfa | |||
| 4567da193e | |||
| bd2a1d5574 | |||
| ab44d608d2 | |||
| cdc7f1565e | |||
| 1493581a4d | |||
| 4461d6cf7f | |||
| 38e64b1f75 | |||
| eb1daf4a20 | |||
| e0c38f7c72 | |||
| 5638ff4a3a | |||
| f4ae39dd44 | |||
| d235125138 | |||
| 78d7759197 | |||
| eddaad3b05 | |||
| f2c80c800c | |||
| a0e787e424 | |||
| c9d1fb8533 | |||
| 006eebb09e | |||
| 4aec824bfd | |||
| 56f7564ce4 | |||
| f89daefd43 | |||
| 8572a2d262 | |||
| d6e41be4b4 | |||
| 5e715ffcce | |||
| 5100341e60 | |||
| 5ca4db6ea5 | |||
| e02e07ae7a | |||
| f3caedc045 | |||
| 59c49254e7 | |||
| ad81b310e3 | |||
| bf124b87fa | |||
| a4868602b5 | |||
| 763aeabddd | |||
| 71fc5af320 | |||
| d28f0e3544 | |||
| b4d0dde129 | |||
| 281630e751 | |||
| 32d79ead15 | |||
| 5a91c7e84a | |||
| e2e1200c89 | |||
| ed1be76606 | |||
| a64de91781 | |||
| e802c8b8cc | |||
| 86f2cf0ac4 | |||
| a844a6b6c1 | |||
| c31146e902 | |||
| fcc5db2fe6 | |||
| 9fdd3ae1be | |||
| c99509a967 | |||
| 1169331462 | |||
| 0978822939 | |||
| 8bf8ecf7fa | |||
| f55db6a5d7 | |||
| e82d6cf91d | |||
| 65a1d165ac | |||
| e3b27bd39c | |||
| 07dde01c3b | |||
| 11a2e8686c | |||
| daeeb17142 | |||
| 35ab2f6704 | |||
| 9c428f6db7 | |||
| a76b067b96 | |||
| a843619c5b | |||
| 5bbc4aea95 | |||
| 4676043826 | |||
| 64a841487f | |||
| cfd69f2da8 | |||
| e97a14f617 | |||
| b7118b6bd8 | |||
| 742da4ccb8 | |||
| 97296ca7d7 | |||
| 1486a9ae1b | |||
| a847e385cb | |||
| 134284723b | |||
| a6eb44ba95 | |||
| 1457738905 | |||
| 3dd0a60555 | |||
| 1eef18dcd3 | |||
| c86ee33371 | |||
| 60690208de | |||
| 69ebee3eeb | |||
| 4bdb367c19 | |||
| c817a3097d | |||
| b3aa25ad59 | |||
| cdddfd37d2 | |||
| 2547db2a8e | |||
| 9363f0ebb4 | |||
| 8d6d8019fe | |||
| e27089157d | |||
| 99fc75eeda | |||
| ec63dd704a | |||
| d46a9f6d1d | |||
| 4b7d87c6bc | |||
| 9c5a0ba7eb | |||
| e461625da4 | |||
| c393cd655d | |||
| ed8edb5aee | |||
| 5df1fa3c65 | |||
| e796968d19 | |||
| c8f17e2ab0 | |||
| 69870eb229 | |||
| 670aed2074 | |||
| 0020c7c6dc | |||
| 20b98122c1 | |||
| 35c102aa98 | |||
| fb316a22c6 | |||
| 5342af60cb | |||
| 3d01bd7c57 | |||
| 1b3ac83876 | |||
| 4ba3104bf3 | |||
| eda2b87a57 | |||
| ac0216d916 | |||
| d0986383ad | |||
| ab7f507b03 | |||
| e096ba27ce | |||
| 25ce2a649a | |||
| 0e200b1fb6 | |||
| 552b19cbb0 | |||
| 3f0377a840 | |||
| d9147874dd | |||
| 06ed124057 | |||
| 68aa97a676 | |||
| 20bb07e829 | |||
| 53b6cc21b1 | |||
| 96a80f0ed2 | |||
| 9ebb150b68 | |||
| 4ae7312c7f | |||
| 113393de8f | |||
| 5daa027c10 | |||
| 7394b4ac27 | |||
| 1259da01a5 | |||
| 86a8cd29e5 | |||
| bfc84d50dd | |||
| af060f52e1 | |||
| f87fc1d639 | |||
| 6ddfbcb945 | |||
| d4f11867a8 | |||
| 759f30244a | |||
| fcc49ae7b6 | |||
| 1aa8e9753d | |||
| f400504898 | |||
| 41e6097ac5 | |||
| 8e4b08b493 | |||
| df948179d8 | |||
| 7b3aa43217 | |||
| e42fe5349b | |||
| 1f578ebd2c | |||
| b80875773f | |||
| 3caebb8613 | |||
| 24ac705fe8 | |||
| 57acdd4b21 | |||
| fddba2906a | |||
| c42023855b | |||
| dd38dd9cae | |||
| 5f0341cd53 | |||
| e3d3129e6d | |||
| ed8b6c6bc9 | |||
| 2218fc0d41 | |||
| b8cbcfe986 | |||
| dbc5f5bfcc | |||
| 33cb02b9e4 | |||
| 8783d150e8 | |||
| 449ea9375e | |||
| d5a73a3380 | |||
| 173dd180d9 | |||
| f332cbf1bc | |||
| c4d317b33e | |||
| 9c59d6a69b | |||
| 61bdd4e027 | |||
| ab2efe78b1 | |||
| c3af3e4740 | |||
| 6418eac658 | |||
| d74e9f7410 | |||
| 569c83d90e | |||
| 1dc3cf7824 | |||
| d8dead82b6 | |||
| b053fbc4a7 | |||
| 2144dc3b67 | |||
| 243b4b9414 | |||
| e068fde8f2 | |||
| 3162f04937 | |||
| 58a32c11ec | |||
| f06817f00d | |||
| da4be5c1cf | |||
| a59f5d953a | |||
| 815a988587 | |||
| b65d9ffaed | |||
| 0fc73c3a6f | |||
| 57fdc1b223 | |||
| 22d5fc6cba | |||
| c36f9646f9 | |||
| 45e11f6291 | |||
| 2893c3dc0e | |||
| 116022b01d | |||
| 09cba8774d | |||
| 41129f7c50 | |||
| 44d014c445 | |||
| 51e086b20e | |||
| d71a5c99f4 | |||
| fb0243a029 | |||
| 713441d9cb | |||
| 451f0fd12b | |||
| 5a84fa5a80 | |||
| 751ba8d1c2 | |||
| 974ed439a4 | |||
| 0172c1e385 | |||
| faa19acf81 | |||
| 1f9afb6c6e | |||
| 5d96bc2d3a | |||
| 9366596f5f | |||
| cb6e3ade15 | |||
| 45178b3eb3 | |||
| 31e3e37c9b | |||
| e1489bb407 | |||
| 8b50d8645a | |||
| 796fdb1cf6 | |||
| 5203d40804 | |||
| 21252aad0f | |||
| 0c535904fc | |||
| 490944a02a | |||
| ace85df9b7 | |||
| 9e56441d4a | |||
| d83c3d35eb | |||
| 8f26d63d6f | |||
| 75c520097a | |||
| 38375982dd | |||
| d24a71bbd2 | |||
| 7964c9fca7 | |||
| ec07e4b233 | |||
| b4266b8575 | |||
| 07201203b2 | |||
| e7c5eb93dd | |||
| e70229c672 | |||
| 86c5b28562 | |||
| a9149c5dc0 | |||
| a64430c65f | |||
| 75aab4c031 | |||
| 6f8be3260c | |||
| e74460bd91 | |||
| c25250cb05 | |||
| 42c3cc5296 | |||
| e4b3f90457 | |||
| 992b04f8c5 | |||
| d1e0f3646a | |||
| b4ba565923 | |||
| 006e7dc736 | |||
| 5ed6407ea3 | |||
| faf6b5a4e4 | |||
| f92891895e | |||
| d8cc3c86b4 | |||
| e7f233db5b | |||
| fd9c420dc8 | |||
| dc9fceb8cf | |||
| dc9b8169c0 | |||
| 38caf1e2b7 | |||
| 4b3e7c8858 | |||
| 2be3068675 | |||
| 682e47c7b3 | |||
| 8c90c3ad81 | |||
| d5afcc4aec | |||
| 14ed4201c0 | |||
| 1b9efeb049 | |||
| 4b862cf4c7 | |||
| 4e7683961e | |||
| ce9d44d010 | |||
| 32d052259f | |||
| fb98874948 | |||
| 55a62ead05 | |||
| f81f50646e | |||
| f707c1d02c | |||
| 417c04bf1c | |||
| 8f7f836598 | |||
| 32aea8d154 | |||
| 3f6c8cb622 | |||
| 8f6ff215aa | |||
| 4f01bacb49 | |||
| e6f4b0976f | |||
| e0d9c3f149 | |||
| 9d3cebf430 | |||
| 5e106bf510 | |||
| fc617fb7a9 | |||
| b88ecb6370 | |||
| 9b21001953 | |||
| cb0e10c7ab | |||
| 69884935f3 | |||
| b809008291 | |||
| 79e77f871e | |||
| 32ac6e3429 | |||
| da56c2790f | |||
| 687192f071 | |||
| 2e82ee0aaf | |||
| 4dacf4e342 | |||
| b91f04316a | |||
| 3c4252a933 | |||
| be4b687e48 | |||
| 8950100bd7 | |||
| d651716d99 | |||
| 270606699b | |||
| cc7617a302 | |||
| c04b5f2085 | |||
| 28f3ded4bd | |||
| d56607a686 | |||
| f8dde57133 | |||
| f2ea13a142 | |||
| 69a1fa0d3c | |||
| 5cb54b9ad7 | |||
| ee610cadd3 | |||
| acf131308b | |||
| c591ec3185 | |||
| 264a245d27 | |||
| f6c25d2a8b | |||
| d069d9331c | |||
| ba14031945 | |||
| 0b639e0169 | |||
| afee8631e1 | |||
| 214cb25d1b | |||
| a1457d22d6 | |||
| 82107e2938 | |||
| e2f5fa6962 | |||
| 74336041ea | |||
| 0dea5eb779 | |||
| 267f759452 | |||
| 2779d7efc5 | |||
| 04b7cb15cc | |||
| eba04eb75b | |||
| d924fc92ab | |||
| 69de830a10 | |||
| 60b9811e08 | |||
| 01418c0e36 | |||
| 3f374eebc2 | |||
| 502a2ead00 | |||
| 0d9490e1fb | |||
| 4afb459b30 | |||
| 37f4557fef | |||
| 424979d91f | |||
| 527fbee41e | |||
| 1935b0ebdd | |||
| f6aaef1434 | |||
| 050b59f09d | |||
| db8dcf6073 | |||
| c45002d5b6 | |||
| 3cd9f0ffef | |||
| d065a6f563 | |||
| 49d7a032fb | |||
| 80f3504098 | |||
| 37d971859b | |||
| 2a5bed1d21 | |||
| 0927914c57 | |||
| dda0e0393e | |||
| cc9be7b61e | |||
| 2751076089 | |||
| 2a3f85008b | |||
| 432a732e7c | |||
| dc6045ca8b | |||
| b58b0fd7a8 | |||
| 5e122353e1 | |||
| fc41fb5014 | |||
| 99477c8eef | |||
| f50466f779 | |||
| eb79300fe2 | |||
| b35c96b0b6 | |||
| 2282cd12d7 | |||
| 0cbe992912 | |||
| 98cb6b457c | |||
| 29d66f2b92 | |||
| 763a12dbc6 | |||
| 158f3d898f | |||
| b935999548 | |||
| 2cca6a5afb | |||
| 2954c31b5f | |||
| 6c2d21125e | |||
| 59d69192c6 | |||
| 937a288cee | |||
| 236e1ba885 | |||
| 3bdf2e7e2c | |||
| a160af2d11 | |||
| 341a31da00 | |||
| e0128e7e31 | |||
| 8f51bdcb78 | |||
| 53dc5bab43 | |||
| 8f86de1764 | |||
| 133a7d2576 | |||
| 8b7506ed2d | |||
| c378e4413e | |||
| f3182ddbc6 | |||
| 951d4ad06f | |||
| 2678a00781 | |||
| e6e8786d86 | |||
| 1ec3782f64 | |||
| a4ec31eebe | |||
| 94b631ccfe | |||
| 26d8df5ea9 | |||
| 0569d0555f | |||
| 3e2349c4ff | |||
| af7e736de9 | |||
| 51879a9c46 | |||
| 524f3d6d08 | |||
| 64fe78ff9a | |||
| e798f3f276 | |||
| ddb04c6ea3 | |||
| 213ffdab62 | |||
| 60354b2f1f | |||
| 4bb214cb2a | |||
| cfd4399685 | |||
| 30563ed3e5 | |||
| 46344776a4 | |||
| 0d215d609b | |||
| c15ea8c0b4 | |||
| d6061fb699 | |||
| 7f2b6178d5 | |||
| 53177bf40e | |||
| 857b945410 | |||
| 904593c103 | |||
| dcfa7e3b36 | |||
| 589f345825 | |||
| 0b7c22886d | |||
| e9e2846532 | |||
| e0fc191883 | |||
| b2ecd89a71 | |||
| 9ed95a6081 | |||
| 3f51f89d86 | |||
| 01778f718a | |||
| 7d5ddd8eac | |||
| 2447601219 | |||
| 701e43c13d | |||
| bbbccccf47 | |||
| 1e9ca0a9bf | |||
| 0b62bb8168 | |||
| 4f9f62992f | |||
| 1938d6cae0 | |||
| 13e8c55781 | |||
| 6264f9b585 | |||
| 4482bfcabb | |||
| 015088a53f | |||
| ef7d707432 | |||
| d1f6a924fb | |||
| f312757daf | |||
| 1d83729e6c | |||
| 6a45858b4a | |||
| 1b448c2bdf | |||
| f6cd190245 | |||
| 23303e5407 | |||
| b5237848e9 | |||
| 7cac0c9a7c | |||
| 9dbbe4675f | |||
| 95978f16e9 | |||
| d055bba452 | |||
| 8ef809a02b | |||
| 458941f952 | |||
| 5852a508aa | |||
| e2b4995fbb | |||
| a3556d9f68 | |||
| fe890a1a41 | |||
| c06bb18249 | |||
| 9099969b41 | |||
| 6358f59f67 | |||
| 073034dd3c | |||
| 17fb815805 | |||
| 409e7c41b4 | |||
| b9a1a5027c | |||
| 49535f6378 | |||
| c058452605 | |||
| b3511dba77 | |||
| afbe27c55f | |||
| 41d227207d | |||
| 92b586c061 | |||
| acbc17c909 | |||
| 15f17747ee | |||
| 781054fc9d | |||
| b59769a30a | |||
| 26e0e09e24 | |||
| 3a2990a911 | |||
| d8060b3041 | |||
| f42ec5318f | |||
| bd0d425cbf | |||
| b3d5d7c33e | |||
| 1746869dc3 | |||
| 633f4cbbe5 | |||
| 0944e2f758 | |||
| b49e4004ab | |||
| 68381f8b64 | |||
| f180066058 | |||
| 6b7de2e85e | |||
| c650a978e9 | |||
| e05cadafe6 | |||
| c6008a4f90 | |||
| 5624855eba | |||
| 799ff86fc0 | |||
| 798fc84e82 | |||
| cc363a3c88 | |||
| 6fdaef1f58 | |||
| 6db6c93295 | |||
| 4fb0f30d32 | |||
| 7fa4eb079b | |||
| c5392b8844 | |||
| e460973957 | |||
| e1c6311a18 | |||
| ed11e2f05a | |||
| 3182e5af88 | |||
| 5cfdf626fe | |||
| e55834d523 | |||
| 9d5a52a980 | |||
| 5649c906a5 | |||
| ee548d27e5 | |||
| 1dc737b5e5 | |||
| 427869d4ca | |||
| 36395ced89 | |||
| 52a9f2c893 | |||
| bd88be2513 | |||
| 7107c1d6b2 | |||
| 51f4a343c9 | |||
| a12ee1b78b | |||
| 4bf59a55da | |||
| dbac9bf9f6 | |||
| b95083fe92 | |||
| e2d297eb8a | |||
| a3176bbb67 | |||
| 4203dde151 | |||
| 880661710f | |||
| d844fa0fb5 | |||
| 18ede2e900 | |||
| 06cc96bee7 | |||
| 43a12d2a81 | |||
| 0a29ffcf4c | |||
| 9c88532c21 | |||
| f3450b8f10 | |||
| a4d56e376f | |||
| 24b5bac589 | |||
| 762f17f1c1 | |||
| 105c8c9745 | |||
| 93d99287eb | |||
| ce156c3450 | |||
| 7db16e6156 | |||
| e80033c287 | |||
| 1553f9b75d | |||
| c244a98962 | |||
| a8ad1e718e | |||
| b5712f4bd1 | |||
| 6bcb0de43d | |||
| 80651d2425 | |||
| 46492b8238 | |||
| 1be561543c | |||
| e430a46e20 | |||
| 8d187c8ba1 | |||
| eacf03768f | |||
| b077c9b4f3 | |||
| 893749fcab | |||
| 848ead5e78 | |||
| 9c47acb004 | |||
| 8ca54bcc7b | |||
| 7e64d57ba8 | |||
| a517fc4e15 | |||
| 4f4aea22ce | |||
| e0ea2bdde4 | |||
| d40dc1d90b | |||
| 4571151e3c | |||
| 3e43963f67 | |||
| fe71d6ac41 | |||
| 0514950333 | |||
| a2dc781840 | |||
| 2c1c6fab35 | |||
| 3c2e428c54 | |||
| 8f7fe5c3ee | |||
| 93e9dd6425 | |||
| c95f0fce6e | |||
| a3c7e7e552 | |||
| 1e2590af49 | |||
| 562e608e1f | |||
| 417d5a2804 | |||
| c0c8d2caa7 | |||
| 727175e4f4 | |||
| 577d2b13ca | |||
| 6ac2f922e2 | |||
| 98297e55c1 | |||
| aa2094a2cc | |||
| f8c053cc96 | |||
| 790f8426ac | |||
| ff11609a82 | |||
| 94346033a8 | |||
| cb1401f556 | |||
| ae676d7486 | |||
| 2d39e43677 | |||
| 0ccc7e3c06 | |||
| 2d20ceea01 | |||
| cee2702fdf | |||
| 6c94be70dc | |||
| f24020e7b7 | |||
| 728f1707b6 | |||
| adea15df10 | |||
| be91f2396c | |||
| 8724d904b7 | |||
| ef95479157 | |||
| 710cd23537 | |||
| 0af313a81f | |||
| 71be388989 | |||
| db3098f633 | |||
| ac197f42f2 | |||
| d82882ba28 | |||
| 957a12875d | |||
| 796eb5043c | |||
| 4f8d86828f | |||
| 5370605815 | |||
| d5fb71b63f | |||
| 2455c291d8 | |||
| 80ad28e9cc | |||
| 74552ba545 | |||
| 141cab1105 | |||
| f012a41345 | |||
| 1f95df60d4 | |||
| 560c8c8cac | |||
| 7cd79f8a94 | |||
| 667304c81e | |||
| 2dd95c6ef6 | |||
| 29e66e1d47 | |||
| 5eb5af2f87 | |||
| e47b62805b | |||
| 57adc73e95 | |||
| 8f4d64d37a | |||
| 9ce3813044 | |||
| 6436e2836d | |||
| 77c83019d0 | |||
| e6dfe96569 | |||
| 5d515198e6 | |||
| 1d912c0db2 | |||
| bac04dea8d | |||
| 3b39d13412 | |||
| 9838b2cf0a | |||
| 0ac56ca571 | |||
| 12321bc2f0 | |||
| 3a55dfa32f | |||
| 373972f5dc | |||
| 60a701f84f | |||
| 14f7c01fcb | |||
| caf4f1a7ba | |||
| eb55ac9a97 | |||
| b9d8868aab | |||
| bec03534ef | |||
| 565eab9dc1 | |||
| 4d229862b6 | |||
| 3739eb7731 | |||
| ae5f9fb8ac | |||
| 4320a81846 | |||
| 9fcf40fdc4 | |||
| 79d6ac100c | |||
| a3e3153ee3 | |||
| 0f525d2b07 | |||
| 8de3f5045b | |||
| fba4ae91e3 | |||
| dda68d6c95 | |||
| 25af25cd19 | |||
| dfd5b2c225 | |||
| e850d8e917 | |||
| 677cf725a1 | |||
| e95bb9cb0f | |||
| 2c223a5826 | |||
| bbc346bd7a | |||
| cf32b93269 | |||
| 84f1da76ad | |||
| e845fba8b3 | |||
| 01152ead61 | |||
| 198281aa47 | |||
| 8e8d86606b | |||
| b4c2e21415 | |||
| 6080e1f338 | |||
| 6dd3fdaa55 | |||
| 64312f9c7f | |||
| 86542febf9 | |||
| 9da49f9f8a | |||
| ce3872ce1a | |||
| c466dba8c4 | |||
| 46d412a6c3 | |||
| e2872d9af8 | |||
| 3474b26f61 | |||
| 740e934e5d | |||
| 61c5fc1057 | |||
| 7ef77bf16c | |||
| aa3eb78956 | |||
| cdd7b2deb9 | |||
| c27300c19d | |||
| 8927971a19 | |||
| 1ced115b54 | |||
| fcbd594def | |||
| 4b8d02fdba | |||
| e10284bd13 | |||
| 4b5f1d64e6 | |||
| b7477d287b | |||
| 6bab6c2454 | |||
| 586c45616c | |||
| ccd405fdce | |||
| dbf78d1b69 | |||
| 5f947ea2d6 | |||
| 73afa82147 | |||
| 744b79419b | |||
| ce20dd97ff | |||
| 3983d5aca4 | |||
| 7b0de2d2a9 | |||
| 2b65482abd | |||
| fe01e80af5 | |||
| fc43a0d8e9 | |||
| e709cdc9d5 | |||
| d2d698f64e | |||
| 7f1e33be32 | |||
| 443f1a1554 | |||
| ebb025c40a | |||
| f3ce582fa5 | |||
| 372744178e | |||
| fc3aa96b5a | |||
| f4c723cc60 | |||
| 7864c8ceb4 | |||
| 4c80aac4d6 | |||
| e2b6e85431 | |||
| 8587153ddd | |||
| 21956e400f | |||
| fa7346f79b | |||
| 7227b43bbe | |||
| e8c75249f1 | |||
| cc5628cbce | |||
| 441808b1df | |||
| 42b0fe7853 | |||
| 7877f5db2f | |||
| b972e05660 | |||
| 23579a9b1d | |||
| af99753d47 | |||
| 4b2366e537 | |||
| bea72c2ee3 | |||
| 32a50fcfad | |||
| 30fa741365 | |||
| bed2544ff4 | |||
| 5a773de3b1 | |||
| 924405c8ba | |||
| 93e9de3932 | |||
| a8dd81eace | |||
| ec8793c6fe | |||
| ffc0a230be | |||
| 5d4922ed8d | |||
| 974c33fe37 | |||
| 3f2b4d60fd | |||
| ca633b13af | |||
| a671e152bd | |||
| a564aae80a | |||
| 9f8e31db78 | |||
| 84e9282f87 | |||
| 3949f4fd45 | |||
| 944a180b68 | |||
| 9cd1a12b6a | |||
| a4a2d2fc0d | |||
| 6df839612d | |||
| dd630abd0e | |||
| 6826c0ded5 | |||
| f1d0d4f81b | |||
| bfa56f771d | |||
| 167b9c13e5 | |||
| 4b7d9a3b9d | |||
| c7585c5594 | |||
| c3d7b88cf6 | |||
| dc4ce234b7 | |||
| 12330b0aff | |||
| edb2a17bcb | |||
| 00b6416583 | |||
| 62297f1f98 | |||
| c00b0727e3 | |||
| 13616b9820 | |||
| 6530e1d937 | |||
| aff00615cb | |||
| be53bfa88f | |||
| 5de50f1a8b | |||
| 61886ea10a | |||
| ea94f6bc91 | |||
| 6080c18c90 | |||
| 595d5dddbe | |||
| 9b81e7f71b | |||
| bdc6c8c65a | |||
| 2dcc7d284f | |||
| 234e4be924 | |||
| 1083e022cc | |||
| cb1b4ec0b9 | |||
| 40c46351e6 | |||
| 3f75e4aeb3 | |||
| 4321fabf0b | |||
| 8e93bf9075 | |||
| 831cd2f297 | |||
| 42d61518b3 | |||
| 112782ccaf | |||
| 67a3a30d4c | |||
| 898d92ba54 | |||
| 323a405004 | |||
| 3f25609561 | |||
| 97047bccde | |||
| 31960b53a0 | |||
| ac41f3d662 | |||
| 82eebbc3b0 | |||
| b1d74e21e2 | |||
| 7868c3094b | |||
| ebaa4cee65 | |||
| 141b22765e | |||
| 050fad3114 | |||
| 01f143667f | |||
| 2729eb9f5f | |||
| 0a8e0d7889 | |||
| 25bffa6d56 | |||
| cf7fb7e1a2 | |||
| 4b7017580c | |||
| 90852b5715 | |||
| 2103fd016b | |||
| 973ad55dfe | |||
| c3dea97857 | |||
| 0e37381179 | |||
| f7bc975534 | |||
| fab24bcd1e | |||
| 9be2e6b815 | |||
| 4037170b4a | |||
| b1974f31a9 | |||
| e6bf8f078d | |||
| cea4ee4ea9 | |||
| 283ff44da9 | |||
| adee104899 | |||
| 1a844abcec | |||
| 5f30745908 | |||
| 4ae0f3999c | |||
| dcb16378c8 | |||
| b59a5c8609 | |||
| 55c9124c54 | |||
| 1376b4c0b8 | |||
| 9333e4fb68 | |||
| 04d3faf057 | |||
| bcfbed9b3f | |||
| dda51bf367 | |||
| a324288d97 | |||
| f21d2a2187 | |||
| 5272fec948 | |||
| fe11ebce55 | |||
| 221cf56ddc | |||
| 7efd8be238 | |||
| 105862b524 | |||
| cce8cdc7bf | |||
| 834c2c2495 | |||
| 59f7ee6682 | |||
| 6cbd68fe9f | |||
| e1bf23251f | |||
| 3aebadd90d | |||
| e57a35ab3e | |||
| 13c014215d | |||
| 02931f1826 | |||
| a640d9e298 | |||
| ce68da1613 | |||
| 3599122ca6 | |||
| 0003830a42 | |||
| 3804a89619 | |||
| d4748efd42 | |||
| 0bda1d46a2 | |||
| 43e3ef2bee | |||
| ce44e3949c | |||
| 7bb1262571 | |||
| 39f1aea8e3 | |||
| bda19d01ed | |||
| 1f5364f01d | |||
| 65e88d2d1c | |||
| cef8aa67dd | |||
| 5941b22eb6 | |||
| 9e7c55847e | |||
| 5209b74605 | |||
| b90a74d26a | |||
| 8c1737e597 | |||
| 2ea5bd2d44 | |||
| 4166e7931e | |||
| 89f2c25d73 | |||
| abb1ca2afe | |||
| f7befd1593 | |||
| 28511de23c | |||
| 2ff3d1b7c5 | |||
| fe6ae7e142 | |||
| 0da6c83ce4 | |||
| 184b7db43c | |||
| e442e34c1b | |||
| 011efb0ce7 | |||
| 63d00f87d8 | |||
| 0323858145 | |||
| a70e8ec7a7 | |||
| b306a3ef41 | |||
| ccd3467a61 | |||
| 40338afe7a | |||
| ff97f6af56 | |||
| 6e7858e00f | |||
| 95468c85a8 | |||
| f59e10d82c | |||
| 930370783e | |||
| 75062ada8a | |||
| 23618923d8 | |||
| f1d3a2f322 | |||
| 3b7fbbaf6e | |||
| 725d793b20 | |||
| 5c3baca055 | |||
| 6e5abc92a0 | |||
| 8df6e95781 | |||
| 2290a6c0df | |||
| 907e8d93a3 | |||
| 918497fb94 | |||
| 3ccd6304c7 | |||
| 51d47adf57 | |||
| f1e5206f56 | |||
| f410635e2c | |||
| 302d57bf19 | |||
| 4c301a49b4 | |||
| 4ecfee292e | |||
| 2a193ef455 | |||
| 96e241ef9c | |||
| e294a895e8 | |||
| 003b9b1551 | |||
| a4e4af502e | |||
| 06aada20c1 | |||
| 2dace38d43 | |||
| 554aa1ddf0 | |||
| 3b2a5f1ce3 | |||
| 3fc4b098e8 | |||
| a7d672f6b4 | |||
| 7e347f5cce | |||
| 4eaa6ebb47 | |||
| e85ef6881d | |||
| b1f6786392 | |||
| 696fffb603 | |||
| 3bb366ee04 | |||
| 6a59974f89 | |||
| 8e39267c42 | |||
| b937534ce5 | |||
| f5b46f7356 | |||
| cd58c09be3 | |||
| e8f0038c36 | |||
| 0b77b33902 | |||
| c3b5323010 | |||
| 81eaae4070 | |||
| 65461ce86f | |||
| 536e3139a2 | |||
| e9c7b120a0 | |||
| d6a230a235 | |||
| 6bf300ada8 | |||
| d307db8a95 | |||
| c4c32d80b2 | |||
| f4c1e34402 | |||
| 0068d62122 | |||
| 3f1fa59e09 | |||
| df5114c62c | |||
| 956e3924ff | |||
| 20ad166e0f | |||
| 12ea88f409 | |||
| 0c5648bfb1 | |||
| 91ca19f294 | |||
| 71250afd2c | |||
| cfdef7bca7 | |||
| 872f935fd5 | |||
| 0ed1f73990 | |||
| 349a2f72cb | |||
| 2b4a4d6109 | |||
| 9f882d2fbb | |||
| cb4a9730aa | |||
| e0657d09d8 | |||
| 01b9cb13b4 | |||
| 2c7260557c | |||
| 9e5156ab73 | |||
| 3dc1614fbc | |||
| 2f69a9c38e | |||
| 5e536c3fa5 | |||
| 6bb9d27d4e | |||
| 2d1bf33902 | |||
| 985a220fca | |||
| 31e137cf6d | |||
| f796447815 | |||
| 936e772ba0 | |||
| ecee797d00 | |||
| 357a8fc124 | |||
| 1233af0ddd | |||
| a264d10685 | |||
| ed17701a0a | |||
| 49e1ccea28 | |||
| 4c43b0d1e3 | |||
| 5ce09defca | |||
| da9064b714 | |||
| 7bb53e4b06 | |||
| 6a4ce1b658 | |||
| f84595e1e8 | |||
| 41d5c54033 | |||
| b9d6b63c09 | |||
| 506ad0b3f1 | |||
| c8302174a9 | |||
| 39cebfbb4e | |||
| d36ec9af47 | |||
| 5f6d971bf7 | |||
| 7a722d92a3 | |||
| 0bf0eba450 | |||
| d40783f794 | |||
| 88733473e2 | |||
| 7b65533095 | |||
| 52b533c121 | |||
| a4fa2e14fb | |||
| 6933f1d818 | |||
| b5d6cb2a8d | |||
| d1478c5ce0 | |||
| fbe62f0f3e | |||
| f84705b756 | |||
| cf2189c11a | |||
| dfc4178252 | |||
| 07952f2146 | |||
| a90dad22a9 | |||
| 64f7330609 | |||
| 5e382c120b | |||
| 3eea568f5f | |||
| 0077b29d6e | |||
| dfa6306b61 | |||
| a4bf075a1a | |||
| 373d622535 | |||
| ba1df58eb3 | |||
| 9fb85f7c76 | |||
| 5e58f0a212 | |||
| 8fa01f13e9 | |||
| 4ce136be17 | |||
| 4099154dc0 | |||
| 3f983a5c82 | |||
| 9743e3689a | |||
| 1363f55f77 | |||
| f1d98f6c7b | |||
| 9279a54d28 | |||
| 81889d8130 | |||
| 6aecb8fbc1 | |||
| 8aa413032d | |||
| 5bc4686eb8 | |||
| f676d1c61c | |||
| ac54b5cbdf | |||
| b4b1e5b605 | |||
| 5eace49739 | |||
| e93d7518f3 | |||
| 9c97cd8816 | |||
| 90f20c36c5 | |||
| 9f8dd7992a | |||
| f4d3fe9176 | |||
| ffc7c13717 | |||
| daf93c473b | |||
| d21782696a | |||
| 3357475fc4 | |||
| ead64d92a5 | |||
| 5eaac6cb17 | |||
| b3f0a44f10 | |||
| e4d0e2f730 | |||
| 492a42883e | |||
| b182f73415 | |||
| e766b9737e | |||
| 2335f93579 | |||
| 1730260343 | |||
| 27506e9ed8 | |||
| dc64a186d5 | |||
| 3163e09b98 | |||
| dcb9978bb1 | |||
| 4a94a0a5c5 | |||
| 8a2d20403e | |||
| ec706e95cc | |||
| bd3b14a27f | |||
| 082d9e852c | |||
| 36da519b26 | |||
| 06ffdde892 | |||
| 1ec57c080c | |||
| a635f27c68 | |||
| ee3d7a9a35 | |||
| 9a1c869efe | |||
| 837ed76f85 | |||
| b46589cd14 | |||
| d04e4606d2 | |||
| 385bd0eb8a | |||
| 089656e5c4 | |||
| 84ec6dd458 | |||
| 322c139c26 | |||
| babe1833bb | |||
| 9effa47dd8 | |||
| 7ef57cc0cf | |||
| 97420aae1b | |||
| 415e6309f9 | |||
| 83e63ff854 | |||
| de7f103130 | |||
| 2cb912681d | |||
| 04bdf94b78 | |||
| c7389ddaa7 | |||
| e778ab2e3a | |||
| 533d86607f | |||
| cb2096670f | |||
| 284f221a9d | |||
| bc639dd438 | |||
| 1baddbb40e | |||
| f784dab868 | |||
| 85192aaa21 | |||
| 054c705fe2 | |||
| 07b0d8cf6e | |||
| 597d16f566 | |||
| 0ca2c781c3 | |||
| f642de9c41 | |||
| 8965388d05 | |||
| 58c4582f15 | |||
| 44bc1b5cc0 | |||
| 714ebb3e08 | |||
| 8f871c2e3a | |||
| 5cdc5bc441 | |||
| 8d060837ad | |||
| 1d230d4cd6 | |||
| 3636ae7667 | |||
| 9ffb5112c6 | |||
| ca5d574cd7 | |||
| c80283dbcc | |||
| 3fcaddf2d3 | |||
| 6ecff5bce9 | |||
| a103c7dcb6 | |||
| 63746bbb47 | |||
| ed0be6fc9a | |||
| 26404ff5d7 | |||
| adf1674877 | |||
| ab2235fc88 | |||
| 441a6d3fe7 | |||
| e00397620a | |||
| 38fa58c0a3 | |||
| b40fd7b243 | |||
| ae34877496 | |||
| 599cf1e5cb | |||
| 474963dcf1 | |||
| e22384b6b4 | |||
| fb00652396 | |||
| a5dbb5d91f | |||
| e75a03b6f8 | |||
| eb7fe7f3e0 | |||
| 3179808f17 | |||
| fde9f05bd0 | |||
| 8de4290c5b | |||
| 19c74c8872 | |||
| 50edb5d1f4 | |||
| c6ccfd7e75 | |||
| 3796ce69e4 | |||
| 9835e31b46 | |||
| a35040c909 | |||
| a4c94638ca | |||
| e70a8ae6a0 | |||
| 100359e38d | |||
| cd995aca56 | |||
| 3a4bae88ca | |||
| e60eae27fb | |||
| cd6c01e230 | |||
| 0af264429f | |||
| a6d3862350 | |||
| 3fca4850dd | |||
| ba7e41d9a6 | |||
| fe33ce3413 | |||
| 4e25e8aaa2 | |||
| 91be826c7d | |||
| fdfe0cddb8 | |||
| e8ef62116f | |||
| caf8bb39d8 | |||
| 222ba6ee53 | |||
| 8dcda73072 | |||
| 810365d334 | |||
| 4b31510589 | |||
| dfce9a34b8 | |||
| dc9370c32b | |||
| 8dbc721c08 | |||
| 6448b84430 | |||
| 93d6ce40c3 | |||
| ce5be2c1be | |||
| 20fe837022 | |||
| e3ce18fa3e | |||
| 864a1d5e93 | |||
| 9cf7eec247 | |||
| d9c15621f6 | |||
| fea14218a9 | |||
| dbbded5250 | |||
| d65cfc7981 | |||
| dc9124f291 | |||
| 4cd433b6bc | |||
| f9a9ee6b0c | |||
| 1741f7ed58 | |||
| d459c751be | |||
| 34ef8b52f6 | |||
| 5ae96905bb | |||
| b1fdbc0151 | |||
| a5ad27b5f2 | |||
| efcd5052a2 | |||
| f2b10c0ba8 | |||
| f182be2d79 | |||
| 41b10630bb | |||
| 45915bed90 | |||
| a2c2ab428a | |||
| a05f74d302 | |||
| 74e94f3a97 | |||
| 15ee8c6cac | |||
| 18957b1f41 | |||
| 29930cac41 | |||
| e3338dc3ff | |||
| 97b7b4a501 | |||
| b471a72856 | |||
| fed7d911a3 | |||
| ca442970a3 | |||
| 9dbb77c10a | |||
| 1116502bc0 | |||
| edaf17bdd4 | |||
| c61d731358 | |||
| a8415a3484 | |||
| cd2467085e | |||
| 64efb3d2a4 | |||
| e05f137bd8 | |||
| 0c73ddc08b | |||
| 19cc43c442 | |||
| 7108fc81a9 | |||
| 5943b9d7d6 | |||
| 0271e4c918 | |||
| 9dc33eff3a | |||
| 5aef1c8a68 | |||
| c608a05270 | |||
| e2cfd247c3 | |||
| 97eb9154b2 | |||
| d7ff635445 | |||
| aff57fb54e | |||
| e89285a219 | |||
| 706f43caa8 | |||
| dc4faf57cb | |||
| 7baf8052a2 | |||
| d3c59585fd | |||
| 859bb8dc79 | |||
| 58cd2e07ba | |||
| a5a6fb590a | |||
| 3619993e68 | |||
| 88e12c78fa | |||
| 5c285b4ac6 | |||
| c6b729c470 | |||
| 890014759e | |||
| 68c1c43381 | |||
| d0dfcaaad5 | |||
| 3cffaddc0a | |||
| bf4cac0c82 | |||
| f680749a00 | |||
| 13a67980d9 | |||
| f110d595d2 | |||
| 9c8857352b | |||
| c09a1fdba8 | |||
| cdc7033a51 | |||
| fa30c759d7 | |||
| d040be2df0 | |||
| 935c831a7f | |||
| 867e95eef1 | |||
| 2ee04bd1b6 | |||
| 75d567e555 | |||
| d8a489971c | |||
| 19ce5b5c76 | |||
| 7c70ea4d3e | |||
| 2784285d47 | |||
| c946a7a1d5 | |||
| 3e60b49b8b | |||
| 4e7331bbb8 | |||
| b8c7e86223 | |||
| 3b925f8674 | |||
| f1f6d41c73 | |||
| 29ef1cb1be | |||
| 4296085d65 | |||
| c797b09228 | |||
| a870ef0030 | |||
| 43ed9e7310 | |||
| bcd27355f9 | |||
| 6a14dc69c0 | |||
| ed9acd25f9 | |||
| 7b24e66ed3 | |||
| abd3d4b546 | |||
| 4040c4240a | |||
| 1ee747f3ef | |||
| f88874bec8 | |||
| ed440a2150 | |||
| 2fd46b196b | |||
| 12dfcaf7e7 | |||
| f4a199f621 | |||
| bb708e0aa3 | |||
| d625740ca4 | |||
| 250402e9b9 | |||
| 1d2ffe56fb | |||
| d16c0d2887 | |||
| b3555f2f94 | |||
| 83a638fc6d | |||
| f1534a710f | |||
| a16845340b | |||
| ffa4725f8e | |||
| 7792c66c64 | |||
| 1a3985d709 | |||
| 4714895c59 | |||
| 1e37951701 | |||
| e8be1ad752 | |||
| e316a70b6c | |||
| 40a8d21c15 | |||
| 28d5ca7ed9 | |||
| 110b18545f | |||
| a478605da4 | |||
| f5f1589813 | |||
| 0c332b6adb | |||
| ba712ce357 | |||
| 2d2395accf | |||
| 8634289b7a | |||
| 45043fb9a8 | |||
| 0449795725 | |||
| a96093f1b7 | |||
| bd4f7691e9 | |||
| e12acbae70 | |||
| 48dc4eac10 | |||
| a869c92eee | |||
| 4fefd14538 | |||
| c09dbfa47c | |||
| d3c9f66de6 | |||
| 01d7694108 | |||
| 1425b651d4 | |||
| b1befbeefc | |||
| 3a9a84a0b1 | |||
| 368284cccc | |||
| ef777f4db9 | |||
| a8e4e8e882 | |||
| cf93760d00 | |||
| dd8b9ff8fb | |||
| bfed03b7b5 | |||
| 860f06ec9e | |||
| b58376920f | |||
| 4ace075ddf | |||
| dda98a474d | |||
| f1c0df7d87 | |||
| c78e098cb4 | |||
| a3438c4f8d | |||
| 92ecf2d5de | |||
| f18b653725 | |||
| 5128438cfb | |||
| f29f25822b | |||
| ecfe218840 | |||
| dd33d2b5d0 | |||
| 12a8d4e10b | |||
| c5c2fb31b1 | |||
| 343b7faf98 | |||
| 18aa8bbf60 | |||
| a358d1630f | |||
| 01375b321c | |||
| d2739d52e0 | |||
| 4668510106 | |||
| ffcd311c90 | |||
| b94a636542 | |||
| a7aec6bfbc | |||
| 190ca9eddd | |||
| 2cf9eb69eb | |||
| ffcb90da52 | |||
| 878b0c9275 | |||
| 5505cb0dea | |||
| 7ac14dccda | |||
| 6cffd0a723 | |||
| 220ebf93c7 | |||
| d0681a5592 | |||
| 09d167c16d | |||
| 477bb45df7 | |||
| e006306036 | |||
| 065cbcf0f9 | |||
| 7a6b958bbe | |||
| ef6a5b6599 | |||
| cdae919b5e | |||
| 12889f4549 | |||
| 089d59b691 | |||
| b3e247e9cc | |||
| 56392b87f7 | |||
| 1b1a4aeb38 | |||
| 16147e0c08 | |||
| 139317cf1b | |||
| 72b94127fb | |||
| 1f1fc94d22 | |||
| a574fe026c | |||
| aa82083d30 | |||
| 08d5df70c2 | |||
| 29b8fa5897 | |||
| e96faf31d4 | |||
| 157a73aa99 | |||
| bdd298c8a0 | |||
| 3f7dd21186 | |||
| 086b708cf7 | |||
| 57e0e57f48 | |||
| 4b7efbfdc0 | |||
| 7dc2653042 | |||
| e428453835 | |||
| f84c8229de | |||
| a73427d68d | |||
| e4456bb236 | |||
| 06eadd0c15 | |||
| 3c90dfa660 | |||
| ace1b8ee71 | |||
| 676356e800 | |||
| f732e54c22 | |||
| cdc2e74f68 | |||
| 724f3e872b | |||
| d63e5165eb | |||
| 9892c4392e | |||
| 5ced1a775c | |||
| 761de1318e | |||
| 02508512d5 | |||
| 6e6105af05 | |||
| d569419e13 | |||
| 93f1641803 | |||
| ff52bf93fa | |||
| a039275a0c | |||
| a98d10104d | |||
| 8924bc59b1 | |||
| eefe60a9c9 | |||
| fe1cb3d904 | |||
| 0448278a78 | |||
| 99c0c2ff4c | |||
| b369b734ca | |||
| 57150a20fd | |||
| 1634d7d531 | |||
| d563de4207 | |||
| 5cd4b82ed0 | |||
| 5f728d348c | |||
| 596c4b6e40 | |||
| 36d1e7c44a | |||
| 25c17082f2 | |||
| 810ccf8e94 | |||
| c8ed0b19f0 | |||
| 9e09444c65 | |||
| 5923fa0cd5 | |||
| b2d4c5d14b | |||
| 0bb9c1d650 | |||
| fbfa3abffd | |||
| b5656aa5dd | |||
| d53fd6a109 | |||
| b0650b926b | |||
| 845f6a0a93 | |||
| d8daa83c79 | |||
| 7bb0199e83 | |||
| f014dadf06 | |||
| 393e54ce91 | |||
| fdf4ad9543 | |||
| 5f0d384c9e | |||
| 4271700046 | |||
| e153b0ab78 | |||
| 26868ae668 | |||
| 17c0364eda | |||
| b28ac7af8c | |||
| 2dcaa21a44 | |||
| 33cc8363f9 | |||
| 9b61e1c85c | |||
| 6f53fdc02d | |||
| 6f850f5a55 | |||
| a482a4b1f4 | |||
| 3664e6f96d | |||
| dda8808173 | |||
| 63a24c23cc | |||
| 1ec3a72f79 | |||
| 566285ec0e | |||
| d5ba82338d | |||
| cbecd2a2fc | |||
| 3772dd40ac | |||
| f69a0f0261 | |||
| cb323ffb84 | |||
| 0db73e71a0 | |||
| eeb0c838db | |||
| dc48ee5aed | |||
| c0acfa57a9 | |||
| 3e166ef927 | |||
| 4942d83de5 | |||
| 4c30b39e71 | |||
| e55f4fe6b6 | |||
| aff74cffa0 | |||
| 8b29bb8664 | |||
| 3cee57b6c2 | |||
| 857f4a4fc8 | |||
| a942293a74 | |||
| 550b121990 | |||
| cc84901a49 | |||
| 9d3764c5d9 | |||
| 0950235ccd | |||
| 8ed7fc894e | |||
| e504ffa225 | |||
| 9c63b37bb4 | |||
| 5c110ca359 | |||
| 1ab61beeb9 | |||
| 8e45a546c9 | |||
| 745a7f76ea | |||
| 8cb9ab3204 | |||
| 12533d1414 | |||
| bd1c164d57 | |||
| 7446c2096d | |||
| 8ce5c4b885 | |||
| ab76112f5f | |||
| 9c54e39eae | |||
| 61eab44474 | |||
| f6285ec710 | |||
| ed878ec4b4 | |||
| e38d41d67a | |||
| 3d237d72bd | |||
| 8044d2390c | |||
| 6b82e6b5ac | |||
| 842e6a93e2 | |||
| f140f054e5 | |||
| 5cd4726e23 | |||
| bccc58d693 | |||
| e25f1c1481 | |||
| fc4e690996 | |||
| dadb2f9d37 | |||
| 5bf15b0587 | |||
| 5f9c0c3204 | |||
| dfa4f0c309 | |||
| f0063b4b0d | |||
| 5dc51c34ea | |||
| 5bf7a55bfa | |||
| eb9ae8d5dc | |||
| 2a133587cc | |||
| 0e4a19c368 | |||
| 813c820227 | |||
| 870cee5707 | |||
| 4e55d2d941 | |||
| 8e962bf992 | |||
| 0815715f7b | |||
| 85e4697b7f | |||
| 16fdb9bf4c | |||
| 46f3d50a54 | |||
| 3a38240fb2 | |||
| 662f0b8fb6 | |||
| 96ce42ae91 | |||
| 93f587b851 | |||
| 89a940ec81 | |||
| a33771b15d | |||
| 9a566e5559 | |||
| 6e75d42a92 | |||
| 575413cac9 | |||
| 6a9476c6d0 | |||
| 5468f1705c | |||
| 5ea132e712 | |||
| 8128fcf8bc | |||
| e89655f793 | |||
| 2db2b068c4 | |||
| a59e214317 | |||
| ae2b6e4d7a | |||
| b10fc6a0b0 | |||
| 70977e5228 | |||
| 4482391574 | |||
| bd078fc883 | |||
| 644af87782 | |||
| 1ce36c1069 | |||
| 0a71005ecc | |||
| 698618a4b3 | |||
| f9642dd79f | |||
| 85d1a3c016 | |||
| 38c74c81a6 | |||
| 4c04991b70 | |||
| 293a339fed | |||
| 5255a527f9 | |||
| 9440dfb66c | |||
| 7a019eee19 | |||
| 93f56a5dc8 | |||
| 68264228b8 | |||
| 66c1b8e26c | |||
| 5776c048ea | |||
| 76dd09bc50 | |||
| 73d18d3abd | |||
| c1c9d0c8a3 | |||
| 64420ead7c | |||
| 6d035c6888 | |||
| 833ca8cce9 | |||
| d02d506b13 | |||
| f306056e5d | |||
| 58ec669d15 | |||
| d1b61bfed3 | |||
| 325e0c6781 | |||
| 8d66cd52b5 | |||
| 4b9277629c | |||
| 6515a6188b | |||
| 8b3ca52502 | |||
| fae003e085 | |||
| 4b961d2d8f | |||
| e27fc512b4 | |||
| 8f0f600b6b | |||
| 5950610690 | |||
| fce3df0c82 | |||
| e2021231c6 | |||
| f61dd7509e | |||
| db2b64e58c | |||
| d70999c386 | |||
| eb6ecc59ab | |||
| 1e0e2fadfd | |||
| 4325f714b9 | |||
| 137cd45497 | |||
| f3dbe4416f | |||
| 7fb55c0f51 | |||
| fdc6cbc507 | |||
| 072085ae82 | |||
| 04a8996348 | |||
| c26dcc2618 | |||
| a4dc340bbc | |||
| 3c069fb588 | |||
| 1fe38f5ed1 | |||
| 841c9424e9 | |||
| 9c44a0c7d3 | |||
| 2883d2eb31 | |||
| f5aade943e | |||
| d17c3f39d0 | |||
| 9ac9ace6b8 | |||
| c9d2cef58d | |||
| a9e30eefdc | |||
| 1a895db9bd | |||
| a955bc3b9b | |||
| 96e888a4f5 | |||
| 99ff0c1e3c | |||
| 599e89b1f9 | |||
| 33c527f15e | |||
| eb02dacfdc | |||
| e6a0e5b858 | |||
| 545ba80697 | |||
| 1e250ee95c | |||
| 5a12eedc2c | |||
| 5605fde777 | |||
| 9ac142688a | |||
| 2791790bf5 | |||
| 1752972be9 | |||
| c877aba09f | |||
| 70e33518a9 | |||
| cb81a9f783 | |||
| b6b499d865 | |||
| 6704ad8193 | |||
| 942628a261 | |||
| 4ea8bac10d | |||
| eafccc5721 | |||
| a01bec3a11 | |||
| 3868175b85 | |||
| 904cb01067 | |||
| 5c0cb425a6 | |||
| 9dbb2ef630 | |||
| bafd2817ee | |||
| 3380293923 | |||
| a549c1ec8b | |||
| ad84997ce0 | |||
| 42e2576813 | |||
| 31b995fa98 | |||
| 0364bec995 | |||
| aa39f3d0a3 | |||
| db545f43ea | |||
| bbe003a454 | |||
| 819f0f68f6 | |||
| 8c0160937b | |||
| 6de789dfe3 | |||
| afa2bb3bf5 | |||
| 89e66c0741 | |||
| 0dc4afba99 | |||
| 152578e576 | |||
| 63d6ab6fa7 | |||
| 75c8c59d78 | |||
| 87a59b6a9b | |||
| 2001fa86cf | |||
| 52747782a7 | |||
| 66f2668326 | |||
| b262efc24c | |||
| ce7ad76447 | |||
| 9e98b6616e | |||
| f4c9eaa904 | |||
| f8a0988e5f | |||
| bf919207ed | |||
| dac6b5c992 | |||
| 7f8043777e | |||
| 854b3feb36 | |||
| 22447e6ddb | |||
| be2ec36e1f | |||
| 98cf16479d | |||
| 584735cbd0 | |||
| 3741493cb7 | |||
| 4ea861fe5c | |||
| cd3df4d3c1 | |||
| 881a1edccb | |||
| 1b7b574289 | |||
| d1d7498447 | |||
| 50c18727e7 | |||
| e9bfde470a | |||
| 68f718a210 | |||
| c3e528ad4b | |||
| 28af97c400 | |||
| c2e4c343ab | |||
| 8a78589c2f | |||
| 841ee18435 | |||
| 71f54701d2 | |||
| 1c99939dfa | |||
| 50462cecd0 | |||
| aa6a32f023 | |||
| c4dc9064e3 | |||
| bc5be10a0e | |||
| 98d9b57379 | |||
| 021a16050a | |||
| 555104aff0 | |||
| 95d63b78f4 | |||
| 80f9e1f4f1 | |||
| a77997a4de | |||
| ec4eb8e2a9 | |||
| 1bdeade71e | |||
| 629ba105cb | |||
| 891a1af995 | |||
| 0fbc6ac151 | |||
| a6384d1b73 | |||
| 2fb9514890 | |||
| fe89794505 | |||
| 08800c9faf | |||
| 469a4700d2 | |||
| 6707f974a5 | |||
| c122cada2b | |||
| 96f02d8c95 | |||
| dd717b60b8 | |||
| 3c20c7f4b4 | |||
| 1a09e70a04 | |||
| 027453bbd2 | |||
| b621efa4a5 | |||
| 2915e4698c | |||
| b687b1a4c5 | |||
| b53827f32b | |||
| d9641128a8 | |||
| dfb5562142 | |||
| d467c04749 | |||
| 3d7cffef2b | |||
| f2fe81d9b5 | |||
| cf98a22269 | |||
| 49f75d7036 | |||
| ce940235b0 | |||
| f5626f678d | |||
| b3a59c3946 | |||
| 93c390c4fc | |||
| 941ab5a98f | |||
| 2ecdf803c0 | |||
| 5b2a399392 | |||
| a9ea1d7606 | |||
| 1ce8ac2de6 | |||
| e2019579fb | |||
| fb3c6e56ee | |||
| 3fad007ae0 | |||
| 8891b6c930 | |||
| 400c592acf | |||
| e13f3254ad | |||
| bf40a07bb9 | |||
| 8f3a6b8479 | |||
| 7642b7cc72 | |||
| e12ea60d85 | |||
| 0b13c4aed6 | |||
| 47919382e9 | |||
| d60d67ee7e | |||
| 559aa687a5 | |||
| bc0761f002 | |||
| c0c2fc0eba | |||
| 44fe43c74c | |||
| 53a2a5d693 | |||
| 2334c26cbb | |||
| 0b6dde46d9 | |||
| 98d9d81aff | |||
| 736a62b632 | |||
| cea6a83d8a | |||
| 2751fd7efc | |||
| 2822042eeb | |||
| dc46d88ddd | |||
| e04f76b558 | |||
| a758056494 | |||
| 1ecdea5db3 | |||
| e1bb773d85 | |||
| 7e934eff5d | |||
| cfdf5603af | |||
| 45bfb8c6b6 | |||
| 65608a51b8 | |||
| b6314597fe | |||
| 20a588199a | |||
| 59916f1e95 | |||
| 8b91f8f9e7 | |||
| cbc3cce66f | |||
| b4b63b5860 | |||
| b9ae15a890 | |||
| d955389c46 | |||
| 975eb885c1 | |||
| a3aed96757 | |||
| dc70bfabaf | |||
| 6932340671 | |||
| f6637b7caf | |||
| 4f4be44caa | |||
| 7832497ba7 | |||
| 7d06e2395f | |||
| 3a479d7eef | |||
| 8fe8a1e9ee | |||
| 2d8b2e7fb0 | |||
| 9c0365f92c | |||
| b48abb08d2 | |||
| d8f3e032c7 | |||
| 8dbcb255ad | |||
| db06cbbc86 | |||
| 98ab23c1a3 | |||
| d0ca9ba6a6 | |||
| b242368675 | |||
| 664527ce63 | |||
| 99e4f80be0 | |||
| 702dae9fcd | |||
| 48fe1ba559 |
@@ -0,0 +1,4 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.kt]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
custom: https://signal.org/donate/
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: 🛠️ Bug report
|
||||||
|
about: Let us know that something isn't working as intended
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
||||||
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 📃Support Center
|
||||||
|
url: https://support.signal.org/
|
||||||
|
about: Find answers to many common questions.
|
||||||
|
- name: ✨ Feature request
|
||||||
|
url: https://community.signalusers.org/c/feature-requests/
|
||||||
|
about: Missing something in Signal? Let us know.
|
||||||
|
- name: 💬 Community support
|
||||||
|
url: https://community.signalusers.org/c/support/
|
||||||
|
about: Feel free to ask anything.
|
||||||
|
- name: 📖 Developer documentation
|
||||||
|
url: https://signal.org/docs/
|
||||||
|
about: Official Signal developer documentation.
|
||||||
|
- name: 📚 Translation feedback.
|
||||||
|
url: https://community.signalusers.org/c/translation-feedback/
|
||||||
|
about: Share feedback on translations.
|
||||||
|
- name: ❓ Other issue?
|
||||||
|
url: https://community.signalusers.org/
|
||||||
|
about: Search on the community forums.
|
||||||
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
- '4.**'
|
- '4.**'
|
||||||
|
- '5.**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -13,18 +14,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.8
|
||||||
|
|
||||||
- name: Install NDK
|
|
||||||
run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew qa
|
run: ./gradlew qa
|
||||||
|
|
||||||
|
- name: Archive reports for failed build
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: reports
|
||||||
|
path: '*/build/reports'
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Reproducible Build Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 5 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Build image
|
||||||
|
run: cd reproducible-builds && docker build -t signal-android . && cd ..
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: docker run --rm -v $(pwd):/project -w /project signal-android ./gradlew clean assembleRelease
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
.classpath
|
.classpath
|
||||||
captures/
|
captures/
|
||||||
project.properties
|
project.properties
|
||||||
|
keystore.debug.properties
|
||||||
|
keystore.staging.properties
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
bin/
|
bin/
|
||||||
@@ -23,5 +25,5 @@ ffpr
|
|||||||
test/androidTestEspresso/res/values/arrays.xml
|
test/androidTestEspresso/res/values/arrays.xml
|
||||||
obj/
|
obj/
|
||||||
jni/libspeex/.deps/
|
jni/libspeex/.deps/
|
||||||
*.sh
|
|
||||||
pkcs11.password
|
pkcs11.password
|
||||||
|
dev.keystore
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="RIGHT_MARGIN" value="240" />
|
||||||
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
|
<option name="SOFT_MARGINS" value="160" />
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||||
|
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="BRACE_STYLE" value="5" />
|
||||||
|
<option name="CLASS_BRACE_STYLE" value="5" />
|
||||||
|
<option name="METHOD_BRACE_STYLE" value="5" />
|
||||||
|
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||||
|
<option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
|
||||||
|
<option name="ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS" value="true" />
|
||||||
|
<option name="ALIGN_CONSECUTIVE_ASSIGNMENTS" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||||
|
<option name="WRAP_FIRST_METHOD_IN_CALL_CHAIN" value="true" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_MULTIPLE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="ENUM_CONSTANTS_WRAP" value="5" />
|
||||||
|
<option name="WRAP_ON_TYPING" value="0" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<groups>
|
||||||
|
<group>
|
||||||
|
<type>GETTERS_AND_SETTERS</type>
|
||||||
|
<order>KEEP</order>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<type>OVERRIDDEN_METHODS</type>
|
||||||
|
<order>KEEP</order>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<type>DEPENDENT_METHODS</type>
|
||||||
|
<order>BREADTH_FIRST</order>
|
||||||
|
</group>
|
||||||
|
</groups>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
@@ -83,7 +83,7 @@ There are several other ways to get involved:
|
|||||||
* Try to reproduce issues and help with troubleshooting.
|
* Try to reproduce issues and help with troubleshooting.
|
||||||
* Discover solutions to open issues and post any relevant findings.
|
* Discover solutions to open issues and post any relevant findings.
|
||||||
* Test other people's pull requests.
|
* Test other people's pull requests.
|
||||||
* Contribute to Signal via the [Freedom of the Press Foundation's donation page](https://freedom.press/crowdfunding/signal/).
|
* [Donate to Signal.](https://signal.org/donate/)
|
||||||
* Share Signal with your friends and family.
|
* Share Signal with your friends and family.
|
||||||
|
|
||||||
Signal is made for you. Thank you for your feedback and support.
|
Signal is made for you. Thank you for your feedback and support.
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
FROM ubuntu:17.10
|
|
||||||
|
|
||||||
RUN dpkg --add-architecture i386 && \
|
|
||||||
apt-get update -y && \
|
|
||||||
apt-get install -y software-properties-common && \
|
|
||||||
apt-get update -y && \
|
|
||||||
apt-get install -y libc6:i386=2.26-0ubuntu2.1 libncurses5:i386=6.0+20160625-1ubuntu1 libstdc++6:i386=7.2.0-8ubuntu3.2 lib32z1=1:1.2.11.dfsg-0ubuntu2 wget openjdk-8-jdk=8u171-b11-0ubuntu0.17.10.1 git unzip opensc pcscd && \
|
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
|
||||||
apt-get autoremove -y && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
ENV ANDROID_SDK_FILENAME android-sdk_r24.4.1-linux.tgz
|
|
||||||
ENV ANDROID_SDK_URL https://dl.google.com/android/${ANDROID_SDK_FILENAME}
|
|
||||||
ENV ANDROID_API_LEVELS android-28
|
|
||||||
ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3
|
|
||||||
ENV ANDROID_HOME /usr/local/android-sdk-linux
|
|
||||||
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
|
||||||
RUN cd /usr/local/ && \
|
|
||||||
wget -q ${ANDROID_SDK_URL} && \
|
|
||||||
tar -xzf ${ANDROID_SDK_FILENAME} && \
|
|
||||||
rm ${ANDROID_SDK_FILENAME}
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter ${ANDROID_API_LEVELS}
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter extra-android-m2repository,extra-android-support,extra-google-google_play_services,extra-google-m2repository
|
|
||||||
RUN echo y | android update sdk --no-ui -a --filter tools,platform-tools,build-tools-${ANDROID_BUILD_TOOLS_VERSION}
|
|
||||||
RUN rm -rf ${ANDROID_HOME}/tools && unzip ${ANDROID_HOME}/temp/*.zip -d ${ANDROID_HOME}
|
|
||||||
@@ -2,29 +2,13 @@ import org.signal.signing.ApkSignerUtil
|
|||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
maven {
|
|
||||||
url "https://repo1.maven.org/maven2"
|
|
||||||
}
|
|
||||||
jcenter {
|
|
||||||
content {
|
|
||||||
includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
|
||||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
|
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'com.google.protobuf'
|
apply plugin: 'com.google.protobuf'
|
||||||
apply plugin: 'androidx.navigation.safeargs'
|
apply plugin: 'androidx.navigation.safeargs'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
|
apply plugin: 'org.jlleitschuh.gradle.ktlint'
|
||||||
apply from: 'translations.gradle'
|
apply from: 'translations.gradle'
|
||||||
apply from: 'witness-verifications.gradle'
|
apply from: 'witness-verifications.gradle'
|
||||||
|
|
||||||
@@ -35,24 +19,12 @@ repositories {
|
|||||||
includeGroupByRegex "com\\.github\\.chrisbanes.*"
|
includeGroupByRegex "com\\.github\\.chrisbanes.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/"
|
|
||||||
content {
|
|
||||||
includeGroupByRegex "me\\.leolin.*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maven {
|
maven {
|
||||||
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
|
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
|
||||||
content {
|
content {
|
||||||
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
|
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/"
|
|
||||||
content {
|
|
||||||
includeGroupByRegex "org\\.signal.*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maven { // textdrawable
|
maven { // textdrawable
|
||||||
url 'https://dl.bintray.com/amulyakhare/maven'
|
url 'https://dl.bintray.com/amulyakhare/maven'
|
||||||
content {
|
content {
|
||||||
@@ -63,6 +35,9 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
maven {
|
||||||
|
url "https://dl.cloudsmith.io/qxAgwaeEE1vN8aLU/mobilecoin/mobilecoin/maven/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protobuf {
|
protobuf {
|
||||||
@@ -80,38 +55,58 @@ protobuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 649
|
def canonicalVersionCode = 851
|
||||||
def canonicalVersionName = "4.62.1"
|
def canonicalVersionName = "5.12.3"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 100
|
||||||
def abiPostFix = ['universal' : 0,
|
def abiPostFix = ['universal' : 0,
|
||||||
'armeabi-v7a' : 1,
|
'armeabi-v7a' : 1,
|
||||||
'arm64-v8a' : 2,
|
'arm64-v8a' : 2,
|
||||||
'x86' : 3,
|
'x86' : 3,
|
||||||
'x86_64' : 4]
|
'x86_64' : 4]
|
||||||
|
|
||||||
|
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
|
||||||
|
|
||||||
android {
|
android {
|
||||||
flavorDimensions "none"
|
buildToolsVersion BUILD_TOOL_VERSION
|
||||||
compileSdkVersion 28
|
compileSdkVersion COMPILE_SDK
|
||||||
buildToolsVersion '28.0.3'
|
|
||||||
|
flavorDimensions 'distribution', 'environment'
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
freeCompilerArgs = ["-Xallow-result-return-type"]
|
||||||
|
}
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
javaMaxHeapSize "4g"
|
javaMaxHeapSize "4g"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (keystores.debug != null) {
|
||||||
|
debug {
|
||||||
|
storeFile file("${project.rootDir}/${keystores.debug.storeFile}")
|
||||||
|
storePassword keystores.debug.storePassword
|
||||||
|
keyAlias keystores.debug.keyAlias
|
||||||
|
keyPassword keystores.debug.keyPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode canonicalVersionCode * postFixSize
|
versionCode canonicalVersionCode * postFixSize
|
||||||
versionName canonicalVersionName
|
versionName canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion 19
|
minSdkVersion MINIMUM_SDK
|
||||||
targetSdkVersion 28
|
targetSdkVersion TARGET_SDK
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
project.ext.set("archivesBaseName", "Signal");
|
project.ext.set("archivesBaseName", "Signal");
|
||||||
|
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
|
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
|
||||||
|
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
|
||||||
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
|
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
|
||||||
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
||||||
@@ -119,16 +114,23 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
|
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||||
|
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"," +
|
||||||
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
|
"\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
|
||||||
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")";
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
|
||||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
||||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||||
|
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
|
||||||
|
buildConfigField "int[]", "MOBILE_COIN_REGIONS", "new int[]{44}"
|
||||||
|
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
|
||||||
|
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\""
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
@@ -149,8 +151,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
coreLibraryDesugaringEnabled true
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JAVA_VERSION
|
||||||
|
targetCompatibility JAVA_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
@@ -161,10 +164,16 @@ android {
|
|||||||
exclude 'META-INF/LICENSE'
|
exclude 'META-INF/LICENSE'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||||
|
exclude '/org/spongycastle/x509/CertPathReviewerMessages.properties'
|
||||||
|
exclude '/org/spongycastle/x509/CertPathReviewerMessages_de.properties'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
|
if (keystores['debug'] != null) {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
isDefault true
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||||
'proguard/proguard-firebase-messaging.pro',
|
'proguard/proguard-firebase-messaging.pro',
|
||||||
@@ -188,8 +197,74 @@ android {
|
|||||||
testProguardFiles 'proguard/proguard-automation.pro',
|
testProguardFiles 'proguard/proguard-automation.pro',
|
||||||
'proguard/proguard.cfg'
|
'proguard/proguard.cfg'
|
||||||
}
|
}
|
||||||
staging {
|
flipper {
|
||||||
initWith debug
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
minifyEnabled false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles = buildTypes.debug.proguardFiles
|
||||||
|
}
|
||||||
|
perf {
|
||||||
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
debuggable false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
}
|
||||||
|
mock {
|
||||||
|
initWith debug
|
||||||
|
isDefault false
|
||||||
|
minifyEnabled false
|
||||||
|
matchingFallbacks = ['debug']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
play {
|
||||||
|
dimension 'distribution'
|
||||||
|
isDefault true
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
website {
|
||||||
|
dimension 'distribution'
|
||||||
|
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
||||||
|
}
|
||||||
|
|
||||||
|
internal {
|
||||||
|
dimension 'distribution'
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
study {
|
||||||
|
dimension 'distribution'
|
||||||
|
|
||||||
|
applicationIdSuffix ".study"
|
||||||
|
ext.websiteUpdateUrl = "null"
|
||||||
|
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||||
|
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
prod {
|
||||||
|
dimension 'environment'
|
||||||
|
|
||||||
|
isDefault true
|
||||||
|
|
||||||
|
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\""
|
||||||
|
}
|
||||||
|
|
||||||
|
staging {
|
||||||
|
dimension 'environment'
|
||||||
|
|
||||||
|
applicationIdSuffix ".staging"
|
||||||
|
|
||||||
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
|
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
|
||||||
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
|
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
|
||||||
@@ -197,34 +272,15 @@ android {
|
|||||||
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
|
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
|
||||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
|
||||||
buildConfigField "String", "CDS_MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
|
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||||
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
|
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " +
|
||||||
|
"\"51a56084c0b21c6b8f62b1bc792ec9bedac4c7c3964bb08ddcab868158c09982\", " +
|
||||||
|
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")"
|
||||||
|
buildConfigField "KbsEnclave[]", "KBS_FALLBACKS", "new KbsEnclave[0]"
|
||||||
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
|
||||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
|
||||||
}
|
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
|
||||||
flipper {
|
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""
|
||||||
initWith debug
|
|
||||||
minifyEnabled false
|
|
||||||
}
|
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
proguardFiles = buildTypes.debug.proguardFiles
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
play {
|
|
||||||
dimension "none"
|
|
||||||
ext.websiteUpdateUrl = "null"
|
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
|
||||||
}
|
|
||||||
|
|
||||||
website {
|
|
||||||
dimension "none"
|
|
||||||
ext.websiteUpdateUrl = "https://updates.signal.org/android"
|
|
||||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
|
|
||||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +296,18 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android.variantFilter { variant ->
|
||||||
|
def distribution = variant.getFlavors().get(0).name
|
||||||
|
def environment = variant.getFlavors().get(1).name
|
||||||
|
def buildType = variant.buildType.name
|
||||||
|
|
||||||
|
if (distribution == 'study' && buildType != 'perf' && buildType != 'mock') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
} else if (distribution != 'study' && buildType == 'mock') {
|
||||||
|
variant.setIgnore(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError true
|
abortOnError true
|
||||||
baseline file("lint-baseline.xml")
|
baseline file("lint-baseline.xml")
|
||||||
@@ -256,33 +324,33 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
lintChecks project(':lintchecks')
|
lintChecks project(':lintchecks')
|
||||||
|
|
||||||
implementation('androidx.appcompat:appcompat:1.1.0-beta01') {
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
|
implementation ('androidx.appcompat:appcompat:1.2.0') {
|
||||||
force = true
|
force = true
|
||||||
}
|
}
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.preference:preference:1.0.0'
|
implementation 'androidx.preference:preference:1.0.0'
|
||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
||||||
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
|
||||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
||||||
implementation "androidx.camera:camera-core:1.0.0-beta01"
|
implementation "androidx.camera:camera-core:1.0.0-beta11"
|
||||||
implementation "androidx.camera:camera-camera2:1.0.0-beta01"
|
implementation "androidx.camera:camera-camera2:1.0.0-beta11"
|
||||||
implementation "androidx.camera:camera-lifecycle:1.0.0-beta01"
|
implementation "androidx.camera:camera-lifecycle:1.0.0-beta11"
|
||||||
|
implementation "androidx.camera:camera-view:1.0.0-alpha18"
|
||||||
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
||||||
implementation "androidx.autofill:autofill:1.0.0"
|
implementation "androidx.autofill:autofill:1.0.0"
|
||||||
implementation "androidx.paging:paging-common:2.1.2"
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
implementation "androidx.paging:paging-runtime:2.1.2"
|
|
||||||
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
|
|
||||||
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
|
||||||
|
|
||||||
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
@@ -295,25 +363,37 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
||||||
|
implementation 'com.google.android.exoplayer:extension-mediasession:2.9.1'
|
||||||
|
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
implementation 'org.conscrypt:conscrypt-android:2.0.0'
|
||||||
implementation 'org.signal:aesgcmprovider:0.0.3'
|
implementation 'org.signal:aesgcmprovider:0.0.3'
|
||||||
|
|
||||||
implementation project(':libsignal-service')
|
implementation project(':libsignal-service')
|
||||||
|
implementation project(':paging')
|
||||||
|
implementation project(':core-util')
|
||||||
|
implementation project(':video')
|
||||||
|
implementation project(':device-transfer')
|
||||||
|
|
||||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||||
|
implementation 'org.whispersystems:signal-client-android:0.5.1'
|
||||||
|
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||||
|
|
||||||
|
implementation('com.mobilecoin:android-sdk:1.0.0') {
|
||||||
|
exclude group: 'com.google.protobuf'
|
||||||
|
}
|
||||||
|
|
||||||
implementation 'org.signal:argon2:13.1@aar'
|
implementation 'org.signal:argon2:13.1@aar'
|
||||||
|
|
||||||
implementation 'org.signal:ringrtc-android:2.0.3'
|
implementation 'org.signal:ringrtc-android:2.9.6'
|
||||||
|
|
||||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
implementation "me.leolin:ShortcutBadger:1.1.22"
|
||||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
kapt 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation 'com.makeramen:roundedimageview:2.1.0'
|
implementation 'com.makeramen:roundedimageview:2.1.0'
|
||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
@@ -342,11 +422,14 @@ dependencies {
|
|||||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.airbnb.android:lottie:3.0.7'
|
implementation 'com.airbnb.android:lottie:3.6.0'
|
||||||
|
|
||||||
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
||||||
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||||
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
|
|
||||||
|
implementation "net.zetetic:android-database-sqlcipher:4.4.3"
|
||||||
|
implementation "androidx.sqlite:sqlite:2.1.0"
|
||||||
|
|
||||||
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
|
||||||
exclude group: 'com.fasterxml.jackson.core'
|
exclude group: 'com.fasterxml.jackson.core'
|
||||||
exclude group: 'org.freemarker'
|
exclude group: 'org.freemarker'
|
||||||
@@ -358,27 +441,30 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||||
testImplementation 'org.mockito:mockito-core:1.9.5'
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
|
testImplementation 'org.powermock:powermock-api-mockito2:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.4'
|
||||||
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
testImplementation 'org.powermock:powermock-classloading-xstream:1.7.4'
|
||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation ('org.robolectric:robolectric:4.2') {
|
testImplementation ('org.robolectric:robolectric:4.4') {
|
||||||
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||||
}
|
}
|
||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||||
|
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
|
configuration = '(play|website)(Prod|Staging)(Debug|Release)RuntimeClasspath'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def assembleWebsiteDescriptor = { variant, file ->
|
def assembleWebsiteDescriptor = { variant, file ->
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
@@ -422,29 +508,29 @@ def signProductionRelease = { variant ->
|
|||||||
|
|
||||||
task signProductionPlayRelease {
|
task signProductionPlayRelease {
|
||||||
doLast {
|
doLast {
|
||||||
signProductionRelease(android.applicationVariants.find { (it.name == 'playRelease') })
|
signProductionRelease(android.applicationVariants.find { (it.name == 'playProdRelease') })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task signProductionInternalRelease {
|
||||||
|
doLast {
|
||||||
|
signProductionRelease(android.applicationVariants.find { (it.name == 'internalProdRelease') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task signProductionWebsiteRelease {
|
task signProductionWebsiteRelease {
|
||||||
doLast {
|
doLast {
|
||||||
def variant = android.applicationVariants.find { (it.name == 'websiteRelease') }
|
def variant = android.applicationVariants.find { (it.name == 'websiteProdRelease') }
|
||||||
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
|
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
|
||||||
assembleWebsiteDescriptor(variant, signedRelease)
|
assembleWebsiteDescriptor(variant, signedRelease)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
|
||||||
if (task.name.equals("assemblePlayRelease")) {
|
|
||||||
task.finalizedBy signProductionPlayRelease
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task.name.equals("assembleWebsiteRelease")) {
|
|
||||||
task.finalizedBy signProductionWebsiteRelease
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getLastCommitTimestamp() {
|
def getLastCommitTimestamp() {
|
||||||
|
if (!(new File('.git').exists())) {
|
||||||
|
return System.currentTimeMillis().toString()
|
||||||
|
}
|
||||||
|
|
||||||
new ByteArrayOutputStream().withStream { os ->
|
new ByteArrayOutputStream().withStream { os ->
|
||||||
def result = exec {
|
def result = exec {
|
||||||
executable = 'git'
|
executable = 'git'
|
||||||
@@ -455,3 +541,37 @@ def getLastCommitTimestamp() {
|
|||||||
return os.toString() + "000"
|
return os.toString() + "000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getGitHash() {
|
||||||
|
if (!(new File('.git').exists())) {
|
||||||
|
return "abcd1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'git', 'rev-parse', '--short', 'HEAD'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
testLogging {
|
||||||
|
events "failed"
|
||||||
|
exceptionFormat "full"
|
||||||
|
showCauses true
|
||||||
|
showExceptions true
|
||||||
|
showStackTraces true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadKeystoreProperties(filename) {
|
||||||
|
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
return keystoreProperties;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<lint>
|
<lint>
|
||||||
|
|
||||||
|
<!-- Wont pass lint or qa with a STOPSHIP in a comment -->
|
||||||
|
<issue id="StopShip" severity="fatal" />
|
||||||
|
|
||||||
<!-- L10N errors -->
|
<!-- L10N errors -->
|
||||||
<!-- This is a runtime crash so we don't want to ship with this. -->
|
<!-- This is a runtime crash so we don't want to ship with this. -->
|
||||||
<issue id="StringFormatMatches" severity="error" />
|
<issue id="StringFormatMatches" severity="error" />
|
||||||
|
|
||||||
<!-- L10N warnings -->
|
<!-- L10N warnings -->
|
||||||
<issue id="MissingTranslation" severity="warning" />
|
<issue id="MissingTranslation" severity="ignore" />
|
||||||
<issue id="MissingQuantity" severity="warning" />
|
<issue id="MissingQuantity" severity="warning" />
|
||||||
|
<issue id="MissingDefaultResource" severity="error">
|
||||||
|
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English, excludeNonTranslatables task will remove these -->
|
||||||
|
</issue>
|
||||||
<issue id="ExtraTranslation" severity="warning" />
|
<issue id="ExtraTranslation" severity="warning" />
|
||||||
<issue id="ImpliedQuantity" severity="warning" />
|
<issue id="ImpliedQuantity" severity="warning" />
|
||||||
|
<issue id="TypographyDashes" severity="error" >
|
||||||
|
<ignore path="*/res/values-*/strings.xml" /> <!-- Ignore for non-English -->
|
||||||
|
</issue>
|
||||||
|
|
||||||
<issue id="CanvasSize" severity="error" />
|
<issue id="CanvasSize" severity="error" />
|
||||||
<issue id="HardcodedText" severity="error" />
|
<issue id="HardcodedText" severity="error" />
|
||||||
@@ -22,6 +31,8 @@
|
|||||||
<issue id="LogNotAppSignal" severity="error" />
|
<issue id="LogNotAppSignal" severity="error" />
|
||||||
<issue id="LogTagInlined" severity="error" />
|
<issue id="LogTagInlined" severity="error" />
|
||||||
|
|
||||||
|
<issue id="AlertDialogBuilderUsage" severity="warning" />
|
||||||
|
|
||||||
<issue id="RestrictedApi" severity="error">
|
<issue id="RestrictedApi" severity="error">
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
|
||||||
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
|
||||||
|
|||||||
@@ -5,5 +5,12 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".FlipperApplicationContext"
|
android:name=".FlipperApplicationContext"
|
||||||
tools:replace="android:name"/>
|
tools:replace="android:name">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -14,14 +15,18 @@ import net.sqlcipher.DatabaseUtils;
|
|||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
import net.sqlcipher.database.SQLiteStatement;
|
import net.sqlcipher.database.SQLiteStatement;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
||||||
@@ -29,13 +34,31 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(FlipperSqlCipherAdapter.class);
|
||||||
|
|
||||||
public FlipperSqlCipherAdapter(Context context) {
|
public FlipperSqlCipherAdapter(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Descriptor> getDatabases() {
|
public List<Descriptor> getDatabases() {
|
||||||
return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext())));
|
try {
|
||||||
|
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
|
||||||
|
databaseHelperField.setAccessible(true);
|
||||||
|
|
||||||
|
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
||||||
|
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
||||||
|
SignalDatabase megaphoneOpenHelper = MegaphoneDatabase.getInstance((Application) getContext());
|
||||||
|
SignalDatabase jobManagerOpenHelper = JobDatabase.getInstance((Application) getContext());
|
||||||
|
|
||||||
|
return Arrays.asList(new Descriptor(mainOpenHelper),
|
||||||
|
new Descriptor(keyValueOpenHelper),
|
||||||
|
new Descriptor(megaphoneOpenHelper),
|
||||||
|
new Descriptor(jobManagerOpenHelper));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -215,7 +238,12 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
case Cursor.FIELD_TYPE_FLOAT:
|
case Cursor.FIELD_TYPE_FLOAT:
|
||||||
return cursor.getDouble(column);
|
return cursor.getDouble(column);
|
||||||
case Cursor.FIELD_TYPE_BLOB:
|
case Cursor.FIELD_TYPE_BLOB:
|
||||||
return cursor.getBlob(column);
|
byte[] blob = cursor.getBlob(column);
|
||||||
|
String bytes = blob != null ? "(blob) " + Hex.toStringCondensed(Arrays.copyOf(blob, Math.min(blob.length, 32))) : null;
|
||||||
|
if (bytes != null && bytes.length() == 32 && blob.length > 32) {
|
||||||
|
bytes += "...";
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
case Cursor.FIELD_TYPE_STRING:
|
case Cursor.FIELD_TYPE_STRING:
|
||||||
default:
|
default:
|
||||||
return cursor.getString(column);
|
return cursor.getString(column);
|
||||||
@@ -223,9 +251,9 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class Descriptor implements DatabaseDescriptor {
|
static class Descriptor implements DatabaseDescriptor {
|
||||||
private final SQLCipherOpenHelper sqlCipherOpenHelper;
|
private final SignalDatabase sqlCipherOpenHelper;
|
||||||
|
|
||||||
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
|
Descriptor(@NonNull SignalDatabase sqlCipherOpenHelper) {
|
||||||
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,11 +263,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull SQLiteDatabase getReadable() {
|
public @NonNull SQLiteDatabase getReadable() {
|
||||||
return sqlCipherOpenHelper.getReadableDatabase();
|
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull SQLiteDatabase getWritable() {
|
public @NonNull SQLiteDatabase getWritable() {
|
||||||
return sqlCipherOpenHelper.getWritableDatabase();
|
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Signal (Flipper)</string>
|
|
||||||
</resources>
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/core_red_shade"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.thoughtcrime.securesms">
|
package="org.thoughtcrime.securesms">
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle" />
|
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.view" />
|
||||||
|
|
||||||
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
|
<permission android:name="${applicationId}.ACCESS_SECRETS"
|
||||||
android:label="Access to TextSecure Secrets"
|
android:label="Access to TextSecure Secrets"
|
||||||
android:protectionLevel="signature" />
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
@@ -35,8 +35,10 @@
|
|||||||
<uses-permission android:name="android.permission.SEND_SMS"/>
|
<uses-permission android:name="android.permission.SEND_SMS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
@@ -62,7 +64,6 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
|
||||||
|
|
||||||
<!-- So we can add a TextSecure 'Account' -->
|
<!-- So we can add a TextSecure 'Account' -->
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
@@ -113,15 +114,22 @@
|
|||||||
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
|
||||||
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
||||||
|
|
||||||
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
|
<activity android:name=".WebRtcCallActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
|
android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||||
|
android:taskAffinity=".calling"
|
||||||
android:launchMode="singleTask"/>
|
android:launchMode="singleTask"/>
|
||||||
|
|
||||||
|
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
|
||||||
|
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".InviteActivity"
|
<activity android:name=".InviteActivity"
|
||||||
android:theme="@style/Signal.Light.NoActionBar.Invite"
|
android:theme="@style/Signal.Light.NoActionBar.Invite"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
@@ -129,7 +137,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
android:value=".MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".PromptMmsActivity"
|
<activity android:name=".PromptMmsActivity"
|
||||||
@@ -150,12 +158,15 @@
|
|||||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
<activity android:name=".preferences.MmsPreferencesActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".sharing.ShareActivity"
|
<activity android:name=".sharing.ShareActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:noHistory="true"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -184,7 +195,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".stickers.StickerPackPreviewActivity"
|
<activity android:name=".stickers.StickerPackPreviewActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
@@ -223,6 +234,38 @@
|
|||||||
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity android:name=".deeplinks.DeepLinkEntryActivity"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@style/Signal.Transparent">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="sgnl"
|
||||||
|
android:host="signal.group" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:autoVerify="true"
|
||||||
|
tools:targetApi="23">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https"
|
||||||
|
android:host="signal.group"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https"
|
||||||
|
android:host="signal.tube" />
|
||||||
|
<data android:scheme="sgnl"
|
||||||
|
android:host="signal.tube" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationActivity"
|
<activity android:name=".conversation.ConversationActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
@@ -233,6 +276,11 @@
|
|||||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".conversation.BubbleConversationActivity"
|
||||||
|
android:theme="@style/Signal.DayNight"
|
||||||
|
android:allowEmbedded="true"
|
||||||
|
android:resizeableActivity="true" />
|
||||||
|
|
||||||
<activity android:name=".longmessage.LongMessageActivity" />
|
<activity android:name=".longmessage.LongMessageActivity" />
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationPopupActivity"
|
<activity android:name=".conversation.ConversationPopupActivity"
|
||||||
@@ -243,24 +291,28 @@
|
|||||||
android:theme="@style/TextSecure.LightTheme.Popup"
|
android:theme="@style/TextSecure.LightTheme.Popup"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".MessageDetailsActivity"
|
<activity android:name=".messagedetails.MessageDetailsActivity"
|
||||||
android:label="@string/AndroidManifest__message_details"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".GroupCreateActivity"
|
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
|
||||||
android:windowSoftInputMode="stateVisible"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.pendingmemberinvites.PendingMemberInvitesActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
|
||||||
|
|
||||||
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
|
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".recipients.ui.managerecipient.ManageRecipientActivity"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize"/>
|
||||||
|
|
||||||
<activity android:name=".DatabaseMigrationActivity"
|
<activity android:name=".DatabaseMigrationActivity"
|
||||||
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
@@ -274,7 +326,7 @@
|
|||||||
<activity android:name=".PassphraseCreateActivity"
|
<activity android:name=".PassphraseCreateActivity"
|
||||||
android:label="@string/AndroidManifest__create_passphrase"
|
android:label="@string/AndroidManifest__create_passphrase"
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -284,7 +336,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".NewConversationActivity"
|
<activity android:name=".NewConversationActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateAlwaysVisible"
|
android:windowSoftInputMode="stateAlwaysVisible"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -294,7 +346,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".giph.ui.GiphyActivity"
|
<activity android:name=".giph.ui.GiphyActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -311,14 +363,36 @@
|
|||||||
<activity android:name=".VerifyIdentityActivity"
|
<activity android:name=".VerifyIdentityActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ApplicationPreferencesActivity"
|
<activity android:name=".components.settings.app.AppSettingsActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.ChatWallpaperActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
|
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
@@ -343,7 +417,6 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
<activity android:name=".logsubmit.SubmitDebugLogActivity"
|
||||||
android:label="@string/AndroidManifest__log_submit"
|
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -359,7 +432,7 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".mediaoverview.MediaOverviewActivity"
|
<activity android:name=".mediaoverview.MediaOverviewActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -410,15 +483,11 @@
|
|||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".RecipientPreferenceActivity"
|
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".mediasend.AvatarSelectionActivity"
|
<activity android:name=".mediasend.AvatarSelectionActivity"
|
||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".BlockedContactsActivity"
|
<activity android:name=".blocked.BlockedUsersActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
@@ -430,6 +499,14 @@
|
|||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
|
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
|
<activity android:name=".payments.preferences.PaymentsActivity"
|
||||||
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
@@ -440,17 +517,14 @@
|
|||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ClearProfileAvatarActivity"
|
<activity android:name=".ClearAvatarPromptActivity"
|
||||||
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
android:theme="@style/Theme.AppCompat.Dialog.Alert"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:icon="@drawable/clear_profile_avatar"
|
||||||
android:icon="@drawable/clear_profile_avatar"
|
android:label="@string/AndroidManifest_remove_photo"
|
||||||
android:label="@string/AndroidManifest_remove_photo">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<intent-filter>
|
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
|
||||||
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
|
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
|
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
@@ -462,47 +536,82 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".contactshare.ContactNameEditActivity"
|
<activity android:name=".contactshare.ContactNameEditActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".contactshare.SharedContactDetailsActivity"
|
<activity android:name=".contactshare.SharedContactDetailsActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ShortcutLauncherActivity"
|
<activity android:name=".ShortcutLauncherActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".maps.PlacePickerActivity"
|
android:name=".maps.PlacePickerActivity"
|
||||||
android:label="@string/PlacePickerActivity_title"
|
android:label="@string/PlacePickerActivity_title"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".MainActivity"
|
<activity android:name=".MainActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".pin.PinRestoreActivity"
|
<activity android:name=".pin.PinRestoreActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
|
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
|
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
|
||||||
android:theme="@style/TextSecure.LightNoActionBar" />
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
|
||||||
|
|
||||||
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
|
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name=".megaphone.ClientDeprecatedActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
|
android:launchMode="singleTask" />
|
||||||
|
|
||||||
|
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/TextSecure.FullScreenMedia" />
|
||||||
|
|
||||||
|
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.Signal.WallpaperCropper" />
|
||||||
|
|
||||||
|
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService"/>
|
||||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||||
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
|
||||||
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
|
||||||
|
|
||||||
|
<service android:name=".components.voice.VoiceNotePlaybackService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".service.QuickResponseService"
|
<service android:name=".service.QuickResponseService"
|
||||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
||||||
android:exported="true" >
|
android:exported="true" >
|
||||||
@@ -615,18 +724,27 @@
|
|||||||
|
|
||||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||||
|
|
||||||
|
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
||||||
|
|
||||||
|
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
|
||||||
|
|
||||||
<provider android:name=".providers.PartProvider"
|
<provider android:name=".providers.PartProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:authorities="org.thoughtcrime.provider.securesms" />
|
android:authorities="${applicationId}.part" />
|
||||||
|
|
||||||
|
<provider android:name=".providers.BlobContentProvider"
|
||||||
|
android:authorities="${applicationId}.blob"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<provider android:name=".providers.MmsBodyProvider"
|
<provider android:name=".providers.MmsBodyProvider"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:authorities="org.thoughtcrime.provider.securesms.mms" />
|
android:authorities="${applicationId}.mms" />
|
||||||
|
|
||||||
<provider android:name="androidx.core.content.FileProvider"
|
<provider android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="org.thoughtcrime.securesms.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
|
|
||||||
@@ -635,23 +753,19 @@
|
|||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
<provider android:name=".database.DatabaseContentProviders$Conversation"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversation"
|
android:authorities="${applicationId}.database.conversation"
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$ConversationList"
|
|
||||||
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
|
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
<provider android:name=".database.DatabaseContentProviders$Attachment"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.attachment"
|
android:authorities="${applicationId}.database.attachment"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
<provider android:name=".database.DatabaseContentProviders$Sticker"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.sticker"
|
android:authorities="${applicationId}.database.sticker"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
<provider android:name=".database.DatabaseContentProviders$StickerPack"
|
||||||
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
|
android:authorities="${applicationId}.database.stickerpack"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver android:name=".service.BootReceiver">
|
<receiver android:name=".service.BootReceiver">
|
||||||
@@ -679,6 +793,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.LocalBackupListener">
|
<receiver android:name=".service.LocalBackupListener">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
@@ -697,11 +818,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
|
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.DeleteNotificationReceiver">
|
<receiver android:name=".notifications.DeleteNotificationReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -748,12 +865,5 @@
|
|||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||||
|
|
||||||
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
|
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
|
|
||||||
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 415 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 395 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 622 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 599 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 643 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 602 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 589 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 531 KiB After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 589 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 93 KiB |
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.mediasend.camerax;
|
package androidx.camera.view;
|
||||||
|
|
||||||
import android.Manifest.permission;
|
import android.Manifest.permission;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -45,43 +45,46 @@ import androidx.annotation.RestrictTo;
|
|||||||
import androidx.annotation.RestrictTo.Scope;
|
import androidx.annotation.RestrictTo.Scope;
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
import androidx.camera.core.CameraSelector;
|
import androidx.camera.core.CameraSelector;
|
||||||
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
|
|
||||||
import androidx.camera.core.FocusMeteringAction;
|
import androidx.camera.core.FocusMeteringAction;
|
||||||
import androidx.camera.core.FocusMeteringResult;
|
import androidx.camera.core.FocusMeteringResult;
|
||||||
import androidx.camera.core.ImageCapture;
|
import androidx.camera.core.ImageCapture;
|
||||||
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
||||||
|
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
|
||||||
import androidx.camera.core.ImageProxy;
|
import androidx.camera.core.ImageProxy;
|
||||||
|
import androidx.camera.core.Logger;
|
||||||
import androidx.camera.core.MeteringPoint;
|
import androidx.camera.core.MeteringPoint;
|
||||||
|
import androidx.camera.core.MeteringPointFactory;
|
||||||
|
import androidx.camera.core.VideoCapture;
|
||||||
|
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
|
||||||
import androidx.camera.core.impl.LensFacingConverter;
|
import androidx.camera.core.impl.LensFacingConverter;
|
||||||
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
||||||
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
||||||
import androidx.camera.core.impl.utils.futures.Futures;
|
import androidx.camera.core.impl.utils.futures.Futures;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.File;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link View} that displays a preview of the camera with methods {@link
|
* A {@link View} that displays a preview of the camera with methods {@link
|
||||||
* #takePicture(Executor, OnImageCapturedCallback)},
|
* #takePicture(Executor, OnImageCapturedCallback)},
|
||||||
* {@link #startRecording(FileDescriptor, Executor, VideoCapture.OnVideoSavedCallback)} and {@link #stopRecording()}.
|
* {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
|
||||||
|
* {@link #startRecording(File , Executor , OnVideoSavedCallback callback)}
|
||||||
|
* and {@link #stopRecording()}.
|
||||||
*
|
*
|
||||||
* <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
|
* <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
|
||||||
* be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
|
* be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
|
||||||
* LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
|
* LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
|
||||||
*/
|
*/
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
// End Signal Custom Code Block
|
public final class SignalCameraView extends FrameLayout {
|
||||||
public final class CameraXView extends FrameLayout {
|
static final String TAG = Log.tag(SignalCameraView.class);
|
||||||
static final String TAG = CameraXView.class.getSimpleName();
|
|
||||||
static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
static final int INDEFINITE_VIDEO_DURATION = -1;
|
static final int INDEFINITE_VIDEO_DURATION = -1;
|
||||||
static final int INDEFINITE_VIDEO_SIZE = -1;
|
static final int INDEFINITE_VIDEO_SIZE = -1;
|
||||||
@@ -107,7 +110,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
// For pinch-to-zoom
|
// For pinch-to-zoom
|
||||||
private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
|
private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
|
||||||
private boolean mIsPinchToZoomEnabled = true;
|
private boolean mIsPinchToZoomEnabled = true;
|
||||||
CameraXModule mCameraModule;
|
SignalCameraXModule mCameraModule;
|
||||||
private final DisplayManager.DisplayListener mDisplayListener =
|
private final DisplayManager.DisplayListener mDisplayListener =
|
||||||
new DisplayListener() {
|
new DisplayListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -124,26 +127,25 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private PreviewView mPreviewView;
|
private PreviewView mPreviewView;
|
||||||
private ScaleType mScaleType = ScaleType.CENTER_CROP;
|
|
||||||
// For accessibility event
|
// For accessibility event
|
||||||
private MotionEvent mUpEvent;
|
private MotionEvent mUpEvent;
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context) {
|
public SignalCameraView(@NonNull Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
this(context, attrs, 0);
|
this(context, attrs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
init(context, attrs);
|
init(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
public CameraXView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
||||||
int defStyleRes) {
|
int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
init(context, attrs);
|
init(context, attrs);
|
||||||
}
|
}
|
||||||
@@ -172,23 +174,23 @@ public final class CameraXView extends FrameLayout {
|
|||||||
|
|
||||||
private void init(Context context, @Nullable AttributeSet attrs) {
|
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||||
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
|
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
|
||||||
mCameraModule = new CameraXModule(this);
|
mCameraModule = new SignalCameraXModule(this);
|
||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraXView);
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
|
||||||
setScaleType(
|
setScaleType(
|
||||||
ScaleType.fromId(
|
PreviewView.ScaleType.fromId(
|
||||||
a.getInteger(R.styleable.CameraXView_scaleType,
|
a.getInteger(R.styleable.CameraView_scaleType,
|
||||||
getScaleType().getId())));
|
getScaleType().getId())));
|
||||||
setPinchToZoomEnabled(
|
setPinchToZoomEnabled(
|
||||||
a.getBoolean(
|
a.getBoolean(
|
||||||
R.styleable.CameraXView_pinchToZoomEnabled, isPinchToZoomEnabled()));
|
R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
|
||||||
setCaptureMode(
|
setCaptureMode(
|
||||||
CaptureMode.fromId(
|
CaptureMode.fromId(
|
||||||
a.getInteger(R.styleable.CameraXView_captureMode,
|
a.getInteger(R.styleable.CameraView_captureMode,
|
||||||
getCaptureMode().getId())));
|
getCaptureMode().getId())));
|
||||||
|
|
||||||
int lensFacing = a.getInt(R.styleable.CameraXView_lensFacing, LENS_FACING_BACK);
|
int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
|
||||||
switch (lensFacing) {
|
switch (lensFacing) {
|
||||||
case LENS_FACING_NONE:
|
case LENS_FACING_NONE:
|
||||||
setCameraLensFacing(null);
|
setCameraLensFacing(null);
|
||||||
@@ -203,7 +205,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
// Unhandled event.
|
// Unhandled event.
|
||||||
}
|
}
|
||||||
|
|
||||||
int flashMode = a.getInt(R.styleable.CameraXView_flash, 0);
|
int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
|
||||||
switch (flashMode) {
|
switch (flashMode) {
|
||||||
case FLASH_MODE_AUTO:
|
case FLASH_MODE_AUTO:
|
||||||
setFlash(ImageCapture.FLASH_MODE_AUTO);
|
setFlash(ImageCapture.FLASH_MODE_AUTO);
|
||||||
@@ -265,7 +267,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
if (savedState instanceof Bundle) {
|
if (savedState instanceof Bundle) {
|
||||||
Bundle state = (Bundle) savedState;
|
Bundle state = (Bundle) savedState;
|
||||||
super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
|
super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
|
||||||
setScaleType(ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
|
setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
|
||||||
setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
|
setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
|
||||||
setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
|
setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
|
||||||
setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
|
setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
|
||||||
@@ -298,6 +300,21 @@ public final class CameraXView extends FrameLayout {
|
|||||||
dpyMgr.unregisterDisplayListener(mDisplayListener);
|
dpyMgr.unregisterDisplayListener(mDisplayListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LiveData} of the underlying {@link PreviewView}'s
|
||||||
|
* {@link PreviewView.StreamState}.
|
||||||
|
*
|
||||||
|
* @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
|
||||||
|
* get current value by {@link LiveData#getValue()} or register a observer by
|
||||||
|
* {@link LiveData#observe}.
|
||||||
|
* @see PreviewView#getPreviewStreamState()
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public LiveData<PreviewView.StreamState> getPreviewStreamState() {
|
||||||
|
return mPreviewView.getPreviewStreamState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
PreviewView getPreviewView() {
|
PreviewView getPreviewView() {
|
||||||
return mPreviewView;
|
return mPreviewView;
|
||||||
}
|
}
|
||||||
@@ -347,11 +364,11 @@ public final class CameraXView extends FrameLayout {
|
|||||||
/**
|
/**
|
||||||
* Returns the scale type used to scale the preview.
|
* Returns the scale type used to scale the preview.
|
||||||
*
|
*
|
||||||
* @return The current {@link ScaleType}.
|
* @return The current {@link PreviewView.ScaleType}.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public ScaleType getScaleType() {
|
public PreviewView.ScaleType getScaleType() {
|
||||||
return mScaleType;
|
return mPreviewView.getScaleType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -359,13 +376,10 @@ public final class CameraXView extends FrameLayout {
|
|||||||
*
|
*
|
||||||
* <p>This controls how the view finder should be scaled and positioned within the view.
|
* <p>This controls how the view finder should be scaled and positioned within the view.
|
||||||
*
|
*
|
||||||
* @param scaleType The desired {@link ScaleType}.
|
* @param scaleType The desired {@link PreviewView.ScaleType}.
|
||||||
*/
|
*/
|
||||||
public void setScaleType(@NonNull ScaleType scaleType) {
|
public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
|
||||||
if (scaleType != mScaleType) {
|
mPreviewView.setScaleType(scaleType);
|
||||||
mScaleType = scaleType;
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -401,8 +415,10 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum video duration before {@link VideoCapture.OnVideoSavedCallback#onVideoSaved(FileDescriptor)} is
|
* Sets the maximum video duration before
|
||||||
* called automatically. Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
|
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)} is called
|
||||||
|
* automatically.
|
||||||
|
* Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
|
||||||
*/
|
*/
|
||||||
private void setMaxVideoDuration(long duration) {
|
private void setMaxVideoDuration(long duration) {
|
||||||
mCameraModule.setMaxVideoDuration(duration);
|
mCameraModule.setMaxVideoDuration(duration);
|
||||||
@@ -417,7 +433,8 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the maximum video size in bytes before {@link VideoCapture.OnVideoSavedCallback#onVideoSaved(FileDescriptor)}
|
* Sets the maximum video size in bytes before
|
||||||
|
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)}
|
||||||
* is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
|
* is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
|
||||||
*/
|
*/
|
||||||
private void setMaxVideoSize(long size) {
|
private void setMaxVideoSize(long size) {
|
||||||
@@ -435,28 +452,38 @@ public final class CameraXView extends FrameLayout {
|
|||||||
mCameraModule.takePicture(executor, callback);
|
mCameraModule.takePicture(executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a picture and calls
|
||||||
|
* {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
|
||||||
|
*
|
||||||
|
* <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
|
||||||
|
* {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
|
||||||
|
* front camera, it will be set to true; for back camera, it will be set to false.
|
||||||
|
*
|
||||||
|
* @param outputFileOptions Options to store the newly captured image.
|
||||||
|
* @param executor The executor in which the callback methods will be run.
|
||||||
|
* @param callback Callback which will receive success or failure.
|
||||||
|
*/
|
||||||
|
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
|
||||||
|
@NonNull Executor executor,
|
||||||
|
@NonNull OnImageSavedCallback callback) {
|
||||||
|
mCameraModule.takePicture(outputFileOptions, executor, callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a video and calls the OnVideoSavedCallback when done.
|
* Takes a video and calls the OnVideoSavedCallback when done.
|
||||||
*
|
*
|
||||||
* @param file The destination.
|
* @param outputFileOptions Options to store the newly captured video.
|
||||||
* @param executor The executor in which the callback methods will be run.
|
* @param executor The executor in which the callback methods will be run.
|
||||||
* @param callback Callback which will receive success or failure.
|
* @param callback Callback which will receive success or failure.
|
||||||
*/
|
*/
|
||||||
// Begin Signal Custom Code Block
|
public void startRecording(@NonNull VideoCapture.OutputFileOptions outputFileOptions,
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void startRecording(// Begin Signal Custom Code Block
|
|
||||||
@NonNull FileDescriptor file,
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
@NonNull Executor executor,
|
@NonNull Executor executor,
|
||||||
@NonNull VideoCapture.OnVideoSavedCallback callback) {
|
@NonNull OnVideoSavedCallback callback) {
|
||||||
mCameraModule.startRecording(file, executor, callback);
|
mCameraModule.startRecording(outputFileOptions, executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stops an in progress video. */
|
/** Stops an in progress video. */
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void stopRecording() {
|
public void stopRecording() {
|
||||||
mCameraModule.stopRecording();
|
mCameraModule.stopRecording();
|
||||||
}
|
}
|
||||||
@@ -554,7 +581,8 @@ public final class CameraXView extends FrameLayout {
|
|||||||
mDownEventTimestamp = System.currentTimeMillis();
|
mDownEventTimestamp = System.currentTimeMillis();
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
if (delta() < ViewConfiguration.getLongPressTimeout()) {
|
if (delta() < ViewConfiguration.getLongPressTimeout()
|
||||||
|
&& mCameraModule.isBoundToLifecycle()) {
|
||||||
mUpEvent = event;
|
mUpEvent = event;
|
||||||
performClick();
|
performClick();
|
||||||
}
|
}
|
||||||
@@ -578,19 +606,14 @@ public final class CameraXView extends FrameLayout {
|
|||||||
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
|
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
|
||||||
mUpEvent = null;
|
mUpEvent = null;
|
||||||
|
|
||||||
CameraSelector cameraSelector =
|
|
||||||
new CameraSelector.Builder().requireLensFacing(
|
|
||||||
mCameraModule.getLensFacing()).build();
|
|
||||||
|
|
||||||
DisplayOrientedMeteringPointFactory pointFactory = new DisplayOrientedMeteringPointFactory(
|
|
||||||
getDisplay(), cameraSelector, mPreviewView.getWidth(), mPreviewView.getHeight());
|
|
||||||
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
|
|
||||||
float aePointWidth = afPointWidth * 1.5f;
|
|
||||||
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
|
|
||||||
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
|
|
||||||
|
|
||||||
Camera camera = mCameraModule.getCamera();
|
Camera camera = mCameraModule.getCamera();
|
||||||
if (camera != null) {
|
if (camera != null) {
|
||||||
|
MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
|
||||||
|
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
|
||||||
|
float aePointWidth = afPointWidth * 1.5f;
|
||||||
|
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
|
||||||
|
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
|
||||||
|
|
||||||
ListenableFuture<FocusMeteringResult> future =
|
ListenableFuture<FocusMeteringResult> future =
|
||||||
camera.getCameraControl().startFocusAndMetering(
|
camera.getCameraControl().startFocusAndMetering(
|
||||||
new FocusMeteringAction.Builder(afPoint,
|
new FocusMeteringAction.Builder(afPoint,
|
||||||
@@ -609,7 +632,7 @@ public final class CameraXView extends FrameLayout {
|
|||||||
}, CameraXExecutors.directExecutor());
|
}, CameraXExecutors.directExecutor());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "cannot access camera");
|
Logger.d(TAG, "cannot access camera");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -711,45 +734,11 @@ public final class CameraXView extends FrameLayout {
|
|||||||
return mCameraModule.isTorchOn();
|
return mCameraModule.isTorchOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Options for scaling the bounds of the view finder to the bounds of this view. */
|
|
||||||
public enum ScaleType {
|
|
||||||
/**
|
|
||||||
* Scale the view finder, maintaining the source aspect ratio, so the view finder fills the
|
|
||||||
* entire view. This will cause the view finder to crop the source image if the camera
|
|
||||||
* aspect ratio does not match the view aspect ratio.
|
|
||||||
*/
|
|
||||||
CENTER_CROP(0),
|
|
||||||
/**
|
|
||||||
* Scale the view finder, maintaining the source aspect ratio, so the view finder is
|
|
||||||
* entirely contained within the view.
|
|
||||||
*/
|
|
||||||
CENTER_INSIDE(1);
|
|
||||||
|
|
||||||
private final int mId;
|
|
||||||
|
|
||||||
int getId() {
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScaleType(int id) {
|
|
||||||
mId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ScaleType fromId(int id) {
|
|
||||||
for (ScaleType st : values()) {
|
|
||||||
if (st.mId == id) {
|
|
||||||
return st;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The capture mode used by CameraView.
|
* The capture mode used by CameraView.
|
||||||
*
|
*
|
||||||
* <p>This enum can be used to determine which capture mode will be enabled for {@link
|
* <p>This enum can be used to determine which capture mode will be enabled for {@link
|
||||||
* CameraXView}.
|
* SignalCameraView}.
|
||||||
*/
|
*/
|
||||||
public enum CaptureMode {
|
public enum CaptureMode {
|
||||||
/** A mode where image capture is enabled. */
|
/** A mode where image capture is enabled. */
|
||||||
@@ -832,4 +821,4 @@ public final class CameraXView extends FrameLayout {
|
|||||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.mediasend.camerax;
|
package androidx.camera.view;
|
||||||
|
|
||||||
import android.Manifest.permission;
|
import android.Manifest.permission;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -28,16 +28,19 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.annotation.RequiresPermission;
|
import androidx.annotation.RequiresPermission;
|
||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
|
import androidx.camera.core.CameraInfoUnavailableException;
|
||||||
import androidx.camera.core.CameraSelector;
|
import androidx.camera.core.CameraSelector;
|
||||||
import androidx.camera.core.CameraX;
|
|
||||||
import androidx.camera.core.ImageCapture;
|
import androidx.camera.core.ImageCapture;
|
||||||
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
|
||||||
|
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
|
||||||
|
import androidx.camera.core.Logger;
|
||||||
import androidx.camera.core.Preview;
|
import androidx.camera.core.Preview;
|
||||||
import androidx.camera.core.TorchState;
|
import androidx.camera.core.TorchState;
|
||||||
import androidx.camera.core.UseCase;
|
import androidx.camera.core.UseCase;
|
||||||
|
import androidx.camera.core.VideoCapture;
|
||||||
|
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
|
||||||
import androidx.camera.core.impl.CameraInternal;
|
import androidx.camera.core.impl.CameraInternal;
|
||||||
import androidx.camera.core.impl.LensFacingConverter;
|
import androidx.camera.core.impl.LensFacingConverter;
|
||||||
import androidx.camera.core.impl.VideoCaptureConfig;
|
|
||||||
import androidx.camera.core.impl.utils.CameraOrientationUtil;
|
import androidx.camera.core.impl.utils.CameraOrientationUtil;
|
||||||
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
|
||||||
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
import androidx.camera.core.impl.utils.futures.FutureCallback;
|
||||||
@@ -51,11 +54,10 @@ import androidx.lifecycle.OnLifecycleEvent;
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.video.VideoUtil;
|
import org.thoughtcrime.securesms.video.VideoUtil;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@@ -68,10 +70,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
|
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
|
||||||
|
|
||||||
/** CameraX use case operation built on @{link androidx.camera.core}. */
|
/** CameraX use case operation built on @{link androidx.camera.core}. */
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
// End Signal Custom Code Block
|
@SuppressLint("RestrictedApi")
|
||||||
final class CameraXModule {
|
final class SignalCameraXModule {
|
||||||
public static final String TAG = "CameraXModule";
|
public static final String TAG = "CameraXModule";
|
||||||
|
|
||||||
private static final float UNITY_ZOOM_SCALE = 1f;
|
private static final float UNITY_ZOOM_SCALE = 1f;
|
||||||
@@ -82,13 +83,13 @@ final class CameraXModule {
|
|||||||
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
|
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
|
||||||
|
|
||||||
private final Preview.Builder mPreviewBuilder;
|
private final Preview.Builder mPreviewBuilder;
|
||||||
private final VideoCaptureConfig.Builder mVideoCaptureConfigBuilder;
|
private final VideoCapture.Builder mVideoCaptureBuilder;
|
||||||
private final ImageCapture.Builder mImageCaptureBuilder;
|
private final ImageCapture.Builder mImageCaptureBuilder;
|
||||||
private final CameraXView mCameraXView;
|
private final SignalCameraView mCameraView;
|
||||||
final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
|
final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
|
||||||
private CameraXView.CaptureMode mCaptureMode = CameraXView.CaptureMode.IMAGE;
|
private SignalCameraView.CaptureMode mCaptureMode = SignalCameraView.CaptureMode.IMAGE;
|
||||||
private long mMaxVideoDuration = CameraXView.INDEFINITE_VIDEO_DURATION;
|
private long mMaxVideoDuration = SignalCameraView.INDEFINITE_VIDEO_DURATION;
|
||||||
private long mMaxVideoSize = CameraXView.INDEFINITE_VIDEO_SIZE;
|
private long mMaxVideoSize = SignalCameraView.INDEFINITE_VIDEO_SIZE;
|
||||||
@ImageCapture.FlashMode
|
@ImageCapture.FlashMode
|
||||||
private int mFlash = FLASH_MODE_OFF;
|
private int mFlash = FLASH_MODE_OFF;
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -110,7 +111,6 @@ final class CameraXModule {
|
|||||||
public void onDestroy(LifecycleOwner owner) {
|
public void onDestroy(LifecycleOwner owner) {
|
||||||
if (owner == mCurrentLifecycle) {
|
if (owner == mCurrentLifecycle) {
|
||||||
clearCurrentLifecycle();
|
clearCurrentLifecycle();
|
||||||
mPreview.setSurfaceProvider(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -123,8 +123,8 @@ final class CameraXModule {
|
|||||||
@Nullable
|
@Nullable
|
||||||
ProcessCameraProvider mCameraProvider;
|
ProcessCameraProvider mCameraProvider;
|
||||||
|
|
||||||
CameraXModule(CameraXView view) {
|
SignalCameraXModule(SignalCameraView view) {
|
||||||
mCameraXView = view;
|
mCameraView = view;
|
||||||
|
|
||||||
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
|
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
|
||||||
new FutureCallback<ProcessCameraProvider>() {
|
new FutureCallback<ProcessCameraProvider>() {
|
||||||
@@ -149,14 +149,12 @@ final class CameraXModule {
|
|||||||
|
|
||||||
mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
|
mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture")
|
||||||
mVideoCaptureConfigBuilder =
|
.setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
|
||||||
new VideoCaptureConfig.Builder().setTargetName("VideoCapture")
|
.setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
|
||||||
.setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
|
.setBitRate(VideoUtil.VIDEO_BIT_RATE);
|
||||||
.setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
|
|
||||||
.setBitRate(VideoUtil.VIDEO_BIT_RATE);
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresPermission(permission.CAMERA)
|
@RequiresPermission(permission.CAMERA)
|
||||||
void bindToLifecycle(LifecycleOwner lifecycleOwner) {
|
void bindToLifecycle(LifecycleOwner lifecycleOwner) {
|
||||||
mNewLifecycle = lifecycleOwner;
|
mNewLifecycle = lifecycleOwner;
|
||||||
@@ -173,12 +171,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearCurrentLifecycle();
|
clearCurrentLifecycle();
|
||||||
|
if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
|
||||||
|
// Lifecycle is already in a destroyed state. Since it may have been a valid
|
||||||
|
// lifecycle when bound, but became destroyed while waiting for layout, treat this as
|
||||||
|
// a no-op now that we have cleared the previous lifecycle.
|
||||||
|
mNewLifecycle = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
mCurrentLifecycle = mNewLifecycle;
|
mCurrentLifecycle = mNewLifecycle;
|
||||||
mNewLifecycle = null;
|
mNewLifecycle = null;
|
||||||
if (mCurrentLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
||||||
mCurrentLifecycle = null;
|
|
||||||
throw new IllegalArgumentException("Cannot bind to lifecycle in a destroyed state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCameraProvider == null) {
|
if (mCameraProvider == null) {
|
||||||
// try again once the camera provider is no longer null
|
// try again once the camera provider is no longer null
|
||||||
@@ -188,18 +189,18 @@ final class CameraXModule {
|
|||||||
Set<Integer> available = getAvailableCameraLensFacing();
|
Set<Integer> available = getAvailableCameraLensFacing();
|
||||||
|
|
||||||
if (available.isEmpty()) {
|
if (available.isEmpty()) {
|
||||||
Log.w(TAG, "Unable to bindToLifeCycle since no cameras available");
|
Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
|
||||||
mCameraLensFacing = null;
|
mCameraLensFacing = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the current camera exists, or default to another camera
|
// Ensure the current camera exists, or default to another camera
|
||||||
if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
|
if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
|
||||||
Log.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
|
Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
|
||||||
|
|
||||||
// Default to the first available camera direction
|
// Default to the first available camera direction
|
||||||
mCameraLensFacing = available.iterator().next();
|
mCameraLensFacing = available.iterator().next();
|
||||||
|
|
||||||
Log.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
|
Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not attempt to create use cases for a null cameraLensFacing. This could occur if
|
// Do not attempt to create use cases for a null cameraLensFacing. This could occur if
|
||||||
@@ -216,14 +217,12 @@ final class CameraXModule {
|
|||||||
boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
|
boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
|
||||||
|| getDisplayRotationDegrees() == 180;
|
|| getDisplayRotationDegrees() == 180;
|
||||||
|
|
||||||
Rational targetAspectRatio;
|
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
|
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
Rational targetAspectRatio;
|
||||||
// mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_4_3);
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_4_3, isDisplayPortrait));
|
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_4_3, isDisplayPortrait));
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
@@ -232,7 +231,6 @@ final class CameraXModule {
|
|||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
|
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
// mImageCaptureBuilder.setTargetAspectRatio(AspectRatio.RATIO_16_9);
|
|
||||||
targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
|
targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,15 +243,14 @@ final class CameraXModule {
|
|||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
Size size = VideoUtil.getVideoRecordingSize();
|
Size size = VideoUtil.getVideoRecordingSize();
|
||||||
mVideoCaptureConfigBuilder.setTargetResolution(size);
|
mVideoCaptureBuilder.setTargetResolution(size);
|
||||||
mVideoCaptureConfigBuilder.setMaxResolution(size);
|
mVideoCaptureBuilder.setMaxResolution(size);
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
mVideoCaptureConfigBuilder.setTargetRotation(getDisplaySurfaceRotation());
|
mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
// Begin Signal Custom Code Block
|
||||||
if (MediaConstraints.isVideoTranscodeAvailable()) {
|
if (MediaConstraints.isVideoTranscodeAvailable()) {
|
||||||
mVideoCapture = new VideoCapture(mVideoCaptureConfigBuilder.getUseCaseConfig());
|
mVideoCapture = mVideoCaptureBuilder.build();
|
||||||
}
|
}
|
||||||
// End Signal Custom Code Block
|
// End Signal Custom Code Block
|
||||||
|
|
||||||
@@ -262,15 +259,15 @@ final class CameraXModule {
|
|||||||
mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
|
mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
|
||||||
|
|
||||||
mPreview = mPreviewBuilder.build();
|
mPreview = mPreviewBuilder.build();
|
||||||
mPreview.setSurfaceProvider(mCameraXView.getPreviewView().getPreviewSurfaceProvider());
|
mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
|
||||||
|
|
||||||
CameraSelector cameraSelector =
|
CameraSelector cameraSelector =
|
||||||
new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
|
new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
||||||
mImageCapture,
|
mImageCapture,
|
||||||
mPreview);
|
mPreview);
|
||||||
} else if (getCaptureMode() == CameraXView.CaptureMode.VIDEO) {
|
} else if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
|
||||||
mVideoCapture,
|
mVideoCapture,
|
||||||
mPreview);
|
mPreview);
|
||||||
@@ -301,7 +298,7 @@ final class CameraXModule {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.VIDEO) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,17 +309,32 @@ final class CameraXModule {
|
|||||||
mImageCapture.takePicture(executor, callback);
|
mImageCapture.takePicture(executor, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
|
||||||
@RequiresApi(26)
|
@NonNull Executor executor, OnImageSavedCallback callback) {
|
||||||
public void startRecording(FileDescriptor file,
|
if (mImageCapture == null) {
|
||||||
// End Signal Custom Code Block
|
return;
|
||||||
Executor executor,
|
}
|
||||||
final VideoCapture.OnVideoSavedCallback callback) {
|
|
||||||
|
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
|
||||||
|
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback == null) {
|
||||||
|
throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
|
||||||
|
&& mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
mImageCapture.takePicture(outputFileOptions, executor, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
|
||||||
|
Executor executor, final OnVideoSavedCallback callback) {
|
||||||
if (mVideoCapture == null) {
|
if (mVideoCapture == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getCaptureMode() == CameraXView.CaptureMode.IMAGE) {
|
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
|
||||||
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
|
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,15 +344,14 @@ final class CameraXModule {
|
|||||||
|
|
||||||
mVideoIsRecording.set(true);
|
mVideoIsRecording.set(true);
|
||||||
mVideoCapture.startRecording(
|
mVideoCapture.startRecording(
|
||||||
file,
|
outputFileOptions,
|
||||||
executor,
|
executor,
|
||||||
new VideoCapture.OnVideoSavedCallback() {
|
new VideoCapture.OnVideoSavedCallback() {
|
||||||
@Override
|
@Override
|
||||||
// Begin Signal Custom Code Block
|
public void onVideoSaved(
|
||||||
public void onVideoSaved(@NonNull FileDescriptor savedFile) {
|
@NonNull VideoCapture.OutputFileResults outputFileResults) {
|
||||||
// End Signal Custom Code Block
|
|
||||||
mVideoIsRecording.set(false);
|
mVideoIsRecording.set(false);
|
||||||
callback.onVideoSaved(savedFile);
|
callback.onVideoSaved(outputFileResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -349,15 +360,12 @@ final class CameraXModule {
|
|||||||
@NonNull String message,
|
@NonNull String message,
|
||||||
@Nullable Throwable cause) {
|
@Nullable Throwable cause) {
|
||||||
mVideoIsRecording.set(false);
|
mVideoIsRecording.set(false);
|
||||||
Log.e(TAG, message, cause);
|
Logger.e(TAG, message, cause);
|
||||||
callback.onError(videoCaptureError, message, cause);
|
callback.onError(videoCaptureError, message, cause);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin Signal Custom Code Block
|
|
||||||
@RequiresApi(26)
|
|
||||||
// End Signal Custom Code Block
|
|
||||||
public void stopRecording() {
|
public void stopRecording() {
|
||||||
if (mVideoCapture == null) {
|
if (mVideoCapture == null) {
|
||||||
return;
|
return;
|
||||||
@@ -388,14 +396,15 @@ final class CameraXModule {
|
|||||||
|
|
||||||
@RequiresPermission(permission.CAMERA)
|
@RequiresPermission(permission.CAMERA)
|
||||||
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
|
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
|
||||||
String cameraId;
|
if (mCameraProvider == null) {
|
||||||
try {
|
return false;
|
||||||
cameraId = CameraX.getCameraWithLensFacing(lensFacing);
|
}
|
||||||
} catch (Exception e) {
|
try {
|
||||||
throw new IllegalStateException("Unable to query lens facing.", e);
|
return mCameraProvider.hasCamera(
|
||||||
|
new CameraSelector.Builder().requireLensFacing(lensFacing).build());
|
||||||
|
} catch (CameraInfoUnavailableException e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cameraId != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -454,7 +463,7 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
}, CameraXExecutors.directExecutor());
|
}, CameraXExecutors.directExecutor());
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to set zoom ratio");
|
Logger.e(TAG, "Failed to set zoom ratio");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +495,10 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isBoundToLifecycle() {
|
||||||
|
return mCamera != null;
|
||||||
|
}
|
||||||
|
|
||||||
int getRelativeCameraOrientation(boolean compensateForMirroring) {
|
int getRelativeCameraOrientation(boolean compensateForMirroring) {
|
||||||
int rotationDegrees = 0;
|
int rotationDegrees = 0;
|
||||||
if (mCamera != null) {
|
if (mCamera != null) {
|
||||||
@@ -520,6 +533,11 @@ final class CameraXModule {
|
|||||||
if (!toUnbind.isEmpty()) {
|
if (!toUnbind.isEmpty()) {
|
||||||
mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
|
mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove surface provider once unbound.
|
||||||
|
if (mPreview != null) {
|
||||||
|
mPreview.setSurfaceProvider(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mCamera = null;
|
mCamera = null;
|
||||||
mCurrentLifecycle = null;
|
mCurrentLifecycle = null;
|
||||||
@@ -532,7 +550,7 @@ final class CameraXModule {
|
|||||||
mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
|
mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mVideoCapture != null && MediaConstraints.isVideoTranscodeAvailable()) {
|
if (mVideoCapture != null) {
|
||||||
mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
|
mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +585,7 @@ final class CameraXModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraInternal camera = mImageCapture.getBoundCamera();
|
CameraInternal camera = mImageCapture.getCamera();
|
||||||
|
|
||||||
if (camera == null) {
|
if (camera == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -614,15 +632,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
return mCameraXView.getContext();
|
return mCameraView.getContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return mCameraXView.getWidth();
|
return mCameraView.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return mCameraXView.getHeight();
|
return mCameraView.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDisplayRotationDegrees() {
|
public int getDisplayRotationDegrees() {
|
||||||
@@ -630,15 +648,15 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getDisplaySurfaceRotation() {
|
protected int getDisplaySurfaceRotation() {
|
||||||
return mCameraXView.getDisplaySurfaceRotation();
|
return mCameraView.getDisplaySurfaceRotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMeasuredWidth() {
|
private int getMeasuredWidth() {
|
||||||
return mCameraXView.getMeasuredWidth();
|
return mCameraView.getMeasuredWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMeasuredHeight() {
|
private int getMeasuredHeight() {
|
||||||
return mCameraXView.getMeasuredHeight();
|
return mCameraView.getMeasuredHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -647,11 +665,11 @@ final class CameraXModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public CameraXView.CaptureMode getCaptureMode() {
|
public SignalCameraView.CaptureMode getCaptureMode() {
|
||||||
return mCaptureMode;
|
return mCaptureMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCaptureMode(@NonNull CameraXView.CaptureMode captureMode) {
|
public void setCaptureMode(@NonNull SignalCameraView.CaptureMode captureMode) {
|
||||||
this.mCaptureMode = captureMode;
|
this.mCaptureMode = captureMode;
|
||||||
rebindToLifecycle();
|
rebindToLifecycle();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package org.signal.glide;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public final class Log {
|
||||||
|
|
||||||
|
private Log() {}
|
||||||
|
|
||||||
|
public static void v(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().v(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().d(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void i(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().i(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void w(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().w(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(@NonNull String tag, @NonNull String message) {
|
||||||
|
SignalGlideCodecs.getLogProvider().e(tag, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||||
|
SignalGlideCodecs.getLogProvider().e(tag, message, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Provider {
|
||||||
|
void v(@NonNull String tag, @NonNull String message);
|
||||||
|
void d(@NonNull String tag, @NonNull String message);
|
||||||
|
void i(@NonNull String tag, @NonNull String message);
|
||||||
|
void w(@NonNull String tag, @NonNull String message);
|
||||||
|
void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable);
|
||||||
|
|
||||||
|
Provider EMPTY = new Provider() {
|
||||||
|
@Override
|
||||||
|
public void v(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void w(@NonNull String tag, @NonNull String message) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(@NonNull String tag, @NonNull String message, @NonNull Throwable throwable) { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.signal.glide;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public final class SignalGlideCodecs {
|
||||||
|
|
||||||
|
private static Log.Provider logProvider = Log.Provider.EMPTY;
|
||||||
|
|
||||||
|
private SignalGlideCodecs() {}
|
||||||
|
|
||||||
|
public static void setLogProvider(@NonNull Log.Provider provider) {
|
||||||
|
logProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull Log.Provider getLogProvider() {
|
||||||
|
return logProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.decode.APNGDecoder;
|
||||||
|
import org.signal.glide.common.FrameAnimationDrawable;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.loader.AssetStreamLoader;
|
||||||
|
import org.signal.glide.common.loader.FileLoader;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
import org.signal.glide.common.loader.ResourceStreamLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGDrawable
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public class APNGDrawable extends FrameAnimationDrawable<APNGDecoder> {
|
||||||
|
public APNGDrawable(Loader provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGDrawable(APNGDecoder decoder) {
|
||||||
|
super(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGDecoder createFrameSeqDecoder(Loader streamLoader, FrameSeqDecoder.RenderListener listener) {
|
||||||
|
return new APNGDecoder(streamLoader, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static APNGDrawable fromAsset(Context context, String assetPath) {
|
||||||
|
AssetStreamLoader assetStreamLoader = new AssetStreamLoader(context, assetPath);
|
||||||
|
return new APNGDrawable(assetStreamLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APNGDrawable fromFile(String filePath) {
|
||||||
|
FileLoader fileLoader = new FileLoader(filePath);
|
||||||
|
return new APNGDrawable(fileLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APNGDrawable fromResource(Context context, int resId) {
|
||||||
|
ResourceStreamLoader resourceStreamLoader = new ResourceStreamLoader(context, resId);
|
||||||
|
return new APNGDrawable(resourceStreamLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27acTL.27:_The_Animation_Control_Chunk
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class ACTLChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("acTL");
|
||||||
|
int num_frames;
|
||||||
|
int num_plays;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader apngReader) throws IOException {
|
||||||
|
num_frames = apngReader.readInt();
|
||||||
|
num_plays = apngReader.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGDecoder extends FrameSeqDecoder<APNGReader, APNGWriter> {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(APNGDecoder.class);
|
||||||
|
|
||||||
|
private APNGWriter apngWriter;
|
||||||
|
private int mLoopCount;
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
|
||||||
|
private class SnapShot {
|
||||||
|
byte dispose_op;
|
||||||
|
Rect dstRect = new Rect();
|
||||||
|
ByteBuffer byteBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SnapShot snapShot = new SnapShot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loader webp的reader
|
||||||
|
* @param renderListener 渲染的回调
|
||||||
|
*/
|
||||||
|
public APNGDecoder(Loader loader, FrameSeqDecoder.RenderListener renderListener) {
|
||||||
|
super(loader, renderListener);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGWriter getWriter() {
|
||||||
|
if (apngWriter == null) {
|
||||||
|
apngWriter = new APNGWriter();
|
||||||
|
}
|
||||||
|
return apngWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected APNGReader getReader(Reader reader) {
|
||||||
|
return new APNGReader(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLoopCount() {
|
||||||
|
return mLoopCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void release() {
|
||||||
|
snapShot.byteBuffer = null;
|
||||||
|
apngWriter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Rect read(APNGReader reader) throws IOException {
|
||||||
|
List<Chunk> chunks = APNGParser.parse(reader);
|
||||||
|
List<Chunk> otherChunks = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean actl = false;
|
||||||
|
APNGFrame lastFrame = null;
|
||||||
|
byte[] ihdrData = new byte[0];
|
||||||
|
int canvasWidth = 0, canvasHeight = 0;
|
||||||
|
for (Chunk chunk : chunks) {
|
||||||
|
if (chunk instanceof ACTLChunk) {
|
||||||
|
mLoopCount = ((ACTLChunk) chunk).num_plays;
|
||||||
|
actl = true;
|
||||||
|
} else if (chunk instanceof FCTLChunk) {
|
||||||
|
APNGFrame frame = new APNGFrame(reader, (FCTLChunk) chunk);
|
||||||
|
frame.prefixChunks = otherChunks;
|
||||||
|
frame.ihdrData = ihdrData;
|
||||||
|
frames.add(frame);
|
||||||
|
lastFrame = frame;
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
if (lastFrame != null) {
|
||||||
|
lastFrame.imageChunks.add(chunk);
|
||||||
|
}
|
||||||
|
} else if (chunk instanceof IDATChunk) {
|
||||||
|
if (!actl) {
|
||||||
|
//如果为非APNG图片,则只解码PNG
|
||||||
|
Frame frame = new StillFrame(reader);
|
||||||
|
frame.frameWidth = canvasWidth;
|
||||||
|
frame.frameHeight = canvasHeight;
|
||||||
|
frames.add(frame);
|
||||||
|
mLoopCount = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (lastFrame != null) {
|
||||||
|
lastFrame.imageChunks.add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (chunk instanceof IHDRChunk) {
|
||||||
|
canvasWidth = ((IHDRChunk) chunk).width;
|
||||||
|
canvasHeight = ((IHDRChunk) chunk).height;
|
||||||
|
ihdrData = ((IHDRChunk) chunk).data;
|
||||||
|
} else if (!(chunk instanceof IENDChunk)) {
|
||||||
|
otherChunks.add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
snapShot.byteBuffer = ByteBuffer.allocate((canvasWidth * canvasHeight / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
return new Rect(0, 0, canvasWidth, canvasHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void renderFrame(Frame frame) {
|
||||||
|
if (frame == null || fullRect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = obtainBitmap(fullRect.width() / sampleSize, fullRect.height() / sampleSize);
|
||||||
|
Canvas canvas = cachedCanvas.get(bitmap);
|
||||||
|
if (canvas == null) {
|
||||||
|
canvas = new Canvas(bitmap);
|
||||||
|
cachedCanvas.put(bitmap, canvas);
|
||||||
|
}
|
||||||
|
if (frame instanceof APNGFrame) {
|
||||||
|
// 从缓存中恢复当前帧
|
||||||
|
frameBuffer.rewind();
|
||||||
|
bitmap.copyPixelsFromBuffer(frameBuffer);
|
||||||
|
// 开始绘制前,处理快照中的设定
|
||||||
|
if (this.frameIndex == 0) {
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
} else {
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRect(snapShot.dstRect);
|
||||||
|
switch (snapShot.dispose_op) {
|
||||||
|
// 从快照中恢复上一帧之前的显示内容
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_PREVIOUS:
|
||||||
|
snapShot.byteBuffer.rewind();
|
||||||
|
bitmap.copyPixelsFromBuffer(snapShot.byteBuffer);
|
||||||
|
break;
|
||||||
|
// 清空上一帧所画区域
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_BACKGROUND:
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
break;
|
||||||
|
// 什么都不做
|
||||||
|
case FCTLChunk.APNG_DISPOSE_OP_NON:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后根据dispose设定传递到快照信息中
|
||||||
|
if (((APNGFrame) frame).dispose_op == FCTLChunk.APNG_DISPOSE_OP_PREVIOUS) {
|
||||||
|
if (snapShot.dispose_op != FCTLChunk.APNG_DISPOSE_OP_PREVIOUS) {
|
||||||
|
snapShot.byteBuffer.rewind();
|
||||||
|
bitmap.copyPixelsToBuffer(snapShot.byteBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapShot.dispose_op = ((APNGFrame) frame).dispose_op;
|
||||||
|
canvas.save();
|
||||||
|
if (((APNGFrame) frame).blend_op == FCTLChunk.APNG_BLEND_OP_SOURCE) {
|
||||||
|
canvas.clipRect(
|
||||||
|
frame.frameX / sampleSize,
|
||||||
|
frame.frameY / sampleSize,
|
||||||
|
(frame.frameX + frame.frameWidth) / sampleSize,
|
||||||
|
(frame.frameY + frame.frameHeight) / sampleSize);
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
snapShot.dstRect.set(frame.frameX / sampleSize,
|
||||||
|
frame.frameY / sampleSize,
|
||||||
|
(frame.frameX + frame.frameWidth) / sampleSize,
|
||||||
|
(frame.frameY + frame.frameHeight) / sampleSize);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
//开始真正绘制当前帧的内容
|
||||||
|
Bitmap inBitmap = obtainBitmap(frame.frameWidth, frame.frameHeight);
|
||||||
|
recycleBitmap(frame.draw(canvas, paint, sampleSize, inBitmap, getWriter()));
|
||||||
|
recycleBitmap(inBitmap);
|
||||||
|
frameBuffer.rewind();
|
||||||
|
bitmap.copyPixelsToBuffer(frameBuffer);
|
||||||
|
recycleBitmap(bitmap);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to render!", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGFrame extends Frame<APNGReader, APNGWriter> {
|
||||||
|
public final byte blend_op;
|
||||||
|
public final byte dispose_op;
|
||||||
|
byte[] ihdrData;
|
||||||
|
List<Chunk> imageChunks = new ArrayList<>();
|
||||||
|
List<Chunk> prefixChunks = new ArrayList<>();
|
||||||
|
private static final byte[] sPNGSignatures = {(byte) 137, 80, 78, 71, 13, 10, 26, 10};
|
||||||
|
private static final byte[] sPNGEndChunk = {0, 0, 0, 0, 0x49, 0x45, 0x4E, 0x44, (byte) 0xAE, 0x42, 0x60, (byte) 0x82};
|
||||||
|
|
||||||
|
private static ThreadLocal<CRC32> sCRC32 = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private CRC32 getCRC32() {
|
||||||
|
CRC32 crc32 = sCRC32.get();
|
||||||
|
if (crc32 == null) {
|
||||||
|
crc32 = new CRC32();
|
||||||
|
sCRC32.set(crc32);
|
||||||
|
}
|
||||||
|
return crc32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGFrame(APNGReader reader, FCTLChunk fctlChunk) {
|
||||||
|
super(reader);
|
||||||
|
blend_op = fctlChunk.blend_op;
|
||||||
|
dispose_op = fctlChunk.dispose_op;
|
||||||
|
frameDuration = fctlChunk.delay_num * 1000 / (fctlChunk.delay_den == 0 ? 100 : fctlChunk.delay_den);
|
||||||
|
frameWidth = fctlChunk.width;
|
||||||
|
frameHeight = fctlChunk.height;
|
||||||
|
frameX = fctlChunk.x_offset;
|
||||||
|
frameY = fctlChunk.y_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int encode(APNGWriter apngWriter) throws IOException {
|
||||||
|
int fileSize = 8 + 13 + 12;
|
||||||
|
|
||||||
|
//prefixChunks
|
||||||
|
for (Chunk chunk : prefixChunks) {
|
||||||
|
fileSize += chunk.length + 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
//imageChunks
|
||||||
|
for (Chunk chunk : imageChunks) {
|
||||||
|
if (chunk instanceof IDATChunk) {
|
||||||
|
fileSize += chunk.length + 12;
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
fileSize += chunk.length + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileSize += sPNGEndChunk.length;
|
||||||
|
apngWriter.reset(fileSize);
|
||||||
|
apngWriter.putBytes(sPNGSignatures);
|
||||||
|
//IHDR Chunk
|
||||||
|
apngWriter.writeInt(13);
|
||||||
|
int start = apngWriter.position();
|
||||||
|
apngWriter.writeFourCC(IHDRChunk.ID);
|
||||||
|
apngWriter.writeInt(frameWidth);
|
||||||
|
apngWriter.writeInt(frameHeight);
|
||||||
|
apngWriter.putBytes(ihdrData);
|
||||||
|
CRC32 crc32 = getCRC32();
|
||||||
|
crc32.reset();
|
||||||
|
crc32.update(apngWriter.toByteArray(), start, 17);
|
||||||
|
apngWriter.writeInt((int) crc32.getValue());
|
||||||
|
|
||||||
|
//prefixChunks
|
||||||
|
for (Chunk chunk : prefixChunks) {
|
||||||
|
if (chunk instanceof IENDChunk) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reader.reset();
|
||||||
|
reader.skip(chunk.offset);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length + 12);
|
||||||
|
apngWriter.skip(chunk.length + 12);
|
||||||
|
}
|
||||||
|
//imageChunks
|
||||||
|
for (Chunk chunk : imageChunks) {
|
||||||
|
if (chunk instanceof IDATChunk) {
|
||||||
|
reader.reset();
|
||||||
|
reader.skip(chunk.offset);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length + 12);
|
||||||
|
apngWriter.skip(chunk.length + 12);
|
||||||
|
} else if (chunk instanceof FDATChunk) {
|
||||||
|
apngWriter.writeInt(chunk.length - 4);
|
||||||
|
start = apngWriter.position();
|
||||||
|
apngWriter.writeFourCC(IDATChunk.ID);
|
||||||
|
|
||||||
|
reader.reset();
|
||||||
|
// skip to fdat data position
|
||||||
|
reader.skip(chunk.offset + 4 + 4 + 4);
|
||||||
|
reader.read(apngWriter.toByteArray(), apngWriter.position(), chunk.length - 4);
|
||||||
|
|
||||||
|
apngWriter.skip(chunk.length - 4);
|
||||||
|
crc32.reset();
|
||||||
|
crc32.update(apngWriter.toByteArray(), start, chunk.length);
|
||||||
|
apngWriter.writeInt((int) crc32.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endChunk
|
||||||
|
apngWriter.putBytes(sPNGEndChunk);
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, APNGWriter writer) {
|
||||||
|
try {
|
||||||
|
int length = encode(writer);
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
options.inSampleSize = sampleSize;
|
||||||
|
options.inMutable = true;
|
||||||
|
options.inBitmap = reusedBitmap;
|
||||||
|
byte[] bytes = writer.toByteArray();
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, length, options);
|
||||||
|
assert bitmap != null;
|
||||||
|
canvas.drawBitmap(bitmap, (float) frameX / sampleSize, (float) frameY / sampleSize, paint);
|
||||||
|
return bitmap;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.StreamReader;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link {https://www.w3.org/TR/PNG/#5PNG-file-signature}
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGParser {
|
||||||
|
static class FormatException extends IOException {
|
||||||
|
FormatException() {
|
||||||
|
super("APNG Format error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(String filePath) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = new FileInputStream(filePath);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Context context, String assetPath) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getAssets().open(assetPath);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Context context, int resId) {
|
||||||
|
InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getResources().openRawResource(resId);
|
||||||
|
return isAPNG(new StreamReader(inputStream));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAPNG(Reader in) {
|
||||||
|
APNGReader reader = (in instanceof APNGReader) ? (APNGReader) in : new APNGReader(in);
|
||||||
|
try {
|
||||||
|
if (!reader.matchFourCC("\u0089PNG") || !reader.matchFourCC("\r\n\u001a\n")) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
while (reader.available() > 0) {
|
||||||
|
Chunk chunk = parseChunk(reader);
|
||||||
|
if (chunk instanceof ACTLChunk) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Chunk> parse(APNGReader reader) throws IOException {
|
||||||
|
if (!reader.matchFourCC("\u0089PNG") || !reader.matchFourCC("\r\n\u001a\n")) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Chunk> chunks = new ArrayList<>();
|
||||||
|
while (reader.available() > 0) {
|
||||||
|
chunks.add(parseChunk(reader));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Chunk parseChunk(APNGReader reader) throws IOException {
|
||||||
|
int offset = reader.position();
|
||||||
|
int size = reader.readInt();
|
||||||
|
int fourCC = reader.readFourCC();
|
||||||
|
Chunk chunk;
|
||||||
|
if (fourCC == ACTLChunk.ID) {
|
||||||
|
chunk = new ACTLChunk();
|
||||||
|
} else if (fourCC == FCTLChunk.ID) {
|
||||||
|
chunk = new FCTLChunk();
|
||||||
|
} else if (fourCC == FDATChunk.ID) {
|
||||||
|
chunk = new FDATChunk();
|
||||||
|
} else if (fourCC == IDATChunk.ID) {
|
||||||
|
chunk = new IDATChunk();
|
||||||
|
} else if (fourCC == IENDChunk.ID) {
|
||||||
|
chunk = new IENDChunk();
|
||||||
|
} else if (fourCC == IHDRChunk.ID) {
|
||||||
|
chunk = new IHDRChunk();
|
||||||
|
} else {
|
||||||
|
chunk = new Chunk();
|
||||||
|
}
|
||||||
|
chunk.offset = offset;
|
||||||
|
chunk.fourcc = fourCC;
|
||||||
|
chunk.length = size;
|
||||||
|
chunk.parse(reader);
|
||||||
|
chunk.crc = reader.readInt();
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Length (长度) 4字节 指定数据块中数据域的长度,其长度不超过(231-1)字节
|
||||||
|
* Chunk Type Code (数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成
|
||||||
|
* Chunk Data (数据块数据) 可变长度 存储按照Chunk Type Code指定的数据
|
||||||
|
* CRC (循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码
|
||||||
|
* @Link https://www.w3.org/TR/PNG
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class Chunk {
|
||||||
|
int length;
|
||||||
|
int fourcc;
|
||||||
|
int crc;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
static int fourCCToInt(String fourCC) {
|
||||||
|
if (TextUtils.isEmpty(fourCC) || fourCC.length() != 4) {
|
||||||
|
return 0xbadeffff;
|
||||||
|
}
|
||||||
|
return (fourCC.charAt(0) & 0xff)
|
||||||
|
| (fourCC.charAt(1) & 0xff) << 8
|
||||||
|
| (fourCC.charAt(2) & 0xff) << 16
|
||||||
|
| (fourCC.charAt(3) & 0xff) << 24
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse(APNGReader reader) throws IOException {
|
||||||
|
int available = reader.available();
|
||||||
|
innerParse(reader);
|
||||||
|
int offset = available - reader.available();
|
||||||
|
if (offset > length) {
|
||||||
|
throw new IOException("Out of chunk area");
|
||||||
|
} else if (offset < length) {
|
||||||
|
reader.skip(length - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
* @see {link=https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27fcTL.27:_The_Frame_Control_Chunk}
|
||||||
|
*/
|
||||||
|
class FCTLChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("fcTL");
|
||||||
|
int sequence_number;
|
||||||
|
/**
|
||||||
|
* x_offset >= 0
|
||||||
|
* y_offset >= 0
|
||||||
|
* width > 0
|
||||||
|
* height > 0
|
||||||
|
* x_offset + width <= 'IHDR' width
|
||||||
|
* y_offset + height <= 'IHDR' height
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Width of the following frame.
|
||||||
|
*/
|
||||||
|
int width;
|
||||||
|
/**
|
||||||
|
* Height of the following frame.
|
||||||
|
*/
|
||||||
|
int height;
|
||||||
|
/**
|
||||||
|
* X position at which to render the following frame.
|
||||||
|
*/
|
||||||
|
int x_offset;
|
||||||
|
/**
|
||||||
|
* Y position at which to render the following frame.
|
||||||
|
*/
|
||||||
|
int y_offset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay_num and delay_den parameters together specify a fraction indicating the time to
|
||||||
|
* display the current frame, in seconds. If the denominator is 0, it is to be treated as if it
|
||||||
|
* were 100 (that is, delay_num then specifies 1/100ths of a second).
|
||||||
|
* If the the value of the numerator is 0 the decoder should render the next frame as quickly as
|
||||||
|
* possible, though viewers may impose a reasonable lower bound.
|
||||||
|
* <p>
|
||||||
|
* Frame timings should be independent of the time required for decoding and display of each frame,
|
||||||
|
* so that animations will run at the same rate regardless of the performance of the decoder implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame delay fraction numerator.
|
||||||
|
*/
|
||||||
|
short delay_num;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame delay fraction denominator.
|
||||||
|
*/
|
||||||
|
short delay_den;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of frame area disposal to be done after rendering this frame.
|
||||||
|
* dispose_op specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
|
||||||
|
* If the first 'fcTL' chunk uses a dispose_op of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
|
||||||
|
*/
|
||||||
|
byte dispose_op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of frame area rendering for this frame.
|
||||||
|
*/
|
||||||
|
byte blend_op;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_NON = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_BACKGROUND = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.
|
||||||
|
*/
|
||||||
|
static final int APNG_DISPOSE_OP_PREVIOUS = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* blend_op<code> specifies whether the frame is to be alpha blended into the current output buffer content,
|
||||||
|
* or whether it should completely replace its region in the output buffer.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
|
||||||
|
*/
|
||||||
|
static final int APNG_BLEND_OP_SOURCE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frame should be composited onto the output buffer based on its alpha,
|
||||||
|
* using a simple OVER operation as described in the Alpha Channel Processing section of the Extensions
|
||||||
|
* to the PNG Specification, Version 1.2.0. Note that the second variation of the sample code is applicable.
|
||||||
|
*/
|
||||||
|
static final int APNG_BLEND_OP_OVER = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
sequence_number = reader.readInt();
|
||||||
|
width = reader.readInt();
|
||||||
|
height = reader.readInt();
|
||||||
|
x_offset = reader.readInt();
|
||||||
|
y_offset = reader.readInt();
|
||||||
|
delay_num = reader.readShort();
|
||||||
|
delay_den = reader.readShort();
|
||||||
|
dispose_op = reader.peek();
|
||||||
|
blend_op = reader.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/APNG#.27fdAT.27:_The_Frame_Data_Chunk
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class FDATChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("fdAT");
|
||||||
|
int sequence_number;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
sequence_number = reader.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 作用描述
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IDATChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("IDAT");
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 作用描述
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IENDChunk extends Chunk {
|
||||||
|
static final int ID = Chunk.fourCCToInt("IEND");
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IHDR chunk shall be the first chunk in the PNG datastream. It contains:
|
||||||
|
* <p>
|
||||||
|
* Width 4 bytes
|
||||||
|
* Height 4 bytes
|
||||||
|
* Bit depth 1 byte
|
||||||
|
* Colour type 1 byte
|
||||||
|
* Compression method 1 byte
|
||||||
|
* Filter method 1 byte
|
||||||
|
* Interlace method 1 byte
|
||||||
|
*
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
class IHDRChunk extends Chunk {
|
||||||
|
static final int ID = fourCCToInt("IHDR");
|
||||||
|
/**
|
||||||
|
* 图像宽度,以像素为单位
|
||||||
|
*/
|
||||||
|
int width;
|
||||||
|
/**
|
||||||
|
* 图像高度,以像素为单位
|
||||||
|
*/
|
||||||
|
int height;
|
||||||
|
|
||||||
|
byte[] data = new byte[5];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void innerParse(APNGReader reader) throws IOException {
|
||||||
|
width = reader.readInt();
|
||||||
|
height = reader.readInt();
|
||||||
|
reader.read(data, 0, data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.apng.io.APNGReader;
|
||||||
|
import org.signal.glide.apng.io.APNGWriter;
|
||||||
|
import org.signal.glide.common.decode.Frame;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class StillFrame extends Frame<APNGReader, APNGWriter> {
|
||||||
|
|
||||||
|
public StillFrame(APNGReader reader) {
|
||||||
|
super(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, APNGWriter writer) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
options.inSampleSize = sampleSize;
|
||||||
|
options.inMutable = true;
|
||||||
|
options.inBitmap = reusedBitmap;
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
try {
|
||||||
|
reader.reset();
|
||||||
|
bitmap = BitmapFactory.decodeStream(reader.toInputStream(), null, options);
|
||||||
|
assert bitmap != null;
|
||||||
|
paint.setXfermode(null);
|
||||||
|
canvas.drawBitmap(bitmap, 0, 0, paint);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.io;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.FilterReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGReader extends FilterReader {
|
||||||
|
private static ThreadLocal<byte[]> __intBytes = new ThreadLocal<>();
|
||||||
|
|
||||||
|
|
||||||
|
protected static byte[] ensureBytes() {
|
||||||
|
byte[] bytes = __intBytes.get();
|
||||||
|
if (bytes == null) {
|
||||||
|
bytes = new byte[4];
|
||||||
|
__intBytes.set(bytes);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public APNGReader(Reader in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readInt() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 4);
|
||||||
|
return buf[3] & 0xFF |
|
||||||
|
(buf[2] & 0xFF) << 8 |
|
||||||
|
(buf[1] & 0xFF) << 16 |
|
||||||
|
(buf[0] & 0xFF) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short readShort() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 2);
|
||||||
|
return (short) (buf[1] & 0xFF |
|
||||||
|
(buf[0] & 0xFF) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return read FourCC and match chars
|
||||||
|
*/
|
||||||
|
public boolean matchFourCC(String chars) throws IOException {
|
||||||
|
if (TextUtils.isEmpty(chars) || chars.length() != 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int fourCC = readFourCC();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (((fourCC >> (i * 8)) & 0xff) != chars.charAt(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readFourCC() throws IOException {
|
||||||
|
byte[] buf = ensureBytes();
|
||||||
|
read(buf, 0, 4);
|
||||||
|
return buf[0] & 0xff | (buf[1] & 0xff) << 8 | (buf[2] & 0xff) << 16 | (buf[3] & 0xff) << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.apng.io;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.ByteBufferWriter;
|
||||||
|
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNGWriter
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public class APNGWriter extends ByteBufferWriter {
|
||||||
|
public APNGWriter() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeFourCC(int val) {
|
||||||
|
putByte((byte) (val & 0xff));
|
||||||
|
putByte((byte) ((val >> 8) & 0xff));
|
||||||
|
putByte((byte) ((val >> 16) & 0xff));
|
||||||
|
putByte((byte) ((val >> 24) & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeInt(int val) {
|
||||||
|
putByte((byte) ((val >> 24) & 0xff));
|
||||||
|
putByte((byte) ((val >> 16) & 0xff));
|
||||||
|
putByte((byte) ((val >> 8) & 0xff));
|
||||||
|
putByte((byte) (val & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(int size) {
|
||||||
|
super.reset(size);
|
||||||
|
this.byteBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.DrawFilter;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PaintFlagsDrawFilter;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.common.decode.FrameSeqDecoder;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Frame animation drawable
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public abstract class FrameAnimationDrawable<Decoder extends FrameSeqDecoder> extends Drawable implements Animatable2Compat, FrameSeqDecoder.RenderListener {
|
||||||
|
private static final String TAG = Log.tag(FrameAnimationDrawable.class);
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
private final Decoder frameSeqDecoder;
|
||||||
|
private DrawFilter drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
private Matrix matrix = new Matrix();
|
||||||
|
private Set<AnimationCallback> animationCallbacks = new HashSet<>();
|
||||||
|
private Bitmap bitmap;
|
||||||
|
private static final int MSG_ANIMATION_START = 1;
|
||||||
|
private static final int MSG_ANIMATION_END = 2;
|
||||||
|
private Handler uiHandler = new Handler(Looper.getMainLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_ANIMATION_START:
|
||||||
|
for (AnimationCallback animationCallback : animationCallbacks) {
|
||||||
|
animationCallback.onAnimationStart(FrameAnimationDrawable.this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_ANIMATION_END:
|
||||||
|
for (AnimationCallback animationCallback : animationCallbacks) {
|
||||||
|
animationCallback.onAnimationEnd(FrameAnimationDrawable.this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private Runnable invalidateRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private boolean autoPlay = true;
|
||||||
|
|
||||||
|
public FrameAnimationDrawable(Decoder frameSeqDecoder) {
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
this.frameSeqDecoder = frameSeqDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameAnimationDrawable(Loader provider) {
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
this.frameSeqDecoder = createFrameSeqDecoder(provider, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoPlay(boolean autoPlay) {
|
||||||
|
this.autoPlay = autoPlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Decoder createFrameSeqDecoder(Loader streamLoader, FrameSeqDecoder.RenderListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loopLimit <=0为无限播放,>0为实际播放次数
|
||||||
|
*/
|
||||||
|
public void setLoopLimit(int loopLimit) {
|
||||||
|
frameSeqDecoder.setLoopLimit(loopLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
frameSeqDecoder.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
frameSeqDecoder.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
frameSeqDecoder.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return frameSeqDecoder.isPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (autoPlay) {
|
||||||
|
frameSeqDecoder.start();
|
||||||
|
} else {
|
||||||
|
this.frameSeqDecoder.addRenderListener(this);
|
||||||
|
if (!this.frameSeqDecoder.isRunning()) {
|
||||||
|
this.frameSeqDecoder.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (autoPlay) {
|
||||||
|
frameSeqDecoder.stop();
|
||||||
|
} else {
|
||||||
|
this.frameSeqDecoder.removeRenderListener(this);
|
||||||
|
this.frameSeqDecoder.stopIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return frameSeqDecoder.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
if (bitmap == null || bitmap.isRecycled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas.setDrawFilter(drawFilter);
|
||||||
|
canvas.drawBitmap(bitmap, matrix, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBounds(int left, int top, int right, int bottom) {
|
||||||
|
super.setBounds(left, top, right, bottom);
|
||||||
|
boolean sampleSizeChanged = frameSeqDecoder.setDesiredSize(getBounds().width(), getBounds().height());
|
||||||
|
matrix.setScale(
|
||||||
|
1.0f * getBounds().width() * frameSeqDecoder.getSampleSize() / frameSeqDecoder.getBounds().width(),
|
||||||
|
1.0f * getBounds().height() * frameSeqDecoder.getSampleSize() / frameSeqDecoder.getBounds().height());
|
||||||
|
|
||||||
|
if (sampleSizeChanged)
|
||||||
|
this.bitmap = Bitmap.createBitmap(
|
||||||
|
frameSeqDecoder.getBounds().width() / frameSeqDecoder.getSampleSize(),
|
||||||
|
frameSeqDecoder.getBounds().height() / frameSeqDecoder.getSampleSize(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
paint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter colorFilter) {
|
||||||
|
paint.setColorFilter(colorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
Message.obtain(uiHandler, MSG_ANIMATION_START).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRender(ByteBuffer byteBuffer) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.bitmap == null || this.bitmap.isRecycled()) {
|
||||||
|
this.bitmap = Bitmap.createBitmap(
|
||||||
|
frameSeqDecoder.getBounds().width() / frameSeqDecoder.getSampleSize(),
|
||||||
|
frameSeqDecoder.getBounds().height() / frameSeqDecoder.getSampleSize(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
byteBuffer.rewind();
|
||||||
|
if (byteBuffer.remaining() < this.bitmap.getByteCount()) {
|
||||||
|
Log.e(TAG, "onRender:Buffer not large enough for pixels");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.bitmap.copyPixelsFromBuffer(byteBuffer);
|
||||||
|
uiHandler.post(invalidateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd() {
|
||||||
|
Message.obtain(uiHandler, MSG_ANIMATION_END).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setVisible(boolean visible, boolean restart) {
|
||||||
|
if (this.autoPlay) {
|
||||||
|
if (visible) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
} else if (isRunning()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.setVisible(visible, restart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicWidth() {
|
||||||
|
try {
|
||||||
|
return frameSeqDecoder.getBounds().width();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicHeight() {
|
||||||
|
try {
|
||||||
|
return frameSeqDecoder.getBounds().height();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerAnimationCallback(@NonNull AnimationCallback animationCallback) {
|
||||||
|
this.animationCallbacks.add(animationCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unregisterAnimationCallback(@NonNull AnimationCallback animationCallback) {
|
||||||
|
return this.animationCallbacks.remove(animationCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAnimationCallbacks() {
|
||||||
|
this.animationCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: One frame in an animation
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-13
|
||||||
|
*/
|
||||||
|
public abstract class Frame<R extends Reader, W extends Writer> {
|
||||||
|
protected final R reader;
|
||||||
|
public int frameWidth;
|
||||||
|
public int frameHeight;
|
||||||
|
public int frameX;
|
||||||
|
public int frameY;
|
||||||
|
public int frameDuration;
|
||||||
|
|
||||||
|
public Frame(R reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Bitmap draw(Canvas canvas, Paint paint, int sampleSize, Bitmap reusedBitmap, W writer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.decode;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.glide.common.executor.FrameDecoderExecutor;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.Writer;
|
||||||
|
import org.signal.glide.common.loader.Loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Abstract Frame Animation Decoder
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/27
|
||||||
|
*/
|
||||||
|
public abstract class FrameSeqDecoder<R extends Reader, W extends Writer> {
|
||||||
|
private static final String TAG = Log.tag(FrameSeqDecoder.class);
|
||||||
|
private final int taskId;
|
||||||
|
|
||||||
|
private final Loader mLoader;
|
||||||
|
private final Handler workerHandler;
|
||||||
|
protected List<Frame> frames = new ArrayList<>();
|
||||||
|
protected int frameIndex = -1;
|
||||||
|
private int playCount;
|
||||||
|
private Integer loopLimit = null;
|
||||||
|
private Set<RenderListener> renderListeners = new HashSet<>();
|
||||||
|
private AtomicBoolean paused = new AtomicBoolean(true);
|
||||||
|
private static final Rect RECT_EMPTY = new Rect();
|
||||||
|
private Runnable renderTask = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (paused.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (canStep()) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long delay = step();
|
||||||
|
long cost = System.currentTimeMillis() - start;
|
||||||
|
workerHandler.postDelayed(this, Math.max(0, delay - cost));
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onRender(frameBuffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
protected int sampleSize = 1;
|
||||||
|
|
||||||
|
private Set<Bitmap> cacheBitmaps = new HashSet<>();
|
||||||
|
protected Map<Bitmap, Canvas> cachedCanvas = new WeakHashMap<>();
|
||||||
|
protected ByteBuffer frameBuffer;
|
||||||
|
protected volatile Rect fullRect;
|
||||||
|
private W mWriter = getWriter();
|
||||||
|
private R mReader = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If played all the needed
|
||||||
|
*/
|
||||||
|
private boolean finished = false;
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
IDLE,
|
||||||
|
RUNNING,
|
||||||
|
INITIALIZING,
|
||||||
|
FINISHING,
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile State mState = State.IDLE;
|
||||||
|
|
||||||
|
public Loader getLoader() {
|
||||||
|
return mLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract W getWriter();
|
||||||
|
|
||||||
|
protected abstract R getReader(Reader reader);
|
||||||
|
|
||||||
|
protected Bitmap obtainBitmap(int width, int height) {
|
||||||
|
Bitmap ret = null;
|
||||||
|
Iterator<Bitmap> iterator = cacheBitmaps.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
int reuseSize = width * height * 4;
|
||||||
|
ret = iterator.next();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
if (ret != null && ret.getAllocationByteCount() >= reuseSize) {
|
||||||
|
iterator.remove();
|
||||||
|
if (ret.getWidth() != width || ret.getHeight() != height) {
|
||||||
|
ret.reconfigure(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
}
|
||||||
|
ret.eraseColor(0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ret != null && ret.getByteCount() >= reuseSize) {
|
||||||
|
if (ret.getWidth() == width && ret.getHeight() == height) {
|
||||||
|
iterator.remove();
|
||||||
|
ret.eraseColor(0);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Bitmap.Config config = Bitmap.Config.ARGB_8888;
|
||||||
|
ret = Bitmap.createBitmap(width, height, config);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void recycleBitmap(Bitmap bitmap) {
|
||||||
|
if (bitmap != null && !cacheBitmaps.contains(bitmap)) {
|
||||||
|
cacheBitmaps.add(bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码器的渲染回调
|
||||||
|
*/
|
||||||
|
public interface RenderListener {
|
||||||
|
/**
|
||||||
|
* 播放开始
|
||||||
|
*/
|
||||||
|
void onStart();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帧播放
|
||||||
|
*/
|
||||||
|
void onRender(ByteBuffer byteBuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放结束
|
||||||
|
*/
|
||||||
|
void onEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loader webp的reader
|
||||||
|
* @param renderListener 渲染的回调
|
||||||
|
*/
|
||||||
|
public FrameSeqDecoder(Loader loader, @Nullable RenderListener renderListener) {
|
||||||
|
this.mLoader = loader;
|
||||||
|
if (renderListener != null) {
|
||||||
|
this.renderListeners.add(renderListener);
|
||||||
|
}
|
||||||
|
this.taskId = FrameDecoderExecutor.getInstance().generateTaskId();
|
||||||
|
this.workerHandler = new Handler(FrameDecoderExecutor.getInstance().getLooper(taskId));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addRenderListener(final RenderListener renderListener) {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
renderListeners.add(renderListener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeRenderListener(final RenderListener renderListener) {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
renderListeners.remove(renderListener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopIfNeeded() {
|
||||||
|
this.workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (renderListeners.size() == 0) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect getBounds() {
|
||||||
|
if (fullRect == null) {
|
||||||
|
if (mState == State.FINISHING) {
|
||||||
|
Log.e(TAG, "In finishing,do not interrupt");
|
||||||
|
}
|
||||||
|
final Thread thread = Thread.currentThread();
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (fullRect == null) {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fullRect = RECT_EMPTY;
|
||||||
|
} finally {
|
||||||
|
LockSupport.unpark(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
LockSupport.park(thread);
|
||||||
|
}
|
||||||
|
return fullRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCanvasBounds(Rect rect) {
|
||||||
|
fullRect = rect;
|
||||||
|
frameBuffer = ByteBuffer.allocate((rect.width() * rect.height() / (sampleSize * sampleSize) + 1) * 4);
|
||||||
|
if (mWriter == null) {
|
||||||
|
mWriter = getWriter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int getFrameCount() {
|
||||||
|
return this.frames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Loop Count defined in file
|
||||||
|
*/
|
||||||
|
protected abstract int getLoopCount();
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (fullRect == RECT_EMPTY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.RUNNING || mState == State.INITIALIZING) {
|
||||||
|
Log.i(TAG, debugInfo() + " Already started");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.FINISHING) {
|
||||||
|
Log.e(TAG, debugInfo() + " Processing,wait for finish at " + mState);
|
||||||
|
}
|
||||||
|
mState = State.INITIALIZING;
|
||||||
|
if (Looper.myLooper() == workerHandler.getLooper()) {
|
||||||
|
innerStart();
|
||||||
|
} else {
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void innerStart() {
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
try {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Log.i(TAG, debugInfo() + " Set state to RUNNING,cost " + (System.currentTimeMillis() - start));
|
||||||
|
mState = State.RUNNING;
|
||||||
|
}
|
||||||
|
if (getNumPlays() == 0 || !finished) {
|
||||||
|
this.frameIndex = -1;
|
||||||
|
renderTask.run();
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onStart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, debugInfo() + " No need to started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private void innerStop() {
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
frames.clear();
|
||||||
|
for (Bitmap bitmap : cacheBitmaps) {
|
||||||
|
if (bitmap != null && !bitmap.isRecycled()) {
|
||||||
|
bitmap.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheBitmaps.clear();
|
||||||
|
if (frameBuffer != null) {
|
||||||
|
frameBuffer = null;
|
||||||
|
}
|
||||||
|
cachedCanvas.clear();
|
||||||
|
try {
|
||||||
|
if (mReader != null) {
|
||||||
|
mReader.close();
|
||||||
|
mReader = null;
|
||||||
|
}
|
||||||
|
if (mWriter != null) {
|
||||||
|
mWriter.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
release();
|
||||||
|
mState = State.IDLE;
|
||||||
|
for (RenderListener renderListener : renderListeners) {
|
||||||
|
renderListener.onEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (fullRect == RECT_EMPTY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.FINISHING || mState == State.IDLE) {
|
||||||
|
Log.i(TAG, debugInfo() + "No need to stop");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mState == State.INITIALIZING) {
|
||||||
|
Log.e(TAG, debugInfo() + "Processing,wait for finish at " + mState);
|
||||||
|
}
|
||||||
|
mState = State.FINISHING;
|
||||||
|
if (Looper.myLooper() == workerHandler.getLooper()) {
|
||||||
|
innerStop();
|
||||||
|
} else {
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String debugInfo() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void release();
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return mState == State.RUNNING || mState == State.INITIALIZING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return paused.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoopLimit(int limit) {
|
||||||
|
this.loopLimit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.playCount = 0;
|
||||||
|
this.frameIndex = -1;
|
||||||
|
this.finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pause() {
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
paused.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
workerHandler.post(renderTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getSampleSize() {
|
||||||
|
return sampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setDesiredSize(int width, int height) {
|
||||||
|
boolean sampleSizeChanged = false;
|
||||||
|
int sample = getDesiredSample(width, height);
|
||||||
|
if (sample != this.sampleSize) {
|
||||||
|
this.sampleSize = sample;
|
||||||
|
sampleSizeChanged = true;
|
||||||
|
final boolean tempRunning = isRunning();
|
||||||
|
workerHandler.removeCallbacks(renderTask);
|
||||||
|
workerHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
innerStop();
|
||||||
|
try {
|
||||||
|
initCanvasBounds(read(getReader(mLoader.obtain())));
|
||||||
|
if (tempRunning) {
|
||||||
|
innerStart();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sampleSizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getDesiredSample(int desiredWidth, int desiredHeight) {
|
||||||
|
if (desiredWidth == 0 || desiredHeight == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int radio = Math.min(getBounds().width() / desiredWidth, getBounds().height() / desiredHeight);
|
||||||
|
int sample = 1;
|
||||||
|
while ((sample * 2) <= radio) {
|
||||||
|
sample *= 2;
|
||||||
|
}
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Rect read(R reader) throws IOException;
|
||||||
|
|
||||||
|
private int getNumPlays() {
|
||||||
|
return this.loopLimit != null ? this.loopLimit : this.getLoopCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canStep() {
|
||||||
|
if (!isRunning()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getNumPlays() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.playCount < getNumPlays() - 1) {
|
||||||
|
return true;
|
||||||
|
} else if (this.playCount == getNumPlays() - 1 && this.frameIndex < this.getFrameCount() - 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finished = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private long step() {
|
||||||
|
this.frameIndex++;
|
||||||
|
if (this.frameIndex >= this.getFrameCount()) {
|
||||||
|
this.frameIndex = 0;
|
||||||
|
this.playCount++;
|
||||||
|
}
|
||||||
|
Frame frame = getFrame(this.frameIndex);
|
||||||
|
if (frame == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
renderFrame(frame);
|
||||||
|
return frame.frameDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void renderFrame(Frame frame);
|
||||||
|
|
||||||
|
private Frame getFrame(int index) {
|
||||||
|
if (index < 0 || index >= frames.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return frames.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Indexed frame
|
||||||
|
*
|
||||||
|
* @param index <0 means reverse from last index
|
||||||
|
*/
|
||||||
|
public Bitmap getFrameBitmap(int index) throws IOException {
|
||||||
|
if (mState != State.IDLE) {
|
||||||
|
Log.e(TAG, debugInfo() + ",stop first");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mState = State.RUNNING;
|
||||||
|
paused.compareAndSet(true, false);
|
||||||
|
if (frames.size() == 0) {
|
||||||
|
if (mReader == null) {
|
||||||
|
mReader = getReader(mLoader.obtain());
|
||||||
|
} else {
|
||||||
|
mReader.reset();
|
||||||
|
}
|
||||||
|
initCanvasBounds(read(mReader));
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
index += this.frames.size();
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
frameIndex = -1;
|
||||||
|
while (frameIndex < index) {
|
||||||
|
if (canStep()) {
|
||||||
|
step();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameBuffer.rewind();
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(getBounds().width() / getSampleSize(), getBounds().height() / getSampleSize(), Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.copyPixelsFromBuffer(frameBuffer);
|
||||||
|
innerStop();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.executor;
|
||||||
|
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: com.github.penfeizhou.animation.executor
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-11-21
|
||||||
|
*/
|
||||||
|
public class FrameDecoderExecutor {
|
||||||
|
private static int sPoolNumber = 4;
|
||||||
|
private ArrayList<HandlerThread> mHandlerThreadGroup = new ArrayList<>();
|
||||||
|
private AtomicInteger counter = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private FrameDecoderExecutor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Inner {
|
||||||
|
static final FrameDecoderExecutor sInstance = new FrameDecoderExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPoolSize(int size) {
|
||||||
|
sPoolNumber = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrameDecoderExecutor getInstance() {
|
||||||
|
return Inner.sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Looper getLooper(int taskId) {
|
||||||
|
int idx = taskId % sPoolNumber;
|
||||||
|
if (idx >= mHandlerThreadGroup.size()) {
|
||||||
|
HandlerThread handlerThread = new HandlerThread("FrameDecoderExecutor-" + idx);
|
||||||
|
handlerThread.start();
|
||||||
|
|
||||||
|
mHandlerThreadGroup.add(handlerThread);
|
||||||
|
Looper looper = handlerThread.getLooper();
|
||||||
|
if (looper != null) {
|
||||||
|
return looper;
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mHandlerThreadGroup.get(idx) != null) {
|
||||||
|
Looper looper = mHandlerThreadGroup.get(idx).getLooper();
|
||||||
|
if (looper != null) {
|
||||||
|
return looper;
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Looper.getMainLooper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int generateTaskId() {
|
||||||
|
return counter.getAndIncrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-14
|
||||||
|
*/
|
||||||
|
public class ByteBufferReader implements Reader {
|
||||||
|
|
||||||
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public ByteBufferReader(ByteBuffer byteBuffer) {
|
||||||
|
this.byteBuffer = byteBuffer;
|
||||||
|
byteBuffer.position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long total) throws IOException {
|
||||||
|
byteBuffer.position((int) (byteBuffer.position() + total));
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
return byteBuffer.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
byteBuffer.position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int start, int byteCount) throws IOException {
|
||||||
|
byteBuffer.get(buffer, start, byteCount);
|
||||||
|
return byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return byteBuffer.limit() - byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
return new ByteArrayInputStream(byteBuffer.array());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: ByteBufferWriter
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-12
|
||||||
|
*/
|
||||||
|
public class ByteBufferWriter implements Writer {
|
||||||
|
|
||||||
|
protected ByteBuffer byteBuffer;
|
||||||
|
|
||||||
|
public ByteBufferWriter() {
|
||||||
|
reset(10 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putByte(byte b) {
|
||||||
|
byteBuffer.put(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putBytes(byte[] b) {
|
||||||
|
byteBuffer.put(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return byteBuffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skip(int length) {
|
||||||
|
byteBuffer.position(length + position());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] toByteArray() {
|
||||||
|
return byteBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(int size) {
|
||||||
|
if (byteBuffer == null || size > byteBuffer.capacity()) {
|
||||||
|
byteBuffer = ByteBuffer.allocate(size);
|
||||||
|
this.byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
byteBuffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: FileReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-23
|
||||||
|
*/
|
||||||
|
public class FileReader extends FilterReader {
|
||||||
|
private final File mFile;
|
||||||
|
|
||||||
|
public FileReader(File file) throws IOException {
|
||||||
|
super(new StreamReader(new FileInputStream(file)));
|
||||||
|
mFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
reader.close();
|
||||||
|
reader = new StreamReader(new FileInputStream(mFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: FilterReader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-23
|
||||||
|
*/
|
||||||
|
public class FilterReader implements Reader {
|
||||||
|
protected Reader reader;
|
||||||
|
|
||||||
|
public FilterReader(Reader in) {
|
||||||
|
this.reader = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long total) throws IOException {
|
||||||
|
return reader.skip(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
return reader.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() throws IOException {
|
||||||
|
reader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return reader.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int start, int byteCount) throws IOException {
|
||||||
|
return reader.read(buffer, start, byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return reader.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
reset();
|
||||||
|
return reader.toInputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link {https://developers.google.com/speed/webp/docs/riff_container#terminology_basics}
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-11
|
||||||
|
*/
|
||||||
|
public interface Reader {
|
||||||
|
long skip(long total) throws IOException;
|
||||||
|
|
||||||
|
byte peek() throws IOException;
|
||||||
|
|
||||||
|
void reset() throws IOException;
|
||||||
|
|
||||||
|
int position();
|
||||||
|
|
||||||
|
int read(byte[] buffer, int start, int byteCount) throws IOException;
|
||||||
|
|
||||||
|
int available() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close io
|
||||||
|
*/
|
||||||
|
void close() throws IOException;
|
||||||
|
|
||||||
|
InputStream toInputStream() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-11
|
||||||
|
*/
|
||||||
|
public class StreamReader extends FilterInputStream implements Reader {
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public StreamReader(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
try {
|
||||||
|
in.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte peek() throws IOException {
|
||||||
|
byte ret = (byte) read();
|
||||||
|
position++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
int ret = super.read(b, off, len);
|
||||||
|
position += Math.max(0, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
super.reset();
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
long ret = super.skip(n);
|
||||||
|
position += ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int position() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream toInputStream() throws IOException {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: APNG4Android
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-12
|
||||||
|
*/
|
||||||
|
public interface Writer {
|
||||||
|
void reset(int size);
|
||||||
|
|
||||||
|
void putByte(byte b);
|
||||||
|
|
||||||
|
void putBytes(byte[] b);
|
||||||
|
|
||||||
|
int position();
|
||||||
|
|
||||||
|
void skip(int length);
|
||||||
|
|
||||||
|
byte[] toByteArray();
|
||||||
|
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从Asset中读取流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class AssetStreamLoader extends StreamLoader {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final String mAssetName;
|
||||||
|
|
||||||
|
public AssetStreamLoader(Context context, String assetName) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mAssetName = assetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getInputStream() throws IOException {
|
||||||
|
return mContext.getAssets().open(mAssetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.ByteBufferReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: ByteBufferLoader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-15
|
||||||
|
*/
|
||||||
|
public abstract class ByteBufferLoader implements Loader {
|
||||||
|
public abstract ByteBuffer getByteBuffer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader obtain() throws IOException {
|
||||||
|
return new ByteBufferReader(getByteBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.FileReader;
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从文件加载流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class FileLoader implements Loader {
|
||||||
|
|
||||||
|
private final File mFile;
|
||||||
|
private Reader mReader;
|
||||||
|
|
||||||
|
public FileLoader(String path) {
|
||||||
|
mFile = new File(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Reader obtain() throws IOException {
|
||||||
|
return new FileReader(mFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: Loader
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019-05-14
|
||||||
|
*/
|
||||||
|
public interface Loader {
|
||||||
|
Reader obtain() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 从资源加载流
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public class ResourceStreamLoader extends StreamLoader {
|
||||||
|
private final Context mContext;
|
||||||
|
private final int mResId;
|
||||||
|
|
||||||
|
|
||||||
|
public ResourceStreamLoader(Context context, int resId) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mResId = resId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getInputStream() throws IOException {
|
||||||
|
return mContext.getResources().openRawResource(mResId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Zhou Pengfei
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.signal.glide.common.loader;
|
||||||
|
|
||||||
|
import org.signal.glide.common.io.Reader;
|
||||||
|
import org.signal.glide.common.io.StreamReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: pengfei.zhou
|
||||||
|
* @CreateDate: 2019/3/28
|
||||||
|
*/
|
||||||
|
public abstract class StreamLoader implements Loader {
|
||||||
|
protected abstract InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
public final synchronized Reader obtain() throws IOException {
|
||||||
|
return new StreamReader(getInputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||||
|
|
||||||
public final class AppCapabilities {
|
public final class AppCapabilities {
|
||||||
|
|
||||||
private AppCapabilities() {
|
private AppCapabilities() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final boolean UUID_CAPABLE = false;
|
private static final boolean UUID_CAPABLE = false;
|
||||||
|
private static final boolean GV2_CAPABLE = true;
|
||||||
|
private static final boolean GV1_MIGRATION = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
||||||
* asking if the user has set a Signal PIN or not.
|
* asking if the user has set a Signal PIN or not.
|
||||||
*/
|
*/
|
||||||
public static SignalServiceProfile.Capabilities getCapabilities(boolean storageCapable) {
|
public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
|
||||||
return new SignalServiceProfile.Capabilities(UUID_CAPABLE, FeatureFlags.groupsV2(), storageCapable);
|
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import android.content.Context;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
import org.thoughtcrime.securesms.insights.InsightsOptOut;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@@ -34,10 +34,16 @@ public final class AppInitialization {
|
|||||||
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||||
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||||
|
TextSecurePreferences.setPasswordDisabled(context, true);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
TextSecurePreferences.setReadReceiptsEnabled(context, true);
|
||||||
|
TextSecurePreferences.setTypingIndicatorsEnabled(context, true);
|
||||||
|
TextSecurePreferences.setHasSeenWelcomeScreen(context, false);
|
||||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
SignalStore.onFirstEverAppLaunch();
|
SignalStore.onFirstEverAppLaunch();
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
}
|
}
|
||||||
@@ -47,8 +53,33 @@ public final class AppInitialization {
|
|||||||
|
|
||||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
SignalStore.onFirstEverAppLaunch();
|
SignalStore.onFirstEverAppLaunch();
|
||||||
|
SignalStore.onboarding().clearAll();
|
||||||
|
TextSecurePreferences.onPostBackupRestore(context);
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary migration method that does the safest bits of {@link #onFirstEverAppLaunch(Context)}
|
||||||
|
*/
|
||||||
|
public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
|
||||||
|
Log.w(TAG, "onRepairFirstEverAppLaunch()");
|
||||||
|
|
||||||
|
InsightsOptOut.userRequestedOptOut(context);
|
||||||
|
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||||
|
TextSecurePreferences.setPasswordDisabled(context, true);
|
||||||
|
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||||
|
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||||
|
SignalStore.onFirstEverAppLaunch();
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||||
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.DAY_BY_DAY.getPackId(), BlessedPacks.DAY_BY_DAY.getPackKey(), false));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,68 +16,72 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
||||||
import androidx.multidex.MultiDexApplication;
|
import androidx.multidex.MultiDexApplication;
|
||||||
|
|
||||||
import com.google.android.gms.security.ProviderInstaller;
|
import com.google.android.gms.security.ProviderInstaller;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.core.util.logging.PersistentLogger;
|
||||||
|
import org.signal.core.util.tracing.Tracer;
|
||||||
|
import org.signal.glide.SignalGlideCodecs;
|
||||||
import org.signal.ringrtc.CallManager;
|
import org.signal.ringrtc.CallManager;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||||
|
import org.thoughtcrime.securesms.emoji.EmojiSource;
|
||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||||
import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
|
|
||||||
import org.thoughtcrime.securesms.messages.InitialMessageRetriever;
|
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
|
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
|
||||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||||
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
|
|
||||||
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
|
||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||||
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
|
||||||
|
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,18 +92,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
|
public class ApplicationContext extends MultiDexApplication implements AppForegroundObserver.Listener {
|
||||||
|
|
||||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
private static final String TAG = Log.tag(ApplicationContext.class);
|
||||||
|
|
||||||
private ExpiringMessageManager expiringMessageManager;
|
private PersistentLogger persistentLogger;
|
||||||
private ViewOnceMessageManager viewOnceMessageManager;
|
|
||||||
private TypingStatusRepository typingStatusRepository;
|
|
||||||
private TypingStatusSender typingStatusSender;
|
|
||||||
private IncomingMessageObserver incomingMessageObserver;
|
|
||||||
private PersistentLogger persistentLogger;
|
|
||||||
|
|
||||||
private volatile boolean isAppVisible;
|
|
||||||
|
|
||||||
public static ApplicationContext getInstance(Context context) {
|
public static ApplicationContext getInstance(Context context) {
|
||||||
return (ApplicationContext)context.getApplicationContext();
|
return (ApplicationContext)context.getApplicationContext();
|
||||||
@@ -107,88 +104,110 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
Tracer.getInstance().start("Application#onCreate()");
|
||||||
Log.i(TAG, "onCreate()");
|
AppStartup.getInstance().onApplicationCreate();
|
||||||
initializeSecurityProvider();
|
|
||||||
initializeLogging();
|
|
||||||
initializeCrashHandling();
|
|
||||||
initializeAppDependencies();
|
|
||||||
initializeFirstEverAppLaunch();
|
|
||||||
initializeApplicationMigrations();
|
|
||||||
initializeMessageRetrieval();
|
|
||||||
initializeExpiringMessageManager();
|
|
||||||
initializeRevealableMessageManager();
|
|
||||||
initializeTypingStatusRepository();
|
|
||||||
initializeTypingStatusSender();
|
|
||||||
initializeGcmCheck();
|
|
||||||
initializeSignedPreKeyCheck();
|
|
||||||
initializePeriodicTasks();
|
|
||||||
initializeCircumvention();
|
|
||||||
initializeRingRtc();
|
|
||||||
initializePendingMessages();
|
|
||||||
initializeBlobProvider();
|
|
||||||
initializeCleanup();
|
|
||||||
|
|
||||||
FeatureFlags.init();
|
long startTime = System.currentTimeMillis();
|
||||||
NotificationChannels.create(this);
|
|
||||||
RefreshPreKeysJob.scheduleIfNecessary();
|
|
||||||
StorageSyncHelper.scheduleRoutineSync();
|
|
||||||
RegistrationUtil.markRegistrationPossiblyComplete();
|
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
if (FeatureFlags.internalUser()) {
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
Tracer.getInstance().setMaxBufferSize(35_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().beginJobLoop();
|
super.onCreate();
|
||||||
|
|
||||||
|
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
||||||
|
.addBlocking("logging", () -> {
|
||||||
|
initializeLogging();
|
||||||
|
Log.i(TAG, "onCreate()");
|
||||||
|
})
|
||||||
|
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||||
|
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
||||||
|
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||||
|
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
|
||||||
|
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||||
|
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||||
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||||
|
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
||||||
|
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
|
||||||
|
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
||||||
|
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
||||||
|
.addBlocking("vector-compat", () -> {
|
||||||
|
if (Build.VERSION.SDK_INT < 21) {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addBlocking("proxy-init", () -> {
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addBlocking("blob-provider", this::initializeBlobProvider)
|
||||||
|
.addBlocking("feature-flags", FeatureFlags::init)
|
||||||
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
|
.addNonBlocking(this::initializeGcmCheck)
|
||||||
|
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||||
|
.addNonBlocking(this::initializePeriodicTasks)
|
||||||
|
.addNonBlocking(this::initializeCircumvention)
|
||||||
|
.addNonBlocking(this::initializePendingMessages)
|
||||||
|
.addNonBlocking(this::initializeCleanup)
|
||||||
|
.addNonBlocking(this::initializeGlideCodecs)
|
||||||
|
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
||||||
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||||
|
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||||
|
.addNonBlocking(EmojiSource::refresh)
|
||||||
|
.addNonBlocking(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||||
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
||||||
|
.addPostRender(this::initializeExpiringMessageManager)
|
||||||
|
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
Tracer.getInstance().end("Application#onCreate()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart(@NonNull LifecycleOwner owner) {
|
public void onForeground() {
|
||||||
isAppVisible = true;
|
long startTime = System.currentTimeMillis();
|
||||||
Log.i(TAG, "App is now visible.");
|
Log.i(TAG, "App is now visible.");
|
||||||
FeatureFlags.refreshIfNecessary();
|
|
||||||
ApplicationDependencies.getRecipientCache().warmUp();
|
|
||||||
executePendingContactSync();
|
|
||||||
KeyCachingService.onAppForegrounded(this);
|
|
||||||
ApplicationDependencies.getFrameRateTracker().begin();
|
ApplicationDependencies.getFrameRateTracker().begin();
|
||||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||||
catchUpOnMessages();
|
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
FeatureFlags.refreshIfNecessary();
|
||||||
|
ApplicationDependencies.getRecipientCache().warmUp();
|
||||||
|
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
||||||
|
GroupV1MigrationJob.enqueueRoutineMigrationsIfNecessary(this);
|
||||||
|
executePendingContactSync();
|
||||||
|
KeyCachingService.onAppForegrounded(this);
|
||||||
|
ApplicationDependencies.getShakeToReport().enable();
|
||||||
|
checkBuildExpiration();
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop(@NonNull LifecycleOwner owner) {
|
public void onBackground() {
|
||||||
isAppVisible = false;
|
|
||||||
Log.i(TAG, "App is no longer visible.");
|
Log.i(TAG, "App is no longer visible.");
|
||||||
KeyCachingService.onAppBackgrounded(this);
|
KeyCachingService.onAppBackgrounded(this);
|
||||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||||
ApplicationDependencies.getFrameRateTracker().end();
|
ApplicationDependencies.getFrameRateTracker().end();
|
||||||
}
|
ApplicationDependencies.getShakeToReport().disable();
|
||||||
|
|
||||||
public ExpiringMessageManager getExpiringMessageManager() {
|
|
||||||
return expiringMessageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewOnceMessageManager getViewOnceMessageManager() {
|
|
||||||
return viewOnceMessageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypingStatusRepository getTypingStatusRepository() {
|
|
||||||
return typingStatusRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypingStatusSender getTypingStatusSender() {
|
|
||||||
return typingStatusSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAppVisible() {
|
|
||||||
return isAppVisible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersistentLogger getPersistentLogger() {
|
public PersistentLogger getPersistentLogger() {
|
||||||
return persistentLogger;
|
return persistentLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkBuildExpiration() {
|
||||||
|
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
||||||
|
Log.w(TAG, "Build expired!");
|
||||||
|
SignalStore.misc().markClientDeprecated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeSecurityProvider() {
|
private void initializeSecurityProvider() {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
||||||
@@ -214,8 +233,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeLogging() {
|
private void initializeLogging() {
|
||||||
persistentLogger = new PersistentLogger(this);
|
persistentLogger = new PersistentLogger(this, LogSecretProvider.getOrCreateAttachmentSecret(this), BuildConfig.VERSION_NAME);
|
||||||
org.thoughtcrime.securesms.logging.Log.initialize(new AndroidLogger(), persistentLogger);
|
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
|
||||||
|
|
||||||
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
||||||
}
|
}
|
||||||
@@ -230,22 +249,28 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initializeMessageRetrieval() {
|
public void initializeMessageRetrieval() {
|
||||||
this.incomingMessageObserver = new IncomingMessageObserver(this);
|
ApplicationDependencies.getIncomingMessageObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAppDependencies() {
|
private void initializeAppDependencies() {
|
||||||
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this, new SignalServiceNetworkAccess(this)));
|
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeFirstEverAppLaunch() {
|
private void initializeFirstEverAppLaunch() {
|
||||||
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
||||||
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
|
if (!SQLCipherOpenHelper.databaseFileExists(this) || VersionTracker.getDaysSinceFirstInstalled(this) < 365) {
|
||||||
Log.i(TAG, "First ever app launch!");
|
Log.i(TAG, "First ever app launch!");
|
||||||
AppInitialization.onFirstEverAppLaunch(this);
|
AppInitialization.onFirstEverAppLaunch(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
||||||
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
||||||
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
||||||
|
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
||||||
|
AppInitialization.onRepairFirstEverAppLaunch(this);
|
||||||
|
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 912) {
|
||||||
|
Log.i(TAG, "Detected a not-recent install that doesn't have passphrases disabled -- disabling now.");
|
||||||
|
TextSecurePreferences.setPasswordDisabled(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,19 +291,11 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeExpiringMessageManager() {
|
private void initializeExpiringMessageManager() {
|
||||||
this.expiringMessageManager = new ExpiringMessageManager(this);
|
ApplicationDependencies.getExpiringMessageManager().checkSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeRevealableMessageManager() {
|
private void initializeRevealableMessageManager() {
|
||||||
this.viewOnceMessageManager = new ViewOnceMessageManager(this);
|
ApplicationDependencies.getViewOnceMessageManager().scheduleIfNecessary();
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeTypingStatusRepository() {
|
|
||||||
this.typingStatusRepository = new TypingStatusRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeTypingStatusSender() {
|
|
||||||
this.typingStatusSender = new TypingStatusSender(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializePeriodicTasks() {
|
private void initializePeriodicTasks() {
|
||||||
@@ -286,6 +303,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
DirectoryRefreshListener.schedule(this);
|
DirectoryRefreshListener.schedule(this);
|
||||||
LocalBackupListener.schedule(this);
|
LocalBackupListener.schedule(this);
|
||||||
RotateSenderCertificateListener.schedule(this);
|
RotateSenderCertificateListener.schedule(this);
|
||||||
|
MessageProcessReceiver.startOrUpdateAlarm(this);
|
||||||
|
|
||||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||||
UpdateApkRefreshListener.schedule(this);
|
UpdateApkRefreshListener.schedule(this);
|
||||||
@@ -294,31 +312,11 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
|
|
||||||
private void initializeRingRtc() {
|
private void initializeRingRtc() {
|
||||||
try {
|
try {
|
||||||
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
|
if (RtcDeviceLists.hardwareAECBlocked()) {
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
add("Moto G5");
|
|
||||||
add("Moto G (5S) Plus");
|
|
||||||
add("Moto G4");
|
|
||||||
add("TA-1053");
|
|
||||||
add("Mi A1");
|
|
||||||
add("Mi A2");
|
|
||||||
add("E5823"); // Sony z5 compact
|
|
||||||
add("Redmi Note 5");
|
|
||||||
add("FP2"); // Fairphone FP2
|
|
||||||
add("MI 5");
|
|
||||||
}};
|
|
||||||
|
|
||||||
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
|
|
||||||
add("Pixel");
|
|
||||||
add("Pixel XL");
|
|
||||||
}};
|
|
||||||
|
|
||||||
if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
|
|
||||||
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
|
if (!RtcDeviceLists.openSLESAllowed()) {
|
||||||
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,23 +326,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@WorkerThread
|
||||||
private void initializeCircumvention() {
|
private void initializeCircumvention() {
|
||||||
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
|
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
||||||
@Override
|
try {
|
||||||
protected Void doInBackground(Void... params) {
|
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
||||||
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
} catch (Throwable t) {
|
||||||
try {
|
Log.w(TAG, t);
|
||||||
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.w(TAG, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executePendingContactSync() {
|
private void executePendingContactSync() {
|
||||||
@@ -359,58 +349,56 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
if (Build.VERSION.SDK_INT >= 26) {
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
FcmJobService.schedule(this);
|
FcmJobService.schedule(this);
|
||||||
} else {
|
} else {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setNeedsMessagePull(this, false);
|
TextSecurePreferences.setNeedsMessagePull(this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void initializeBlobProvider() {
|
private void initializeBlobProvider() {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
BlobProvider.getInstance().initialize(this);
|
||||||
BlobProvider.getInstance().onSessionStart(this);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void initializeCleanup() {
|
private void initializeCleanup() {
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||||
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void catchUpOnMessages() {
|
private void initializeGlideCodecs() {
|
||||||
InitialMessageRetriever retriever = ApplicationDependencies.getInitialMessageRetriever();
|
SignalGlideCodecs.setLogProvider(new org.signal.glide.Log.Provider() {
|
||||||
|
@Override
|
||||||
|
public void v(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.v(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
if (retriever.isCaughtUp()) {
|
@Override
|
||||||
return;
|
public void d(@NonNull String tag, @NonNull String message) {
|
||||||
}
|
Log.d(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
@Override
|
||||||
long startTime = System.currentTimeMillis();
|
public void i(@NonNull String tag, @NonNull String message) {
|
||||||
|
Log.i(tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
switch (retriever.begin(TimeUnit.SECONDS.toMillis(60))) {
|
@Override
|
||||||
case SUCCESS:
|
public void w(@NonNull String tag, @NonNull String message) {
|
||||||
Log.i(TAG, "Successfully caught up on messages. " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.w(tag, message);
|
||||||
break;
|
}
|
||||||
case FAILURE_TIMEOUT:
|
|
||||||
Log.w(TAG, "Did not finish catching up due to a timeout. " + (System.currentTimeMillis() - startTime) + " ms");
|
@Override
|
||||||
break;
|
public void e(@NonNull String tag, @NonNull String message, @Nullable Throwable throwable) {
|
||||||
case FAILURE_ERROR:
|
Log.e(tag, message, throwable);
|
||||||
Log.w(TAG, "Did not finish catching up due to an error. " + (System.currentTimeMillis() - startTime) + " ms");
|
|
||||||
break;
|
|
||||||
case SKIPPED_ALREADY_CAUGHT_UP:
|
|
||||||
Log.i(TAG, "Already caught up. " + (System.currentTimeMillis() - startTime) + " ms");
|
|
||||||
break;
|
|
||||||
case SKIPPED_ALREADY_RUNNING:
|
|
||||||
Log.i(TAG, "Already in the process of catching up. " + (System.currentTimeMillis() - startTime) + " ms");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context base) {
|
protected void attachBaseContext(Context base) {
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
|
DynamicLanguageContextWrapper.updateContext(base);
|
||||||
|
super.attachBaseContext(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ProviderInitializationException extends RuntimeException {
|
private static class ProviderInitializationException extends RuntimeException {
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
* Copyright (C) 2013-2017 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.help.HelpFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
|
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
|
||||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Activity for application preference display and management.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarActivity
|
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
|
||||||
{
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final String PREFERENCE_CATEGORY_PROFILE = "preference_category_profile";
|
|
||||||
private static final String PREFERENCE_CATEGORY_SMS_MMS = "preference_category_sms_mms";
|
|
||||||
private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "preference_category_notifications";
|
|
||||||
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
|
|
||||||
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
|
|
||||||
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
|
|
||||||
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
|
|
||||||
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
|
|
||||||
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
|
|
||||||
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
this.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
if (getIntent() != null && getIntent().getCategories() != null && getIntent().getCategories().contains("android.intent.category.NOTIFICATION_PREFERENCES")) {
|
|
||||||
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
|
||||||
} else if (icicle == null) {
|
|
||||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
|
||||||
{
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content);
|
|
||||||
fragment.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
if (fragmentManager.getBackStackEntryCount() > 0) {
|
|
||||||
fragmentManager.popBackStack();
|
|
||||||
} else {
|
|
||||||
// TODO [greyson] Navigation
|
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
||||||
if (key.equals(TextSecurePreferences.THEME_PREF)) {
|
|
||||||
recreate();
|
|
||||||
} else if (key.equals(TextSecurePreferences.LANGUAGE_PREF)) {
|
|
||||||
recreate();
|
|
||||||
|
|
||||||
Intent intent = new Intent(this, KeyCachingService.class);
|
|
||||||
intent.setAction(KeyCachingService.LOCALE_CHANGE_EVENT);
|
|
||||||
startService(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle icicle) {
|
|
||||||
super.onCreate(icicle);
|
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_PROFILE)
|
|
||||||
.setOnPreferenceClickListener(new ProfileClickListener());
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_HELP)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
|
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
|
||||||
|
|
||||||
tintIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tintIcons() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) return;
|
|
||||||
|
|
||||||
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
|
|
||||||
preference.getIcon().setColorFilter(ThemeUtil.getThemedColor(requireContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_IN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.text_secure_normal__menu_settings);
|
|
||||||
setCategorySummaries();
|
|
||||||
setCategoryVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCategorySummaries() {
|
|
||||||
((ProfilePreference)this.findPreference(PREFERENCE_CATEGORY_PROFILE)).refresh();
|
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS)
|
|
||||||
.setSummary(SmsMmsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
|
||||||
.setSummary(NotificationsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
|
||||||
.setSummary(AppProtectionPreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
|
||||||
.setSummary(AppearancePreferenceFragment.getSummary(getActivity()));
|
|
||||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
|
||||||
.setSummary(ChatsPreferenceFragment.getSummary(getActivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCategoryVisibility() {
|
|
||||||
Preference devicePreference = this.findPreference(PREFERENCE_CATEGORY_DEVICES);
|
|
||||||
if (devicePreference != null && !TextSecurePreferences.isPushRegistered(getActivity())) {
|
|
||||||
getPreferenceScreen().removePreference(devicePreference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
private String category;
|
|
||||||
|
|
||||||
CategoryClickListener(String category) {
|
|
||||||
this.category = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
Fragment fragment = null;
|
|
||||||
|
|
||||||
switch (category) {
|
|
||||||
case PREFERENCE_CATEGORY_SMS_MMS:
|
|
||||||
fragment = new SmsMmsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_NOTIFICATIONS:
|
|
||||||
fragment = new NotificationsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_APP_PROTECTION:
|
|
||||||
fragment = new AppProtectionPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_APPEARANCE:
|
|
||||||
fragment = new AppearancePreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_CHATS:
|
|
||||||
fragment = new ChatsPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_STORAGE:
|
|
||||||
fragment = new StoragePreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_DEVICES:
|
|
||||||
Intent intent = new Intent(getActivity(), DeviceActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_ADVANCED:
|
|
||||||
fragment = new AdvancedPreferenceFragment();
|
|
||||||
break;
|
|
||||||
case PREFERENCE_CATEGORY_HELP:
|
|
||||||
fragment = new HelpFragment();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragment != null) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
|
|
||||||
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
|
|
||||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
||||||
|
|
||||||
fragmentTransaction.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end);
|
|
||||||
|
|
||||||
fragmentTransaction.replace(android.R.id.content, fragment);
|
|
||||||
fragmentTransaction.addToBackStack(null);
|
|
||||||
fragmentTransaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
Intent intent = new Intent(preference.getContext(), EditProfileActivity.class);
|
|
||||||
intent.putExtra(EditProfileActivity.EXCLUDE_SYSTEM, true);
|
|
||||||
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
|
|
||||||
intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
|
|
||||||
|
|
||||||
requireActivity().startActivity(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,37 +3,43 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.transition.TransitionInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
import com.bumptech.glide.load.DataSource;
|
import com.bumptech.glide.load.DataSource;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.engine.GlideException;
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
import com.bumptech.glide.request.RequestListener;
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for displaying avatars full screen.
|
* Activity for displaying avatars full screen.
|
||||||
*/
|
*/
|
||||||
public final class AvatarPreviewActivity extends PassphraseRequiredActionBarActivity {
|
public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private static final String TAG = Log.tag(AvatarPreviewActivity.class);
|
private static final String TAG = Log.tag(AvatarPreviewActivity.class);
|
||||||
|
|
||||||
@@ -58,90 +64,74 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActionBarActi
|
|||||||
setTheme(R.style.TextSecure_MediaPreview);
|
setTheme(R.style.TextSecure_MediaPreview);
|
||||||
setContentView(R.layout.contact_photo_preview_activity);
|
setContentView(R.layout.contact_photo_preview_activity);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
ImageView avatar = findViewById(R.id.avatar);
|
postponeEnterTransition();
|
||||||
|
TransitionInflater inflater = TransitionInflater.from(this);
|
||||||
|
getWindow().setSharedElementEnterTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_enter_transition_set));
|
||||||
|
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
|
||||||
|
}
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
ImageView avatar = findViewById(R.id.avatar);
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
requireSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
|
|
||||||
showSystemUI();
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
Context context = getApplicationContext();
|
Context context = getApplicationContext();
|
||||||
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
|
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
|
||||||
|
|
||||||
Recipient.live(recipientId).observe(this, recipient -> {
|
Recipient.live(recipientId).observe(this, recipient -> {
|
||||||
ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
|
ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
|
||||||
: recipient.getContactPhoto();
|
: recipient.getContactPhoto();
|
||||||
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
|
FallbackContactPhoto fallbackPhoto = recipient.isSelf() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
|
||||||
: recipient.getFallbackContactPhoto();
|
: recipient.getFallbackContactPhoto();
|
||||||
|
|
||||||
GlideApp.with(this).load(contactPhoto)
|
Resources resources = this.getResources();
|
||||||
.fallback(fallbackPhoto.asCallCard(this))
|
|
||||||
.error(fallbackPhoto.asCallCard(this))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
||||||
.addListener(new RequestListener<Drawable>() {
|
|
||||||
@Override
|
|
||||||
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
|
||||||
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
|
|
||||||
finish();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
GlideApp.with(this)
|
||||||
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
.asBitmap()
|
||||||
return false;
|
.load(contactPhoto)
|
||||||
}
|
.fallback(fallbackPhoto.asCallCard(this))
|
||||||
})
|
.error(fallbackPhoto.asCallCard(this))
|
||||||
.into(avatar);
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.addListener(new RequestListener<Bitmap>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
|
||||||
|
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
|
||||||
|
finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
toolbar.setTitle(recipient.toShortString(context));
|
@Override
|
||||||
|
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(new CustomTarget<Bitmap>() {
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
|
||||||
|
avatar.setImageDrawable(RoundedBitmapDrawableFactory.create(resources, resource));
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
startPostponedEnterTransition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbar.setTitle(recipient.getDisplayName(context));
|
||||||
});
|
});
|
||||||
|
|
||||||
avatar.setOnClickListener(v -> toggleUiVisibility());
|
FullscreenHelper fullscreenHelper = new FullscreenHelper(this);
|
||||||
|
|
||||||
showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
|
||||||
}
|
|
||||||
|
|
||||||
private static void showAndHideWithSystemUI(@NonNull Window window, @NonNull View... views) {
|
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer));
|
||||||
window.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
|
|
||||||
boolean hide = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
|
|
||||||
|
|
||||||
for (View view : views) {
|
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
|
||||||
view.animate()
|
|
||||||
.alpha(hide ? 0 : 1)
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toggleUiVisibility() {
|
|
||||||
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
|
|
||||||
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
|
|
||||||
showSystemUI();
|
|
||||||
} else {
|
|
||||||
hideSystemUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideSystemUI() {
|
|
||||||
getWindow().getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
|
||||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_FULLSCREEN );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSystemUI() {
|
|
||||||
getWindow().getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Build.VERSION_CODES;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
|
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
|
||||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
if (BaseActivity.isMenuWorkaroundRequired()) {
|
|
||||||
forceOverflowMenu();
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
initializeScreenshotSecurity();
|
|
||||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
return (keyCode == KeyEvent.KEYCODE_MENU && BaseActivity.isMenuWorkaroundRequired()) || super.onKeyDown(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_MENU && BaseActivity.isMenuWorkaroundRequired()) {
|
|
||||||
openOptionsMenu();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeScreenshotSecurity() {
|
|
||||||
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
|
||||||
} else {
|
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified from: http://stackoverflow.com/a/13098824
|
|
||||||
*/
|
|
||||||
private void forceOverflowMenu() {
|
|
||||||
try {
|
|
||||||
ViewConfiguration config = ViewConfiguration.get(this);
|
|
||||||
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
|
|
||||||
if(menuKeyField != null) {
|
|
||||||
menuKeyField.setAccessible(true);
|
|
||||||
menuKeyField.setBoolean(config, false);
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.w(TAG, "Failed to force overflow menu.");
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
Log.w(TAG, "Failed to force overflow menu.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) {
|
|
||||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName)
|
|
||||||
.toBundle();
|
|
||||||
ActivityCompat.startActivity(this, intent, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
|
||||||
protected void setStatusBarColor(int color) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
getWindow().setStatusBarColor(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(Context newBase) {
|
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,116 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.content.Intent;
|
||||||
import android.os.Build.VERSION;
|
import android.content.res.Configuration;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
import android.view.View;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import android.view.WindowManager;
|
||||||
import android.view.KeyEvent;
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.ConfigurationUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
|
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
|
|
||||||
public abstract class BaseActivity extends FragmentActivity {
|
import java.util.Objects;
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
/**
|
||||||
return (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) || super.onKeyDown(keyCode, event);
|
* Base class for all activities. The vast majority of activities shouldn't extend this directly.
|
||||||
}
|
* Instead, they should extend {@link PassphraseRequiredActivity} so they're protected by
|
||||||
|
* screen lock.
|
||||||
|
*/
|
||||||
|
public abstract class BaseActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = Log.tag(BaseActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
if (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) {
|
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||||
openOptionsMenu();
|
logEvent("onCreate()");
|
||||||
return true;
|
super.onCreate(savedInstanceState);
|
||||||
}
|
AppStartup.getInstance().onCriticalRenderEventEnd();
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isMenuWorkaroundRequired() {
|
|
||||||
return VERSION.SDK_INT < VERSION_CODES.KITKAT &&
|
|
||||||
VERSION.SDK_INT > VERSION_CODES.GINGERBREAD_MR1 &&
|
|
||||||
("LGE".equalsIgnoreCase(Build.MANUFACTURER) || "E6710".equalsIgnoreCase(Build.DEVICE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
|
initializeScreenshotSecurity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void attachBaseContext(Context newBase) {
|
protected void onStart() {
|
||||||
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
|
logEvent("onStart()");
|
||||||
|
ApplicationDependencies.getShakeToReport().registerActivity(this);
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
logEvent("onStop()");
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
logEvent("onDestroy()");
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeScreenshotSecurity() {
|
||||||
|
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
} else {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) {
|
||||||
|
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName)
|
||||||
|
.toBundle();
|
||||||
|
ActivityCompat.startActivity(this, intent, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(@NonNull Context newBase) {
|
||||||
|
super.attachBaseContext(newBase);
|
||||||
|
|
||||||
|
Configuration configuration = new Configuration(newBase.getResources().getConfiguration());
|
||||||
|
int appCompatNightMode = getDelegate().getLocalNightMode() != AppCompatDelegate.MODE_NIGHT_UNSPECIFIED ? getDelegate().getLocalNightMode()
|
||||||
|
: AppCompatDelegate.getDefaultNightMode();
|
||||||
|
|
||||||
|
configuration.uiMode = (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | mapNightModeToConfigurationUiMode(newBase, appCompatNightMode);
|
||||||
|
|
||||||
|
applyOverrideConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyOverrideConfiguration(@NonNull Configuration overrideConfiguration) {
|
||||||
|
DynamicLanguageContextWrapper.prepareOverrideConfiguration(this, overrideConfiguration);
|
||||||
|
super.applyOverrideConfiguration(overrideConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logEvent(@NonNull String event) {
|
||||||
|
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final @NonNull ActionBar requireSupportActionBar() {
|
||||||
|
return Objects.requireNonNull(getSupportActionBar());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int mapNightModeToConfigurationUiMode(@NonNull Context context, @AppCompatDelegate.NightMode int appCompatNightMode) {
|
||||||
|
if (appCompatNightMode == AppCompatDelegate.MODE_NIGHT_YES) {
|
||||||
|
return Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
} else if (appCompatNightMode == AppCompatDelegate.MODE_NIGHT_NO) {
|
||||||
|
return Configuration.UI_MODE_NIGHT_NO;
|
||||||
|
}
|
||||||
|
return ConfigurationUtil.getNightModeConfiguration(context.getApplicationContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,51 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.view.View;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
||||||
|
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
|
import org.thoughtcrime.securesms.video.exo.AttachmentMediaSourceFactory;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface BindableConversationItem extends Unbindable {
|
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable {
|
||||||
void bind(@NonNull MessageRecord messageRecord,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull GlideRequests glideRequests,
|
||||||
@NonNull Locale locale,
|
@NonNull Locale locale,
|
||||||
@NonNull Set<MessageRecord> batchSelected,
|
@NonNull Set<ConversationMessage> batchSelected,
|
||||||
@NonNull Recipient recipients,
|
@NonNull Recipient recipients,
|
||||||
@Nullable String searchQuery,
|
@Nullable String searchQuery,
|
||||||
boolean pulseHighlight);
|
boolean pulseMention,
|
||||||
|
boolean hasWallpaper,
|
||||||
|
boolean isMessageRequestAccepted,
|
||||||
|
@NonNull AttachmentMediaSourceFactory attachmentMediaSourceFactory,
|
||||||
|
boolean canPlayInline);
|
||||||
|
|
||||||
MessageRecord getMessageRecord();
|
ConversationMessage getConversationMessage();
|
||||||
|
|
||||||
void setEventListener(@Nullable EventListener listener);
|
void setEventListener(@Nullable EventListener listener);
|
||||||
|
|
||||||
@@ -45,7 +59,26 @@ public interface BindableConversationItem extends Unbindable {
|
|||||||
void onAddToContactsClicked(@NonNull Contact contact);
|
void onAddToContactsClicked(@NonNull Contact contact);
|
||||||
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
|
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
|
||||||
void onReactionClicked(long messageId, boolean isMms);
|
void onReactionClicked(@NonNull View reactionTarget, long messageId, boolean isMms);
|
||||||
void onGroupMemberAvatarClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
|
void onGroupMemberClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
|
||||||
|
void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord);
|
||||||
|
void onMessageWithRecaptchaNeededClicked(@NonNull MessageRecord messageRecord);
|
||||||
|
void onRegisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
|
void onUnregisterVoiceNoteCallbacks(@NonNull Observer<VoiceNotePlaybackState> onPlaybackStartObserver);
|
||||||
|
void onVoiceNotePause(@NonNull Uri uri);
|
||||||
|
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
|
||||||
|
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
|
||||||
|
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
|
||||||
|
void onDecryptionFailedLearnMoreClicked();
|
||||||
|
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
||||||
|
void onJoinGroupCallClicked();
|
||||||
|
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
||||||
|
void onEnableCallNotificationsClicked();
|
||||||
|
void onPlayInlineContent(ConversationMessage conversationMessage);
|
||||||
|
void onInMemoryMessageClicked(@NonNull InMemoryMessageRecord messageRecord);
|
||||||
|
void onViewGroupDescriptionChange(@Nullable GroupId groupId, @NonNull String description, boolean isMessageRequestAccepted);
|
||||||
|
|
||||||
|
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||||
|
boolean onUrlClicked(@NonNull String url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,7 @@ public interface BindableConversationListItem extends Unbindable {
|
|||||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||||
@NonNull Set<Long> typingThreads,
|
@NonNull Set<Long> typingThreads,
|
||||||
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||||
|
|
||||||
|
void setBatchMode(boolean batchMode);
|
||||||
|
void updateTypingIndicator(@NonNull Set<Long> typingThreads);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import androidx.annotation.WorkerThread;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,15 +32,15 @@ public final class BlockUnblockDialog {
|
|||||||
AlertDialog.Builder::show);
|
AlertDialog.Builder::show);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showBlockAndDeleteFor(@NonNull Context context,
|
public static void showBlockAndReportSpamFor(@NonNull Context context,
|
||||||
@NonNull Lifecycle lifecycle,
|
@NonNull Lifecycle lifecycle,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull Runnable onBlock,
|
@NonNull Runnable onBlock,
|
||||||
@NonNull Runnable onBlockAndDelete)
|
@NonNull Runnable onBlockAndReportSpam)
|
||||||
{
|
{
|
||||||
SimpleTask.run(lifecycle,
|
SimpleTask.run(lifecycle,
|
||||||
() -> buildBlockFor(context, recipient, onBlock, onBlockAndDelete),
|
() -> buildBlockFor(context, recipient, onBlock, onBlockAndReportSpam),
|
||||||
AlertDialog.Builder::show);
|
AlertDialog.Builder::show);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showUnblockFor(@NonNull Context context,
|
public static void showUnblockFor(@NonNull Context context,
|
||||||
@@ -58,11 +57,11 @@ public final class BlockUnblockDialog {
|
|||||||
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
|
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull Runnable onBlock,
|
@NonNull Runnable onBlock,
|
||||||
@Nullable Runnable onBlockAndDelete)
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
{
|
{
|
||||||
recipient = recipient.resolve();
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||||
Resources resources = context.getResources();
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
@@ -81,10 +80,10 @@ public final class BlockUnblockDialog {
|
|||||||
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
|
||||||
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
|
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
|
||||||
|
|
||||||
if (onBlockAndDelete != null) {
|
if (onBlockAndReportSpam != null) {
|
||||||
builder.setNeutralButton(android.R.string.cancel, null);
|
builder.setNeutralButton(android.R.string.cancel, null);
|
||||||
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_delete, (d, w) -> onBlockAndDelete.run());
|
builder.setNegativeButton(R.string.BlockUnblockDialog_report_spam_and_block, (d, w) -> onBlockAndReportSpam.run());
|
||||||
builder.setNegativeButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
|
||||||
} else {
|
} else {
|
||||||
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
|
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
@@ -101,7 +100,7 @@ public final class BlockUnblockDialog {
|
|||||||
{
|
{
|
||||||
recipient = recipient.resolve();
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||||
Resources resources = context.getResources();
|
Resources resources = context.getResources();
|
||||||
|
|
||||||
if (recipient.isGroup()) {
|
if (recipient.isGroup()) {
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ListView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
import androidx.fragment.app.ListFragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
|
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
|
|
||||||
public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity {
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setTitle(R.string.BlockedContactsActivity_blocked_contacts);
|
|
||||||
initFragment(android.R.id.content, new BlockedContactsFragment());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class BlockedContactsFragment
|
|
||||||
extends ListFragment
|
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>, ListView.OnItemClickListener
|
|
||||||
{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
|
||||||
return inflater.inflate(R.layout.blocked_contacts_fragment, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
super.onCreate(bundle);
|
|
||||||
setListAdapter(new BlockedContactAdapter(requireActivity(), GlideApp.with(this), null));
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle bundle) {
|
|
||||||
super.onActivityCreated(bundle);
|
|
||||||
getListView().setOnItemClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new BlockedContactsLoader(getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
|
|
||||||
if (getListAdapter() != null) {
|
|
||||||
((CursorAdapter) getListAdapter()).changeCursor(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
|
||||||
if (getListAdapter() != null) {
|
|
||||||
((CursorAdapter) getListAdapter()).changeCursor(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
Recipient recipient = ((BlockedContactListItem)view).getRecipient();
|
|
||||||
BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, () -> {
|
|
||||||
RecipientUtil.unblock(requireContext(), recipient);
|
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BlockedContactAdapter extends CursorAdapter {
|
|
||||||
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
|
|
||||||
BlockedContactAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @Nullable Cursor c) {
|
|
||||||
super(context, c);
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
|
||||||
return LayoutInflater.from(context)
|
|
||||||
.inflate(R.layout.blocked_contact_list_item, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
|
||||||
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID)));
|
|
||||||
LiveRecipient recipient = Recipient.live(recipientId);
|
|
||||||
|
|
||||||
((BlockedContactListItem) view).set(glideRequests, recipient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,26 +3,25 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
|
||||||
|
|
||||||
public class ClearProfileAvatarActivity extends Activity {
|
public final class ClearAvatarPromptActivity extends Activity {
|
||||||
|
|
||||||
private static final String ARG_TITLE = "arg_title";
|
private static final String ARG_TITLE = "arg_title";
|
||||||
|
|
||||||
public static Intent createForUserProfilePhoto() {
|
public static Intent createForUserProfilePhoto() {
|
||||||
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
Intent intent = new Intent(ApplicationDependencies.getApplication(), ClearAvatarPromptActivity.class);
|
||||||
|
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
||||||
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent createForGroupProfilePhoto() {
|
public static Intent createForGroupProfilePhoto() {
|
||||||
Intent intent = new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
|
Intent intent = new Intent(ApplicationDependencies.getApplication(), ClearAvatarPromptActivity.class);
|
||||||
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
|
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
@@ -31,10 +30,10 @@ public class ClearProfileAvatarActivity extends Activity {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
|
int message = getIntent().getIntExtra(ARG_TITLE, 0);
|
||||||
|
|
||||||
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
|
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
|
||||||
.setMessage(titleId)
|
.setMessage(message)
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
|
||||||
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@@ -12,12 +11,12 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.crypto.DatabaseSessionLock;
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MessageDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
@@ -30,17 +29,16 @@ import org.thoughtcrime.securesms.util.Base64;
|
|||||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.SignalSessionLock;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
|
||||||
|
|
||||||
public class ConfirmIdentityDialog extends AlertDialog {
|
public class ConfirmIdentityDialog extends AlertDialog {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
|
private static final String TAG = Log.tag(ConfirmIdentityDialog.class);
|
||||||
|
|
||||||
private OnClickListener callback;
|
private OnClickListener callback;
|
||||||
|
|
||||||
@@ -51,7 +49,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
|
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
|
||||||
String name = recipient.toShortString(context);
|
String name = recipient.getDisplayName(context);
|
||||||
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
|
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
|
||||||
SpannableString spannableString = new SpannableString(introduction + " " +
|
SpannableString spannableString = new SpannableString(introduction + " " +
|
||||||
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
|
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
|
||||||
@@ -97,7 +95,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
synchronized (SESSION_LOCK) {
|
try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) {
|
||||||
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
|
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
|
||||||
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
|
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
|
||||||
|
|
||||||
@@ -105,7 +103,6 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processMessageRecord(messageRecord);
|
processMessageRecord(messageRecord);
|
||||||
processPendingMessageRecords(messageRecord.getThreadId(), mismatch);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -115,29 +112,9 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
else processIncomingMessageRecord(messageRecord);
|
else processIncomingMessageRecord(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) {
|
|
||||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext());
|
|
||||||
Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId);
|
|
||||||
MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor);
|
|
||||||
MessageRecord record;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while ((record = reader.getNext()) != null) {
|
|
||||||
for (IdentityKeyMismatch recordMismatch : record.getIdentityKeyMismatches()) {
|
|
||||||
if (mismatch.equals(recordMismatch)) {
|
|
||||||
processMessageRecord(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (reader != null)
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
MessageDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
|
||||||
|
|
||||||
if (messageRecord.isMms()) {
|
if (messageRecord.isMms()) {
|
||||||
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||||
@@ -160,8 +137,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
|
|
||||||
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
private void processIncomingMessageRecord(MessageRecord messageRecord) {
|
||||||
try {
|
try {
|
||||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
|
MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
|
|
||||||
|
|
||||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||||
mismatch.getRecipientId(getContext()),
|
mismatch.getRecipientId(getContext()),
|
||||||
@@ -175,11 +151,11 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||||||
messageRecord.getDateSent(),
|
messageRecord.getDateSent(),
|
||||||
legacy ? Base64.decode(messageRecord.getBody()) : null,
|
legacy ? Base64.decode(messageRecord.getBody()) : null,
|
||||||
!legacy ? Base64.decode(messageRecord.getBody()) : null,
|
!legacy ? Base64.decode(messageRecord.getBody()) : null,
|
||||||
0, null);
|
0,
|
||||||
|
0,
|
||||||
|
null);
|
||||||
|
|
||||||
long pushId = pushDatabase.insert(envelope);
|
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), envelope, messageRecord.getId()));
|
||||||
|
|
||||||
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,16 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -42,12 +43,12 @@ import java.lang.ref.WeakReference;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
|
public abstract class ContactSelectionActivity extends PassphraseRequiredActivity
|
||||||
implements SwipeRefreshLayout.OnRefreshListener,
|
implements SwipeRefreshLayout.OnRefreshListener,
|
||||||
ContactSelectionListFragment.OnContactSelectedListener,
|
ContactSelectionListFragment.OnContactSelectedListener,
|
||||||
ContactSelectionListFragment.ScrollCallback
|
ContactSelectionListFragment.ScrollCallback
|
||||||
{
|
{
|
||||||
private static final String TAG = ContactSelectionActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(ContactSelectionActivity.class);
|
||||||
|
|
||||||
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
|
||||||
|
|
||||||
@@ -65,8 +66,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
||||||
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
|
int displayMode = Util.isDefaultSmsProvider(this) ? DisplayMode.FLAG_ALL
|
||||||
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
|
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +100,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||||
contactsFragment.setOnContactSelectedListener(this);
|
|
||||||
contactsFragment.setOnRefreshListener(this);
|
contactsFragment.setOnRefreshListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {}
|
public boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
|
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.Manifest;
|
|||||||
import android.animation.LayoutTransition;
|
import android.animation.LayoutTransition;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -29,7 +30,6 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.CycleInterpolator;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.HorizontalScrollView;
|
import android.widget.HorizontalScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -40,7 +40,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.constraintlayout.widget.ConstraintSet;
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
@@ -56,7 +55,10 @@ import com.annimon.stream.Stream;
|
|||||||
import com.google.android.material.chip.ChipGroup;
|
import com.google.android.material.chip.ChipGroup;
|
||||||
import com.pnikosis.materialishprogress.ProgressWheel;
|
import com.pnikosis.materialishprogress.ProgressWheel;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||||
|
import org.thoughtcrime.securesms.components.emoji.WarningTextView;
|
||||||
|
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactChip;
|
import org.thoughtcrime.securesms.contacts.ContactChip;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||||
@@ -64,17 +66,18 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
|||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
|
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.UsernameUtil;
|
import org.thoughtcrime.securesms.util.UsernameUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
|
||||||
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
@@ -84,7 +87,6 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +95,7 @@ import java.util.Set;
|
|||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public final class ContactSelectionListFragment extends Fragment
|
public final class ContactSelectionListFragment extends LoggingFragment
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>
|
implements LoaderManager.LoaderCallbacks<Cursor>
|
||||||
{
|
{
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -105,35 +107,43 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
public static final int NO_LIMIT = Integer.MAX_VALUE;
|
||||||
|
|
||||||
public static final String DISPLAY_MODE = "display_mode";
|
public static final String DISPLAY_MODE = "display_mode";
|
||||||
public static final String MULTI_SELECT = "multi_select";
|
|
||||||
public static final String REFRESHABLE = "refreshable";
|
public static final String REFRESHABLE = "refreshable";
|
||||||
public static final String RECENTS = "recents";
|
public static final String RECENTS = "recents";
|
||||||
public static final String TOTAL_CAPACITY = "total_capacity";
|
public static final String SELECTION_LIMITS = "selection_limits";
|
||||||
public static final String CURRENT_SELECTION = "current_selection";
|
public static final String CURRENT_SELECTION = "current_selection";
|
||||||
|
public static final String HIDE_COUNT = "hide_count";
|
||||||
|
public static final String CAN_SELECT_SELF = "can_select_self";
|
||||||
|
public static final String DISPLAY_CHIPS = "display_chips";
|
||||||
|
|
||||||
|
private ConstraintLayout constraintLayout;
|
||||||
|
private TextView emptyText;
|
||||||
|
private OnContactSelectedListener onContactSelectedListener;
|
||||||
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
|
private View showContactsLayout;
|
||||||
|
private Button showContactsButton;
|
||||||
|
private TextView showContactsDescription;
|
||||||
|
private ProgressWheel showContactsProgress;
|
||||||
|
private String cursorFilter;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private RecyclerViewFastScroller fastScroller;
|
||||||
|
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
||||||
|
private ChipGroup chipGroup;
|
||||||
|
private HorizontalScrollView chipGroupScrollContainer;
|
||||||
|
private WarningTextView groupLimit;
|
||||||
|
private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
|
||||||
|
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider;
|
||||||
|
|
||||||
private ConstraintLayout constraintLayout;
|
|
||||||
private TextView emptyText;
|
|
||||||
private OnContactSelectedListener onContactSelectedListener;
|
|
||||||
private SwipeRefreshLayout swipeRefresh;
|
|
||||||
private View showContactsLayout;
|
|
||||||
private Button showContactsButton;
|
|
||||||
private TextView showContactsDescription;
|
|
||||||
private ProgressWheel showContactsProgress;
|
|
||||||
private String cursorFilter;
|
|
||||||
private RecyclerView recyclerView;
|
|
||||||
private RecyclerViewFastScroller fastScroller;
|
|
||||||
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
|
|
||||||
private ChipGroup chipGroup;
|
|
||||||
private HorizontalScrollView chipGroupScrollContainer;
|
|
||||||
private TextView groupLimit;
|
|
||||||
|
|
||||||
@Nullable private FixedViewsAdapter headerAdapter;
|
@Nullable private FixedViewsAdapter headerAdapter;
|
||||||
@Nullable private FixedViewsAdapter footerAdapter;
|
@Nullable private FixedViewsAdapter footerAdapter;
|
||||||
@Nullable private ListCallback listCallback;
|
@Nullable private ListCallback listCallback;
|
||||||
@Nullable private ScrollCallback scrollCallback;
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
private GlideRequests glideRequests;
|
private GlideRequests glideRequests;
|
||||||
private int selectionLimit;
|
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
private Set<RecipientId> currentSelection;
|
private Set<RecipientId> currentSelection;
|
||||||
|
private boolean isMulti;
|
||||||
|
private boolean hideCount;
|
||||||
|
private boolean canSelectSelf;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
@@ -143,9 +153,33 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
listCallback = (ListCallback) context;
|
listCallback = (ListCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof ScrollCallback) {
|
||||||
|
scrollCallback = (ScrollCallback) getParentFragment();
|
||||||
|
}
|
||||||
|
|
||||||
if (context instanceof ScrollCallback) {
|
if (context instanceof ScrollCallback) {
|
||||||
scrollCallback = (ScrollCallback) context;
|
scrollCallback = (ScrollCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof OnContactSelectedListener) {
|
||||||
|
onContactSelectedListener = (OnContactSelectedListener) getParentFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnContactSelectedListener) {
|
||||||
|
onContactSelectedListener = (OnContactSelectedListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof OnSelectionLimitReachedListener) {
|
||||||
|
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||||
|
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getParentFragment() instanceof AbstractContactsCursorLoaderFactoryProvider) {
|
||||||
|
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -174,7 +208,7 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
|
|
||||||
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
|
||||||
if (activity.getIntent().getBooleanExtra(RECENTS, false)) {
|
if (safeArguments().getBoolean(RECENTS, activity.getIntent().getBooleanExtra(RECENTS, false))) {
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
|
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
|
||||||
} else {
|
} else {
|
||||||
initializeNoContactsPermission();
|
initializeNoContactsPermission();
|
||||||
@@ -208,9 +242,23 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
|
Intent intent = requireActivity().getIntent();
|
||||||
|
Bundle arguments = safeArguments();
|
||||||
|
|
||||||
|
swipeRefresh.setEnabled(arguments.getBoolean(REFRESHABLE, intent.getBooleanExtra(REFRESHABLE, true)));
|
||||||
|
|
||||||
|
hideCount = arguments.getBoolean(HIDE_COUNT, intent.getBooleanExtra(HIDE_COUNT, false));
|
||||||
|
selectionLimit = arguments.getParcelable(SELECTION_LIMITS);
|
||||||
|
if (selectionLimit == null) {
|
||||||
|
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
|
||||||
|
}
|
||||||
|
isMulti = selectionLimit != null;
|
||||||
|
canSelectSelf = arguments.getBoolean(CAN_SELECT_SELF, intent.getBooleanExtra(CAN_SELECT_SELF, !isMulti));
|
||||||
|
|
||||||
|
if (!isMulti) {
|
||||||
|
selectionLimit = SelectionLimits.NO_LIMITS;
|
||||||
|
}
|
||||||
|
|
||||||
selectionLimit = requireActivity().getIntent().getIntExtra(TOTAL_CAPACITY, NO_LIMIT);
|
|
||||||
currentSelection = getCurrentSelection();
|
currentSelection = getCurrentSelection();
|
||||||
|
|
||||||
updateGroupLimit(getChipCount());
|
updateGroupLimit(getChipCount());
|
||||||
@@ -218,13 +266,15 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NonNull Bundle safeArguments() {
|
||||||
|
return getArguments() != null ? getArguments() : new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateGroupLimit(int chipCount) {
|
private void updateGroupLimit(int chipCount) {
|
||||||
if (selectionLimit != NO_LIMIT) {
|
int members = currentSelection.size() + chipCount;
|
||||||
groupLimit.setText(String.format(Locale.getDefault(), "%d/%d", currentSelection.size() + chipCount, selectionLimit));
|
groupLimit.setText(getResources().getQuantityString(R.plurals.ContactSelectionListFragment_d_members, members, members));
|
||||||
groupLimit.setVisibility(View.VISIBLE);
|
groupLimit.setVisibility(isMulti && !hideCount ? View.VISIBLE : View.GONE);
|
||||||
} else {
|
groupLimit.setWarning(selectionWarningLimitExceeded());
|
||||||
groupLimit.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -249,14 +299,17 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<RecipientId> getCurrentSelection() {
|
private Set<RecipientId> getCurrentSelection() {
|
||||||
List<RecipientId> currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
|
List<RecipientId> currentSelection = safeArguments().getParcelableArrayList(CURRENT_SELECTION);
|
||||||
|
if (currentSelection == null) {
|
||||||
|
currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
|
||||||
|
}
|
||||||
|
|
||||||
return currentSelection == null ? Collections.emptySet()
|
return currentSelection == null ? Collections.emptySet()
|
||||||
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMulti() {
|
public boolean isMulti() {
|
||||||
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
|
return isMulti;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCursor() {
|
private void initializeCursor() {
|
||||||
@@ -266,17 +319,13 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
glideRequests,
|
glideRequests,
|
||||||
null,
|
null,
|
||||||
new ListClickListener(),
|
new ListClickListener(),
|
||||||
isMulti(),
|
isMulti,
|
||||||
currentSelection);
|
currentSelection);
|
||||||
|
|
||||||
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
|
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
|
||||||
|
|
||||||
if (listCallback != null && FeatureFlags.newGroupUI()) {
|
if (listCallback != null) {
|
||||||
if (FeatureFlags.groupsV2create() && FeatureFlags.groupsV2internalTest()) {
|
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback));
|
||||||
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback), createNewGroupsV1GroupItem(listCallback));
|
|
||||||
} else {
|
|
||||||
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback));
|
|
||||||
}
|
|
||||||
headerAdapter.hide();
|
headerAdapter.hide();
|
||||||
concatenateAdapter.addAdapter(headerAdapter);
|
concatenateAdapter.addAdapter(headerAdapter);
|
||||||
}
|
}
|
||||||
@@ -290,7 +339,7 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.setAdapter(concatenateAdapter);
|
recyclerView.setAdapter(concatenateAdapter);
|
||||||
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
|
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
|
||||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
@@ -317,13 +366,6 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private View createNewGroupsV1GroupItem(@NonNull ListCallback listCallback) {
|
|
||||||
View view = LayoutInflater.from(requireContext())
|
|
||||||
.inflate(R.layout.contact_selection_new_group_v1_item, (ViewGroup) requireView(), false);
|
|
||||||
view.setOnClickListener(v -> listCallback.onNewGroup(true));
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeNoContactsPermission() {
|
private void initializeNoContactsPermission() {
|
||||||
swipeRefresh.setVisibility(View.GONE);
|
swipeRefresh.setVisibility(View.GONE);
|
||||||
|
|
||||||
@@ -374,10 +416,15 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
FragmentActivity activity = requireActivity();
|
FragmentActivity activity = requireActivity();
|
||||||
return new ContactsCursorLoader(activity,
|
int displayMode = safeArguments().getInt(DISPLAY_MODE, activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL));
|
||||||
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
|
boolean displayRecents = safeArguments().getBoolean(RECENTS, activity.getIntent().getBooleanExtra(RECENTS, false));
|
||||||
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
|
|
||||||
|
if (cursorFactoryProvider != null) {
|
||||||
|
return cursorFactoryProvider.get().create();
|
||||||
|
} else {
|
||||||
|
return new ContactsCursorLoader.Factory(activity, displayMode, cursorFilter, displayRecents).create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -450,8 +497,11 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
swipeRefresh.setVisibility(View.VISIBLE);
|
swipeRefresh.setVisibility(View.VISIBLE);
|
||||||
reset();
|
reset();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
Context context = getContext();
|
||||||
initializeNoContactsPermission();
|
if (context != null) {
|
||||||
|
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
||||||
|
initializeNoContactsPermission();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
@@ -463,10 +513,18 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
|
||||||
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
|
||||||
|
|
||||||
if (!isMulti() || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
|
||||||
if (selectionLimitReached()) {
|
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_the_group_is_full, Toast.LENGTH_SHORT).show();
|
return;
|
||||||
groupLimit.animate().scaleX(1.3f).scaleY(1.3f).setInterpolator(new CycleInterpolator(0.5f)).start();
|
}
|
||||||
|
|
||||||
|
if (!isMulti || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
|
||||||
|
if (selectionHardLimitReached()) {
|
||||||
|
if (onSelectionLimitReachedListener != null) {
|
||||||
|
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showHardLimitMessage(requireContext());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,45 +538,61 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
if (uuid.isPresent()) {
|
if (uuid.isPresent()) {
|
||||||
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
|
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
|
||||||
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
|
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
|
||||||
markContactSelected(selected);
|
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null);
|
if (onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null)) {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selected);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
|
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
|
||||||
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
|
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
|
||||||
.setPositiveButton(R.string.ContactSelectionListFragment_okay, (dialog, which) -> dialog.dismiss())
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
markContactSelected(selectedContact);
|
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber());
|
if (onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber())) {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markContactSelected(selectedContact);
|
||||||
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markContactUnselected(selectedContact);
|
markContactUnselected(selectedContact);
|
||||||
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
|
||||||
|
|
||||||
if (onContactSelectedListener != null) {
|
if (onContactSelectedListener != null) {
|
||||||
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean selectionLimitReached() {
|
private boolean selectionHardLimitReached() {
|
||||||
return getChipCount() >= selectionLimit;
|
return getChipCount() + currentSelection.size() >= selectionLimit.getHardLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean selectionWarningLimitReachedExactly() {
|
||||||
|
return getChipCount() + currentSelection.size() == selectionLimit.getRecommendedLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean selectionWarningLimitExceeded() {
|
||||||
|
return getChipCount() + currentSelection.size() > selectionLimit.getRecommendedLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markContactSelected(@NonNull SelectedContact selectedContact) {
|
private void markContactSelected(@NonNull SelectedContact selectedContact) {
|
||||||
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
|
||||||
if (isMulti() && FeatureFlags.newGroupUI()) {
|
if (isMulti) {
|
||||||
addChipForSelectedContact(selectedContact);
|
addChipForSelectedContact(selectedContact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,6 +663,13 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
private void addChip(@NonNull ContactChip chip) {
|
private void addChip(@NonNull ContactChip chip) {
|
||||||
chipGroup.addView(chip);
|
chipGroup.addView(chip);
|
||||||
updateGroupLimit(getChipCount());
|
updateGroupLimit(getChipCount());
|
||||||
|
if (selectionWarningLimitReachedExactly()) {
|
||||||
|
if (onSelectionLimitReachedListener != null) {
|
||||||
|
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
|
||||||
|
} else {
|
||||||
|
GroupLimitDialog.showRecommendedLimitMessage(requireContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getChipCount() {
|
private int getChipCount() {
|
||||||
@@ -609,6 +690,10 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setChipGroupVisibility(int visibility) {
|
private void setChipGroupVisibility(int visibility) {
|
||||||
|
if (!safeArguments().getBoolean(DISPLAY_CHIPS, requireActivity().getIntent().getBooleanExtra(DISPLAY_CHIPS, true))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
|
||||||
|
|
||||||
ConstraintSet constraintSet = new ConstraintSet();
|
ConstraintSet constraintSet = new ConstraintSet();
|
||||||
@@ -617,24 +702,26 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
constraintSet.applyTo(constraintLayout);
|
constraintSet.applyTo(constraintLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
|
|
||||||
this.onContactSelectedListener = onContactSelectedListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
|
||||||
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void smoothScrollChipsToEnd() {
|
private void smoothScrollChipsToEnd() {
|
||||||
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
|
int x = ViewUtil.isLtr(chipGroupScrollContainer) ? chipGroup.getWidth() : 0;
|
||||||
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
chipGroupScrollContainer.smoothScrollTo(x, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnContactSelectedListener {
|
public interface OnContactSelectedListener {
|
||||||
void onContactSelected(Optional<RecipientId> recipientId, String number);
|
/** @return True if the contact is allowed to be selected, otherwise false. */
|
||||||
|
boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number);
|
||||||
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
void onContactDeselected(Optional<RecipientId> recipientId, String number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnSelectionLimitReachedListener {
|
||||||
|
void onSuggestedLimitReached(int limit);
|
||||||
|
void onHardLimitReached(int limit);
|
||||||
|
}
|
||||||
|
|
||||||
public interface ListCallback {
|
public interface ListCallback {
|
||||||
void onInvite();
|
void onInvite();
|
||||||
void onNewGroup(boolean forceV1);
|
void onNewGroup(boolean forceV1);
|
||||||
@@ -643,4 +730,8 @@ public final class ContactSelectionListFragment extends Fragment
|
|||||||
public interface ScrollCallback {
|
public interface ScrollCallback {
|
||||||
void onBeginScroll();
|
void onBeginScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface AbstractContactsCursorLoaderFactoryProvider {
|
||||||
|
@NonNull AbstractContactsCursorLoader.Factory get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.content.ServiceConnection;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,7 +22,7 @@ import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
|
|||||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
|
||||||
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
|
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
|
||||||
|
|
||||||
public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivity {
|
public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
|
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
|
||||||
private final ImportStateHandler importStateHandler = new ImportStateHandler();
|
private final ImportStateHandler importStateHandler = new ImportStateHandler();
|
||||||
@@ -150,7 +151,7 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivi
|
|||||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||||
} else {
|
} else {
|
||||||
// TODO [greyson] Navigation
|
// TODO [greyson] Navigation
|
||||||
startActivity(new Intent(this, MainActivity.class));
|
startActivity(MainActivity.clearTop(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +159,11 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ImportStateHandler extends Handler {
|
private class ImportStateHandler extends Handler {
|
||||||
|
|
||||||
|
public ImportStateHandler() {
|
||||||
|
super(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
switch (message.what) {
|
switch (message.what) {
|
||||||
|
|||||||
@@ -16,20 +16,21 @@ import android.widget.Button;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
@@ -42,13 +43,13 @@ import org.whispersystems.signalservice.internal.push.DeviceLimitExceededExcepti
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DeviceActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceActivity.class);
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
private DeviceAddFragment deviceAddFragment;
|
private DeviceAddFragment deviceAddFragment;
|
||||||
@@ -63,9 +64,14 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
getSupportActionBar().setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_arrow_left_24));
|
setContentView(R.layout.device_activity);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
requireSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
requireSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
|
||||||
|
|
||||||
this.deviceAddFragment = new DeviceAddFragment();
|
this.deviceAddFragment = new DeviceAddFragment();
|
||||||
this.deviceListFragment = new DeviceListFragment();
|
this.deviceListFragment = new DeviceListFragment();
|
||||||
this.deviceLinkFragment = new DeviceLinkFragment();
|
this.deviceLinkFragment = new DeviceLinkFragment();
|
||||||
@@ -74,20 +80,10 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
this.deviceAddFragment.setScanListener(this);
|
this.deviceAddFragment.setScanListener(this);
|
||||||
|
|
||||||
if (getIntent().getBooleanExtra("add", false)) {
|
if (getIntent().getBooleanExtra("add", false)) {
|
||||||
initFragment(android.R.id.content, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
||||||
} else {
|
} else {
|
||||||
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
overridePendingTransition(R.anim.slide_from_end, R.anim.slide_to_start);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
if (isFinishing()) {
|
|
||||||
overridePendingTransition(R.anim.slide_from_start, R.anim.slide_to_end);
|
|
||||||
}
|
|
||||||
super.onPause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,8 +95,9 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == android.R.id.home) {
|
||||||
case android.R.id.home: finish(); return true;
|
finish();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -114,7 +111,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
.withPermanentDenialDialog(getString(R.string.DeviceActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code))
|
.withPermanentDenialDialog(getString(R.string.DeviceActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code))
|
||||||
.onAllGranted(() -> {
|
.onAllGranted(() -> {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(android.R.id.content, deviceAddFragment)
|
.replace(R.id.fragment_container, deviceAddFragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
})
|
})
|
||||||
@@ -124,12 +121,12 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onQrDataFound(final String data) {
|
public void onQrDataFound(final String data) {
|
||||||
Util.runOnMain(() -> {
|
ThreadUtil.runOnMain(() -> {
|
||||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
Uri uri = Uri.parse(data);
|
Uri uri = Uri.parse(data);
|
||||||
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
|
||||||
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
|
||||||
|
|
||||||
@@ -139,14 +136,14 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
|||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
|
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
|
||||||
.replace(android.R.id.content, deviceLinkFragment)
|
.replace(R.id.fragment_container, deviceLinkFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
|
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
|
||||||
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
|
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
|
||||||
.replace(android.R.id.content, deviceLinkFragment)
|
.replace(R.id.fragment_container, deviceLinkFragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewAnimationUtils;
|
import android.view.ViewAnimationUtils;
|
||||||
@@ -15,12 +13,14 @@ import android.view.animation.DecelerateInterpolator;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener;
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.qr.ScanningThread;
|
import org.thoughtcrime.securesms.qr.ScanningThread;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
public class DeviceAddFragment extends Fragment {
|
public class DeviceAddFragment extends LoggingFragment {
|
||||||
|
|
||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
private LinearLayout overlay;
|
private LinearLayout overlay;
|
||||||
@@ -32,9 +32,9 @@ public class DeviceAddFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||||
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
||||||
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
this.overlay = this.container.findViewById(R.id.overlay);
|
||||||
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
this.scannerView = this.container.findViewById(R.id.scanner);
|
||||||
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
this.devicesImage = this.container.findViewById(R.id.devices);
|
||||||
|
|
||||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
@@ -42,9 +42,9 @@ public class DeviceAddFragment extends Fragment {
|
|||||||
this.overlay.setOrientation(LinearLayout.VERTICAL);
|
this.overlay.setOrientation(LinearLayout.VERTICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
@TargetApi(21)
|
||||||
@Override
|
@Override
|
||||||
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
||||||
int oldLeft, int oldTop, int oldRight, int oldBottom)
|
int oldLeft, int oldTop, int oldRight, int oldBottom)
|
||||||
@@ -80,7 +80,7 @@ public class DeviceAddFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
||||||
super.onConfigurationChanged(newConfiguration);
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
|
||||||
this.scannerView.onPause();
|
this.scannerView.onPause();
|
||||||
@@ -107,6 +107,4 @@ public class DeviceAddFragment extends Fragment {
|
|||||||
this.scanningThread.setScanListener(scanListener);
|
this.scanningThread.setScanListener(scanListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
|
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
|
||||||
|
|
||||||
private LinearLayout container;
|
private LinearLayout container;
|
||||||
@@ -31,7 +32,7 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfiguration) {
|
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
|
||||||
super.onConfigurationChanged(newConfiguration);
|
super.onConfigurationChanged(newConfiguration);
|
||||||
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
container.setOrientation(LinearLayout.HORIZONTAL);
|
container.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|||||||
@@ -6,15 +6,6 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.ListFragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -24,12 +15,21 @@ import android.widget.Button;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.melnykov.fab.FloatingActionButton;
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -41,7 +41,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
ListView.OnItemClickListener, Button.OnClickListener
|
ListView.OnItemClickListener, Button.OnClickListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceListFragment.class);
|
||||||
|
|
||||||
private SignalServiceAccountManager accountManager;
|
private SignalServiceAccountManager accountManager;
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
@@ -53,12 +53,12 @@ public class DeviceListFragment extends ListFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
|
this.locale = (Locale) requireArguments().getSerializable(PassphraseRequiredActivity.LOCALE_EXTRA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(activity);
|
super.onAttach(context);
|
||||||
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
this.empty = view.findViewById(R.id.empty);
|
this.empty = view.findViewById(R.id.empty);
|
||||||
this.progressContainer = view.findViewById(R.id.progress_container);
|
this.progressContainer = view.findViewById(R.id.progress_container);
|
||||||
this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
|
this.addDeviceButton = view.findViewById(R.id.add_device);
|
||||||
this.addDeviceButton.setOnClickListener(this);
|
this.addDeviceButton.setOnClickListener(this);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
@@ -122,42 +122,22 @@ public class DeviceListFragment extends ListFragment
|
|||||||
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
||||||
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity());
|
||||||
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
builder.setTitle(getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
||||||
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> handleDisconnectDevice(deviceId));
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
handleDisconnectDevice(deviceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLoaderFailed() {
|
private void handleLoaderFailed() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity());
|
||||||
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
|
||||||
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, which) -> getLoaderManager().restartLoader(0, null, DeviceListFragment.this));
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> requireActivity().onBackPressed());
|
||||||
@Override
|
builder.setOnCancelListener(dialog -> requireActivity().onBackPressed());
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
DeviceListFragment.this.getActivity().onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
|
||||||
@Override
|
|
||||||
public void onCancel(DialogInterface dialog) {
|
|
||||||
DeviceListFragment.this.getActivity().onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActivity {
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
|
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
|
private static final String TAG = Log.tag(DeviceProvisioningActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
@@ -26,7 +29,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.DeviceProvisioningActivity_cancel, (dialog12, which) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog12, which) -> {
|
||||||
dialog12.dismiss();
|
dialog12.dismiss();
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
|
||||||
|
|
||||||
import cn.carbswang.android.numberpickerview.library.NumberPickerView;
|
|
||||||
|
|
||||||
public class ExpirationDialog extends AlertDialog {
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context, int theme) {
|
|
||||||
super(context, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ExpirationDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
|
|
||||||
super(context, cancelable, cancelListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void show(final Context context,
|
|
||||||
final int currentExpiration,
|
|
||||||
final @NonNull OnClickListener listener)
|
|
||||||
{
|
|
||||||
final View view = createNumberPickerView(context, currentExpiration);
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
|
||||||
builder.setTitle(context.getString(R.string.ExpirationDialog_disappearing_messages));
|
|
||||||
builder.setView(view);
|
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
int selected = ((NumberPickerView)view.findViewById(R.id.expiration_number_picker)).getValue();
|
|
||||||
listener.onClick(context.getResources().getIntArray(R.array.expiration_times)[selected]);
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static View createNumberPickerView(final Context context, final int currentExpiration) {
|
|
||||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
|
||||||
final View view = inflater.inflate(R.layout.expiration_dialog, null);
|
|
||||||
final NumberPickerView numberPickerView = view.findViewById(R.id.expiration_number_picker);
|
|
||||||
final TextView textView = view.findViewById(R.id.expiration_details);
|
|
||||||
final int[] expirationTimes = context.getResources().getIntArray(R.array.expiration_times);
|
|
||||||
final String[] expirationDisplayValues = new String[expirationTimes.length];
|
|
||||||
|
|
||||||
int selectedIndex = expirationTimes.length - 1;
|
|
||||||
|
|
||||||
for (int i=0;i<expirationTimes.length;i++) {
|
|
||||||
expirationDisplayValues[i] = ExpirationUtil.getExpirationDisplayValue(context, expirationTimes[i]);
|
|
||||||
|
|
||||||
if ((currentExpiration >= expirationTimes[i]) &&
|
|
||||||
(i == expirationTimes.length -1 || currentExpiration < expirationTimes[i+1])) {
|
|
||||||
selectedIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
numberPickerView.setDisplayedValues(expirationDisplayValues);
|
|
||||||
numberPickerView.setMinValue(0);
|
|
||||||
numberPickerView.setMaxValue(expirationTimes.length-1);
|
|
||||||
|
|
||||||
NumberPickerView.OnValueChangeListener listener = (picker, oldVal, newVal) -> {
|
|
||||||
if (newVal == 0) {
|
|
||||||
textView.setText(R.string.ExpirationDialog_your_messages_will_not_expire);
|
|
||||||
} else {
|
|
||||||
textView.setText(context.getString(R.string.ExpirationDialog_your_messages_will_disappear_s_after_they_have_been_seen, picker.getDisplayedValues()[newVal]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
numberPickerView.setOnValueChangedListener(listener);
|
|
||||||
numberPickerView.setValue(selectedIndex);
|
|
||||||
listener.onValueChange(numberPickerView, selectedIndex, selectedIndex);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnClickListener {
|
|
||||||
public void onClick(int expirationTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,629 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
import com.bumptech.glide.request.target.SimpleTarget;
|
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
|
|
||||||
import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener;
|
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
|
||||||
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
|
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
|
||||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
|
||||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
|
|
||||||
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activity to create and update {@link GroupId.V1} groups
|
|
||||||
*
|
|
||||||
* @author Jake McGinty
|
|
||||||
*/
|
|
||||||
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
|
||||||
implements OnRecipientDeletedListener,
|
|
||||||
RecipientsPanelChangedListener
|
|
||||||
{
|
|
||||||
|
|
||||||
private final static String TAG = GroupCreateActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final String GROUP_ID_EXTRA = "group_id";
|
|
||||||
private static final String GROUP_THREAD_EXTRA = "group_thread";
|
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
|
|
||||||
private static final short REQUEST_CODE_SELECT_AVATAR = 26165;
|
|
||||||
private static final int PICK_CONTACT = 1;
|
|
||||||
|
|
||||||
private EditText groupName;
|
|
||||||
private ListView listView;
|
|
||||||
private ImageView avatar;
|
|
||||||
private TextView creatingText;
|
|
||||||
private Bitmap avatarBmp;
|
|
||||||
|
|
||||||
@NonNull private Optional<GroupData> groupToUpdate = Optional.absent();
|
|
||||||
|
|
||||||
public static Intent newEditGroupIntent(@NonNull Context context, @NonNull GroupId.V1 groupId) {
|
|
||||||
Intent intent = new Intent(context, GroupCreateActivity.class);
|
|
||||||
intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, groupId.toString());
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle state, boolean ready) {
|
|
||||||
setContentView(R.layout.group_create_activity);
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
initializeAppBar();
|
|
||||||
initializeResources();
|
|
||||||
initializeExistingGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
updateViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSignalGroup() {
|
|
||||||
return TextSecurePreferences.isPushRegistered(this) && !getAdapter().hasNonPushMembers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableSignalGroupViews(int reasonResId) {
|
|
||||||
View pushDisabled = findViewById(R.id.push_disabled);
|
|
||||||
pushDisabled.setVisibility(View.VISIBLE);
|
|
||||||
((TextView) findViewById(R.id.push_disabled_reason)).setText(reasonResId);
|
|
||||||
avatar.setEnabled(false);
|
|
||||||
groupName.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableSignalGroupViews() {
|
|
||||||
findViewById(R.id.push_disabled).setVisibility(View.GONE);
|
|
||||||
avatar.setEnabled(true);
|
|
||||||
groupName.setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
private void updateViewState() {
|
|
||||||
if (!TextSecurePreferences.isPushRegistered(this)) {
|
|
||||||
disableSignalGroupViews(R.string.GroupCreateActivity_youre_not_registered_for_signal);
|
|
||||||
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title);
|
|
||||||
} else if (getAdapter().hasNonPushMembers()) {
|
|
||||||
disableSignalGroupViews(R.string.GroupCreateActivity_contacts_dont_support_push);
|
|
||||||
getSupportActionBar().setTitle(R.string.GroupCreateActivity_actionbar_mms_title);
|
|
||||||
} else {
|
|
||||||
enableSignalGroupViews();
|
|
||||||
getSupportActionBar().setTitle(groupToUpdate.isPresent()
|
|
||||||
? R.string.GroupCreateActivity_actionbar_edit_title
|
|
||||||
: R.string.GroupCreateActivity_actionbar_title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isActiveInDirectory(Recipient recipient) {
|
|
||||||
return recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addSelectedContacts(@NonNull Recipient... recipients) {
|
|
||||||
new AddMembersTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addSelectedContacts(@NonNull Collection<Recipient> recipients) {
|
|
||||||
addSelectedContacts(recipients.toArray(new Recipient[recipients.size()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeAppBar() {
|
|
||||||
Drawable upIcon = ContextCompat.getDrawable(this, R.drawable.ic_arrow_left_24);
|
|
||||||
getSupportActionBar().setHomeAsUpIndicator(upIcon);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
RecipientsEditor recipientsEditor = findViewById(R.id.recipients_text);
|
|
||||||
PushRecipientsPanel recipientsPanel = findViewById(R.id.recipients);
|
|
||||||
|
|
||||||
listView = findViewById(R.id.selected_contacts_list);
|
|
||||||
avatar = findViewById(R.id.avatar);
|
|
||||||
groupName = findViewById(R.id.group_name);
|
|
||||||
creatingText = findViewById(R.id.creating_group_text);
|
|
||||||
|
|
||||||
SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this);
|
|
||||||
adapter.setOnRecipientDeletedListener(this);
|
|
||||||
listView.setAdapter(adapter);
|
|
||||||
|
|
||||||
recipientsEditor.setHint(R.string.recipients_panel__add_members);
|
|
||||||
recipientsPanel.setPanelChangeListener(this);
|
|
||||||
|
|
||||||
findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
|
|
||||||
|
|
||||||
avatar.setImageDrawable(getDefaultGroupAvatar());
|
|
||||||
avatar.setOnClickListener(view -> AvatarSelectionBottomSheetDialogFragment.create(avatarBmp != null, false, REQUEST_CODE_SELECT_AVATAR, true).show(getSupportFragmentManager(), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable getDefaultGroupAvatar() {
|
|
||||||
return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeExistingGroup() {
|
|
||||||
final GroupId groupId = GroupId.parseNullableOrThrow(getIntent().getStringExtra(GROUP_ID_EXTRA));
|
|
||||||
|
|
||||||
if (groupId != null) {
|
|
||||||
GroupId.V1 groupIdV1 = groupId.requireV1();
|
|
||||||
|
|
||||||
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupIdV1);
|
|
||||||
|
|
||||||
if (FeatureFlags.newGroupUI()) {
|
|
||||||
avatar.setOnClickListener(v -> startActivity(EditProfileActivity.getIntentForGroupProfile(this, groupIdV1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
|
||||||
menu.clear();
|
|
||||||
|
|
||||||
inflater.inflate(R.menu.group_create, menu);
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
super.onOptionsItemSelected(item);
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case android.R.id.home:
|
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
case R.id.menu_create_group:
|
|
||||||
if (groupToUpdate.isPresent()) handleGroupUpdate();
|
|
||||||
else handleGroupCreate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRecipientDeleted(Recipient recipient) {
|
|
||||||
getAdapter().remove(recipient);
|
|
||||||
updateViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRecipientsPanelUpdate(List<Recipient> recipients) {
|
|
||||||
if (recipients != null && !recipients.isEmpty()) addSelectedContacts(recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGroupCreate() {
|
|
||||||
if (getAdapter().getCount() < 1) {
|
|
||||||
Log.i(TAG, getString(R.string.GroupCreateActivity_contacts_no_members));
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.GroupCreateActivity_contacts_no_members, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isSignalGroup()) {
|
|
||||||
new CreateSignalGroupTask(this, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
} else {
|
|
||||||
new CreateMmsGroupTask(this, getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGroupUpdate() {
|
|
||||||
new UpdateSignalGroupV1Task(this, groupToUpdate.get().id, avatarBmp,
|
|
||||||
getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleOpenConversation(long threadId, Recipient recipient) {
|
|
||||||
Intent intent = new Intent(this, ConversationActivity.class);
|
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
|
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
|
|
||||||
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SelectedRecipientsAdapter getAdapter() {
|
|
||||||
return (SelectedRecipientsAdapter) listView.getAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable String getGroupName() {
|
|
||||||
return groupName.getText() != null ? groupName.getText().toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
|
||||||
super.onActivityResult(reqCode, resultCode, data);
|
|
||||||
|
|
||||||
if (data == null || resultCode != Activity.RESULT_OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (reqCode) {
|
|
||||||
case PICK_CONTACT:
|
|
||||||
List<RecipientId> selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
|
|
||||||
|
|
||||||
for (RecipientId contact : selected) {
|
|
||||||
Recipient recipient = Recipient.resolved(contact);
|
|
||||||
addSelectedContacts(recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case REQUEST_CODE_SELECT_AVATAR:
|
|
||||||
if (data.getBooleanExtra("delete", false)) {
|
|
||||||
avatarBmp = null;
|
|
||||||
avatar.setImageDrawable(getDefaultGroupAvatar());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
|
|
||||||
final DecryptableUri decryptableUri = new DecryptableUri(result.getUri());
|
|
||||||
|
|
||||||
GlideApp.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.load(decryptableUri)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.centerCrop()
|
|
||||||
.override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
|
|
||||||
.into(new SimpleTarget<Bitmap>() {
|
|
||||||
@Override
|
|
||||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
|
||||||
setAvatar(decryptableUri, resource);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AddRecipientButtonListener implements View.OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
Intent intent = new Intent(GroupCreateActivity.this, PushContactSelectionActivity.class);
|
|
||||||
if (groupToUpdate.isPresent()) {
|
|
||||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH);
|
|
||||||
} else {
|
|
||||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH | DisplayMode.FLAG_SMS);
|
|
||||||
}
|
|
||||||
startActivityForResult(intent, PICK_CONTACT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CreateMmsGroupTask extends AsyncTask<Void,Void,GroupActionResult> {
|
|
||||||
private final GroupCreateActivity activity;
|
|
||||||
private final Set<Recipient> members;
|
|
||||||
|
|
||||||
public CreateMmsGroupTask(GroupCreateActivity activity, Set<Recipient> members) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.members = members;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GroupActionResult doInBackground(Void... avoid) {
|
|
||||||
List<RecipientId> memberAddresses = new LinkedList<>();
|
|
||||||
|
|
||||||
for (Recipient recipient : members) {
|
|
||||||
memberAddresses.add(recipient.getId());
|
|
||||||
}
|
|
||||||
memberAddresses.add(Recipient.self().getId());
|
|
||||||
|
|
||||||
GroupId.Mms groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateMmsGroupForMembers(memberAddresses);
|
|
||||||
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId);
|
|
||||||
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
|
|
||||||
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
|
|
||||||
|
|
||||||
return new GroupActionResult(groupRecipient, threadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(GroupActionResult result) {
|
|
||||||
activity.handleOpenConversation(result.getThreadId(), result.getGroupRecipient());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onProgressUpdate(Void... values) {
|
|
||||||
super.onProgressUpdate(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract static class SignalGroupTask extends AsyncTask<Void,Void,Optional<GroupActionResult>> {
|
|
||||||
|
|
||||||
protected GroupCreateActivity activity;
|
|
||||||
protected Bitmap avatar;
|
|
||||||
protected Set<Recipient> members;
|
|
||||||
protected String name;
|
|
||||||
|
|
||||||
public SignalGroupTask(GroupCreateActivity activity,
|
|
||||||
Bitmap avatar,
|
|
||||||
String name,
|
|
||||||
Set<Recipient> members)
|
|
||||||
{
|
|
||||||
this.activity = activity;
|
|
||||||
this.avatar = avatar;
|
|
||||||
this.name = name;
|
|
||||||
this.members = members;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
activity.findViewById(R.id.group_details_layout).setVisibility(View.GONE);
|
|
||||||
activity.findViewById(R.id.creating_group_layout).setVisibility(View.VISIBLE);
|
|
||||||
activity.findViewById(R.id.menu_create_group).setVisibility(View.GONE);
|
|
||||||
final int titleResId = activity.groupToUpdate.isPresent()
|
|
||||||
? R.string.GroupCreateActivity_updating_group
|
|
||||||
: R.string.GroupCreateActivity_creating_group;
|
|
||||||
activity.creatingText.setText(activity.getString(titleResId, activity.getGroupName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Optional<GroupActionResult> groupActionResultOptional) {
|
|
||||||
if (activity.isFinishing()) return;
|
|
||||||
activity.findViewById(R.id.group_details_layout).setVisibility(View.VISIBLE);
|
|
||||||
activity.findViewById(R.id.creating_group_layout).setVisibility(View.GONE);
|
|
||||||
activity.findViewById(R.id.menu_create_group).setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CreateSignalGroupTask extends SignalGroupTask {
|
|
||||||
public CreateSignalGroupTask(GroupCreateActivity activity, Bitmap avatar, String name, Set<Recipient> members) {
|
|
||||||
super(activity, avatar, name, members);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
|
|
||||||
return Optional.of(GroupManager.createGroupV1(activity, members, BitmapUtil.toByteArray(avatar), name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Optional<GroupActionResult> result) {
|
|
||||||
if (result.isPresent() && result.get().getThreadId() > -1) {
|
|
||||||
if (!activity.isFinishing()) {
|
|
||||||
activity.handleOpenConversation(result.get().getThreadId(), result.get().getGroupRecipient());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
Toast.makeText(activity.getApplicationContext(),
|
|
||||||
R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UpdateSignalGroupV1Task extends SignalGroupTask {
|
|
||||||
private final GroupId.V1 groupId;
|
|
||||||
|
|
||||||
UpdateSignalGroupV1Task(GroupCreateActivity activity, GroupId.V1 groupId,
|
|
||||||
Bitmap avatar, String name, Set<Recipient> members)
|
|
||||||
{
|
|
||||||
super(activity, avatar, name, members);
|
|
||||||
this.groupId = groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
|
|
||||||
return Optional.fromNullable(GroupManager.updateGroup(activity, groupId, members, BitmapUtil.toByteArray(avatar), name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Optional<GroupActionResult> result) {
|
|
||||||
if (result.isPresent() && result.get().getThreadId() > -1) {
|
|
||||||
if (!activity.isFinishing()) {
|
|
||||||
Intent intent = activity.getIntent();
|
|
||||||
intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId());
|
|
||||||
intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId().toString());
|
|
||||||
activity.setResult(RESULT_OK, intent);
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
Toast.makeText(activity.getApplicationContext(),
|
|
||||||
R.string.GroupCreateActivity_contacts_invalid_number, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AddMembersTask extends AsyncTask<Recipient,Void,List<AddMembersTask.Result>> {
|
|
||||||
static class Result {
|
|
||||||
Optional<Recipient> recipient;
|
|
||||||
boolean isPush;
|
|
||||||
String reason;
|
|
||||||
|
|
||||||
public Result(@Nullable Recipient recipient, boolean isPush, @Nullable String reason) {
|
|
||||||
this.recipient = Optional.fromNullable(recipient);
|
|
||||||
this.isPush = isPush;
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private GroupCreateActivity activity;
|
|
||||||
private boolean failIfNotPush;
|
|
||||||
|
|
||||||
public AddMembersTask(@NonNull GroupCreateActivity activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.failIfNotPush = activity.groupToUpdate.isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Result> doInBackground(Recipient... recipients) {
|
|
||||||
final List<Result> results = new LinkedList<>();
|
|
||||||
|
|
||||||
for (Recipient recipient : recipients) {
|
|
||||||
boolean isPush = isActiveInDirectory(recipient);
|
|
||||||
|
|
||||||
if (failIfNotPush && !isPush) {
|
|
||||||
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group,
|
|
||||||
recipient.toShortString(activity))));
|
|
||||||
} else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getE164().or(""))) {
|
|
||||||
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_youre_already_in_the_group)));
|
|
||||||
} else {
|
|
||||||
results.add(new Result(recipient, isPush, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(List<Result> results) {
|
|
||||||
if (activity.isFinishing()) return;
|
|
||||||
|
|
||||||
for (Result result : results) {
|
|
||||||
if (result.recipient.isPresent()) {
|
|
||||||
activity.getAdapter().add(result.recipient.get(), result.isPush);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(activity, result.reason, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
activity.updateViewState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<GroupId.V1, Void, Optional<GroupData>> {
|
|
||||||
private GroupCreateActivity activity;
|
|
||||||
|
|
||||||
public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) {
|
|
||||||
super(activity,
|
|
||||||
R.string.GroupCreateActivity_loading_group_details,
|
|
||||||
R.string.please_wait);
|
|
||||||
this.activity = activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Optional<GroupData> doInBackground(GroupId.V1... groupIds) {
|
|
||||||
final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity);
|
|
||||||
final List<Recipient> recipients = db.getGroupMembers(groupIds[0], GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
|
||||||
final Optional<GroupRecord> group = db.getGroup(groupIds[0]);
|
|
||||||
final Set<Recipient> existingContacts = new HashSet<>(recipients.size());
|
|
||||||
existingContacts.addAll(recipients);
|
|
||||||
|
|
||||||
if (group.isPresent()) {
|
|
||||||
Bitmap avatar = null;
|
|
||||||
try {
|
|
||||||
avatar = BitmapFactory.decodeStream(AvatarHelper.getAvatar(getContext(), group.get().getRecipientId()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, "Failed to read avatar.");
|
|
||||||
}
|
|
||||||
return Optional.of(new GroupData(groupIds[0],
|
|
||||||
existingContacts,
|
|
||||||
avatar,
|
|
||||||
BitmapUtil.toByteArray(avatar),
|
|
||||||
group.get().getTitle()));
|
|
||||||
} else {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Optional<GroupData> group) {
|
|
||||||
super.onPostExecute(group);
|
|
||||||
|
|
||||||
if (group.isPresent() && !activity.isFinishing()) {
|
|
||||||
activity.groupToUpdate = group;
|
|
||||||
|
|
||||||
activity.groupName.setText(group.get().name);
|
|
||||||
if (group.get().avatarBmp != null) {
|
|
||||||
activity.setAvatar(group.get().avatarBytes, group.get().avatarBmp);
|
|
||||||
}
|
|
||||||
SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(activity, group.get().recipients);
|
|
||||||
adapter.setOnRecipientDeletedListener(activity);
|
|
||||||
activity.listView.setAdapter(adapter);
|
|
||||||
activity.updateViewState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void setAvatar(T model, Bitmap bitmap) {
|
|
||||||
avatarBmp = bitmap;
|
|
||||||
GlideApp.with(this)
|
|
||||||
.load(model)
|
|
||||||
.circleCrop()
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.into(avatar);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GroupData {
|
|
||||||
GroupId.V1 id;
|
|
||||||
Set<Recipient> recipients;
|
|
||||||
Bitmap avatarBmp;
|
|
||||||
byte[] avatarBytes;
|
|
||||||
String name;
|
|
||||||
|
|
||||||
GroupData(GroupId.V1 id, Set<Recipient> recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) {
|
|
||||||
this.id = id;
|
|
||||||
this.recipients = recipients;
|
|
||||||
this.avatarBmp = avatarBmp;
|
|
||||||
this.avatarBytes = avatarBytes;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,7 @@ public final class GroupMembersDialog {
|
|||||||
public void display() {
|
public void display() {
|
||||||
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
|
||||||
.setTitle(R.string.ConversationActivity_group_members)
|
.setTitle(R.string.ConversationActivity_group_members)
|
||||||
.setIconAttribute(R.attr.group_members_dialog_icon)
|
.setIcon(R.drawable.ic_group_24)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setView(R.layout.dialog_group_members)
|
.setView(R.layout.dialog_group_members)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import android.graphics.PorterDuff;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -27,6 +29,7 @@ import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChange
|
|||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
|
||||||
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
import org.thoughtcrime.securesms.contacts.SelectedContact;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
@@ -34,16 +37,17 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
|
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
public class InviteActivity extends PassphraseRequiredActivity implements ContactSelectionListFragment.OnContactSelectedListener {
|
||||||
|
|
||||||
private ContactSelectionListFragment contactsFragment;
|
private ContactSelectionListFragment contactsFragment;
|
||||||
private EditText inviteText;
|
private EditText inviteText;
|
||||||
@@ -63,7 +67,8 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
|
getIntent().putExtra(ContactSelectionListFragment.SELECTION_LIMITS, SelectionLimits.NO_LIMITS);
|
||||||
|
getIntent().putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
|
||||||
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
|
||||||
|
|
||||||
setContentView(R.layout.invite_activity);
|
setContentView(R.layout.invite_activity);
|
||||||
@@ -92,26 +97,40 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
|
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
|
||||||
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
|
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
|
||||||
|
|
||||||
View shareButton = ViewUtil.findById(this, R.id.share_button);
|
View shareButton = findViewById(R.id.share_button);
|
||||||
View smsButton = ViewUtil.findById(this, R.id.sms_button);
|
Button smsButton = findViewById(R.id.sms_button);
|
||||||
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
|
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
||||||
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
|
ContactFilterToolbar contactFilter = findViewById(R.id.contact_filter);
|
||||||
|
|
||||||
inviteText = ViewUtil.findById(this, R.id.invite_text);
|
inviteText = findViewById(R.id.invite_text);
|
||||||
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
|
smsSendFrame = findViewById(R.id.sms_send_frame);
|
||||||
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
|
smsSendButton = findViewById(R.id.send_sms_button);
|
||||||
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||||
|
|
||||||
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
||||||
updateSmsButtonText();
|
inviteText.addTextChangedListener(new AfterTextChanged(editable -> {
|
||||||
|
boolean isEnabled = editable.length() > 0;
|
||||||
|
smsButton.setEnabled(isEnabled);
|
||||||
|
shareButton.setEnabled(isEnabled);
|
||||||
|
smsButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||||
|
shareButton.animate().alpha(isEnabled ? 1f : 0.5f);
|
||||||
|
}));
|
||||||
|
|
||||||
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
|
|
||||||
contactsFragment.setOnContactSelectedListener(this);
|
|
||||||
shareButton.setOnClickListener(new ShareClickListener());
|
|
||||||
smsButton.setOnClickListener(new SmsClickListener());
|
|
||||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||||
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||||
|
|
||||||
|
if (Util.isDefaultSmsProvider(this)) {
|
||||||
|
shareButton.setOnClickListener(new ShareClickListener());
|
||||||
|
smsButton.setOnClickListener(new SmsClickListener());
|
||||||
|
} else {
|
||||||
|
shareButton.setVisibility(View.GONE);
|
||||||
|
smsButton.setOnClickListener(new ShareClickListener());
|
||||||
|
smsButton.setText(R.string.InviteActivity_share);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Animation loadAnimation(@AnimRes int animResId) {
|
private Animation loadAnimation(@AnimRes int animResId) {
|
||||||
@@ -121,13 +140,14 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
|
public boolean onBeforeContactSelected(Optional<RecipientId> recipientId, String number) {
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size() + 1);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSmsInvites() {
|
private void sendSmsInvites() {
|
||||||
@@ -137,12 +157,11 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
.toArray(new SelectedContact[0]));
|
.toArray(new SelectedContact[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSmsButtonText() {
|
private void updateSmsButtonText(int count) {
|
||||||
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
|
|
||||||
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
|
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
|
||||||
selectedContacts.size(),
|
count,
|
||||||
selectedContacts.size()));
|
count));
|
||||||
smsSendButton.setEnabled(!selectedContacts.isEmpty());
|
smsSendButton.setEnabled(count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onBackPressed() {
|
@Override public void onBackPressed() {
|
||||||
@@ -156,17 +175,17 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
private void cancelSmsSelection() {
|
private void cancelSmsSelection() {
|
||||||
setPrimaryColorsToolbarNormal();
|
setPrimaryColorsToolbarNormal();
|
||||||
contactsFragment.reset();
|
contactsFragment.reset();
|
||||||
updateSmsButtonText();
|
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||||
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
|
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPrimaryColorsToolbarNormal() {
|
private void setPrimaryColorsToolbarNormal() {
|
||||||
primaryToolbar.setBackgroundColor(0);
|
primaryToolbar.setBackgroundColor(0);
|
||||||
primaryToolbar.getNavigationIcon().setColorFilter(null);
|
primaryToolbar.getNavigationIcon().setColorFilter(null);
|
||||||
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.title_text_color_primary));
|
primaryToolbar.setTitleTextColor(ContextCompat.getColor(this, R.color.signal_text_primary));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
getWindow().setStatusBarColor(ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
|
WindowUtil.setStatusBarColor(getWindow(), ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
|
||||||
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
|
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
|
||||||
WindowUtil.setLightStatusBarFromTheme(this);
|
WindowUtil.setLightStatusBarFromTheme(this);
|
||||||
}
|
}
|
||||||
@@ -176,11 +195,11 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
|||||||
|
|
||||||
private void setPrimaryColorsToolbarForSms() {
|
private void setPrimaryColorsToolbarForSms() {
|
||||||
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
|
primaryToolbar.getNavigationIcon().setColorFilter(ContextCompat.getColor(this, R.color.signal_text_toolbar_subtitle), PorterDuff.Mode.SRC_IN);
|
||||||
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
|
primaryToolbar.setTitleTextColor(ContextCompat.getColor(this, R.color.signal_text_toolbar_title));
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
|
WindowUtil.setStatusBarColor(getWindow(), ContextCompat.getColor(this, R.color.core_ultramarine));
|
||||||
WindowUtil.clearLightStatusBar(getWindow());
|
WindowUtil.clearLightStatusBar(getWindow());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in our {@link BuildConfig} to tie together the various attributes of a KBS instance. This
|
||||||
|
* is sitting in the root directory so it can be accessed by the build config.
|
||||||
|
*/
|
||||||
|
public final class KbsEnclave {
|
||||||
|
|
||||||
|
private final String enclaveName;
|
||||||
|
private final String serviceId;
|
||||||
|
private final String mrEnclave;
|
||||||
|
|
||||||
|
public KbsEnclave(@NonNull String enclaveName, @NonNull String serviceId, @NonNull String mrEnclave) {
|
||||||
|
this.enclaveName = enclaveName;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.mrEnclave = mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getMrEnclave() {
|
||||||
|
return mrEnclave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getEnclaveName() {
|
||||||
|
return enclaveName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull String getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
KbsEnclave enclave = (KbsEnclave) o;
|
||||||
|
return enclaveName.equals(enclave.enclaveName) &&
|
||||||
|
serviceId.equals(enclave.serviceId) &&
|
||||||
|
mrEnclave.equals(enclave.mrEnclave);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(enclaveName, serviceId, mrEnclave);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply logs out lifecycle events.
|
||||||
|
*/
|
||||||
|
public abstract class LoggingFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(LoggingFragment.class);
|
||||||
|
|
||||||
|
public LoggingFragment() { }
|
||||||
|
|
||||||
|
public LoggingFragment(@LayoutRes int contentLayoutId) {
|
||||||
|
super(contentLayoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
logEvent("onCreate()");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
logEvent("onStart()");
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
logEvent("onStop()");
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
logEvent("onDestroy()");
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logEvent(@NonNull String event) {
|
||||||
|
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,65 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
|
||||||
public class MainActivity extends PassphraseRequiredActionBarActivity {
|
public class MainActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
|
public static final int RESULT_CONFIG_CHANGED = Activity.RESULT_FIRST_USER + 901;
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final MainNavigator navigator = new MainNavigator(this);
|
private final MainNavigator navigator = new MainNavigator(this);
|
||||||
|
|
||||||
|
public static @NonNull Intent clearTop(@NonNull Context context) {
|
||||||
|
Intent intent = new Intent(context, MainActivity.class);
|
||||||
|
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
|
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||||
super.onCreate(savedInstanceState, ready);
|
super.onCreate(savedInstanceState, ready);
|
||||||
setContentView(R.layout.main_activity);
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
navigator.onCreate(savedInstanceState);
|
navigator.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
handleGroupLinkInIntent(getIntent());
|
||||||
|
handleProxyInIntent(getIntent());
|
||||||
|
|
||||||
|
CachedInflater.from(this).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getIntent() {
|
||||||
|
return super.getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
handleGroupLinkInIntent(intent);
|
||||||
|
handleProxyInIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -30,6 +72,9 @@ public class MainActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicTheme.onResume(this);
|
dynamicTheme.onResume(this);
|
||||||
|
if (SignalStore.misc().isOldDeviceTransferLocked()) {
|
||||||
|
OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,7 +84,29 @@ public class MainActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == MainNavigator.REQUEST_CONFIG_CHANGES && resultCode == RESULT_CONFIG_CHANGED) {
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public @NonNull MainNavigator getNavigator() {
|
public @NonNull MainNavigator getNavigator() {
|
||||||
return navigator;
|
return navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleGroupLinkInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleProxyInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialProxyLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||