Compare commits

..
Author SHA1 Message Date
gdlbo ee1153b909 cached playlists dialog in settings 2024-03-18 17:40:25 +03:00
gdlbo 9e549079b4 fixed gzip 2024-03-18 17:13:05 +03:00
gdlbo 673660f610 reduced fetching thumbs 2024-03-18 16:20:32 +03:00
gdlbo 1865392a65 thumbs caching
also some catalog fixes
2024-03-18 16:09:42 +03:00
gdlbo d52d9f594c fixed playlist empty thumb crash 2024-03-18 14:22:28 +03:00
gdlbo b1fbe458f8 added count of tracks in playlist 2024-03-18 13:52:03 +03:00
gdlbo 4070ad3a03 fixed playlists files deletion 2024-03-18 13:39:28 +03:00
gdlbo 80c317120e Update CatalogJsonInjector.java 2024-03-18 13:21:32 +03:00
gdlbo c4cea050da Merge branch 'main' into playlist_caching 2024-03-18 10:38:46 +03:00
gdlbo 6998bdba1c added new block to remove
cyz unsupported
2024-03-18 10:38:33 +03:00
gdlbo 444887a191 playlist db remover 2024-03-18 10:14:32 +03:00
gdlbo 9bfc39c0f1 add to db from dl success 2024-03-18 09:55:40 +03:00
gdlbo 9ec443cde0 migration from old base 2024-03-18 09:47:38 +03:00
gdlbo 68270f8afb resolved conflicts 2024-03-18 09:16:03 +03:00
gdlbo b8d7330398 Merge branch 'main' into playlist_caching 2024-03-18 09:10:10 +03:00
aliveoutside 5aa0fe9959 Merge remote-tracking branch 'sunmisc/main' #1554 2024-03-18 03:17:30 +03:00
gdlbo c79b419f5e init
WIP

1. Make migration from old base to new
2. Remove track with playlist (not only db deletion)
3. Make download one track add to db
2024-03-17 19:22:57 +03:00
Artur Skubei ba3f9b9afe Merge branch 'main' into playlist_caching 2024-03-17 14:26:37 +03:00
gdlbo 88f0939f04 StrictMode corrections 2024-03-17 12:10:22 +03:00
Vologhat 73653fa2e0 optimize getting of global app context (#1583) 2024-03-16 02:24:51 +03:00
gdlbo c812b13446 fixed cold start 2024-03-14 08:52:01 +03:00
gdlbo ea9291a5eb fixed background menu in nonmilk 2024-03-13 12:55:32 +03:00
gdlbo 8d93e298b4 fixed default value of undelete prefix 2024-03-13 12:42:41 +03:00
gdlbo 49d448ad99 added some null checks 2024-03-13 12:30:27 +03:00
gdlbo 6bcc278dee fixed progress icon for downloaders 2024-03-12 15:30:17 +03:00
gdlbo cff0a98227 fixed download video notification 2024-03-12 15:14:01 +03:00
gdlbo 3529eb7ba1 moved metadataSeparator to MusicFragment 2024-03-12 15:08:25 +03:00
gdlbo 2ee4bf1107 new switch frag solution 2024-03-12 14:26:59 +03:00
gdlbo ac43a7794b fix #1260 2024-03-12 14:24:30 +03:00
gdlbo 47b2519ca5 fixed #1471 2024-03-12 13:25:16 +03:00
aliveoutside 701674ce45 rollback accidentally changed ndk version 2024-03-11 10:18:17 +03:00
aliveoutside e19a005362 Merge branch 'feat/java-mpeg-demux'
# Conflicts:
#	app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
#	smali/res/xml/preferences_media.xml
2024-03-11 10:10:37 +03:00
gdlbo e11e97cd29 added download notification for Video Downloader 2024-03-08 20:04:37 +03:00
gdlbo ab2715456f fixed freeze download video 2024-03-08 19:07:28 +03:00
gdlbo 68526f0ba2 added encryptor name in decrypt method 2024-03-08 14:37:35 +03:00
gdlbo b3243f1d41 removed compression in fb lib 2024-03-07 22:17:01 +03:00
gdlbo 175866ddc5 fixed crash and photo upload 2024-03-07 21:38:29 +03:00
gdlbo f691f8cc9e updated rounding dialog 2024-03-07 20:01:51 +03:00
gdlbo 305614dae6 fix #1581 2024-03-07 18:05:20 +03:00
gdlbo 4bbcde750a fixed execute.getAudioServicePackages 2024-03-07 16:50:38 +03:00
gdlbo e159b4f594 moved native to main inj 2024-03-04 19:31:46 +03:00
gdlbo 204b0c6e2c moved dockbar editor to main settings 2024-03-03 16:13:45 +03:00
gdlbo 522f6cd42b removed useless cache buttons 2024-03-03 09:53:02 +03:00
gdlbo 7dd9e5ff3a more gzip support 2024-03-02 23:33:18 +03:00
Artur Skubei 07f8542f56 SponsorPost vote system (#1562)
also added gzip support for all methods
2024-03-02 22:24:15 +03:00
Artur Skubei 6b1e6f07c5 Internal VK Proxy Support (#1579)
thanks @awers24 for tests
2024-03-02 21:47:34 +03:00
gdlbo 5466dd72dd some lastfm fixes 2024-03-01 22:50:31 +03:00
Artur Skubei 5ac3b9cf6e Another ndk fix (#1572) 2024-02-25 22:07:42 +03:00
Artur Skubei de6edc795a Update build.yml 2024-02-25 21:46:27 +03:00
Artur Skubei 19d36272cb ndk fix (#1571) 2024-02-25 21:43:08 +03:00
gdlbo d2fe435ad2 added music fragment
migration from media content
2024-02-25 19:47:23 +03:00
gdlbo b6e7d24979 Update VTVerifications.java 2024-02-20 11:48:35 +03:00
gdlbo d10d5eca4b fixed auto updating filters 2024-02-19 15:39:41 +03:00
gdlbo 67f1477907 Revert "improve code readability, remove redundant constructions"
This reverts commit b05a239b90.
2024-02-19 11:38:11 +03:00
gdlbo 18ef4eda21 Revert "rollback to for-loops with improvements"
This reverts commit 84cf9ba10a.
2024-02-19 11:38:10 +03:00
Tahgolov 84cf9ba10a rollback to for-loops with improvements 2024-02-18 23:07:19 +03:00
Tahgolov b05a239b90 improve code readability, remove redundant constructions 2024-02-18 23:05:31 +03:00
gdlbo 8959558c64 Fixed 5.1 false coloring and improved coloring speed/app performance (#1566) 2024-02-18 14:52:22 +03:00
gdlbo e19ed8845d fix #1343 2024-02-18 02:08:16 +03:00
gdlbo b5ae2662db fix noad 2024-02-17 22:27:56 +03:00
aliveoutside 1a7a0e7f4f recover accidentally deleted vk ffmpeg libs 2024-02-17 15:02:05 +03:00
aliveoutside 953aa790e3 fix typo in logging 2024-02-16 23:51:14 +03:00
aliveoutside c1de68893c implement id3v2.3 tagging 2024-02-16 21:23:57 +03:00
aliveoutside e0d3f22f16 improve demuxer performance
use buffered io for input and output streams
reuse single bytebuffer instead of creating it for every packet
2024-02-16 06:32:53 +03:00
gdlbo d4710663ff disabler force traffic saver v2 2024-02-16 03:58:30 +03:00
aliveoutside 60e32df046 use java demuxer to extract mp3 from .ts files instead of ffmpeg 2024-02-16 01:13:39 +03:00
sunmisc bbc4aa98f7 exception is passed to onFailure 2024-02-15 18:49:14 +03:00
sunmisc 287af14589 transfer audio download to ffmpeg 2024-02-15 16:36:29 +03:00
gdlbo 18f40e0da9 fixed useless false visibility vkZ
ofc jk (vkx)
2024-02-14 23:12:20 +03:00
gdlbo 9ff8c1d9fe refactored FoafBase
added check for vk messenger accounts
rewritten all legacy code
2024-02-14 23:03:19 +03:00
gdlbo 91440e4f8b refactored VTVerifs
speedup processIds + removed getting 1337 verifs (depreciated)
2024-02-14 22:13:22 +03:00
gdlbo a195e05d7b refactored sig checker
support for new android versions
2024-02-14 22:10:59 +03:00
gdlbo 8866bf90c7 refactored webview injector
speedup by runtime injection evaluateJavascript
2024-02-14 22:10:33 +03:00
gdlbo eab5b7074e refactored superapp utils
removed old vk pay and events
2024-02-14 21:45:31 +03:00
gdlbo bdee6c08c0 disable events in superapp 2024-02-14 21:16:41 +03:00
gdlbo 0744613aa0 Update preferences_debug.xml 2024-02-14 19:31:02 +03:00
gdlbo f6de4c5bc9 some toggles fixes
probably may fixed slow messages load
fixed default feed
2024-02-14 19:11:16 +03:00
gdlbo f59e93aab6 fix #1551 2024-02-14 19:03:23 +03:00
gdlbo 2d97341886 video-covers disabler 2024-02-14 17:53:05 +03:00
gdlbo b247c5c090 some config network fixes 2024-02-14 17:06:35 +03:00
gdlbo fd6e866f6f fixed bug nonmilk
Reported by https://4pda.to/forum/index.php?showuser=1502902
2024-02-14 16:45:45 +03:00
gdlbo 780ebce3ff disable libverify by default
2fa fix
2024-02-12 12:31:06 +03:00
sunmisc 404594df87 unnecessary atomic 2024-02-10 15:11:30 +03:00
gdlbo 2a85b3a4f0 fixes for whitelists 2024-02-09 15:41:18 +03:00
gdlbo 413701d28e SponsorPost summ 2024-02-07 16:40:54 +03:00
gdlbo 016bd342c4 added summary for sponsorpost 2024-02-07 16:33:54 +03:00
gdlbo d04b2249f0 enable metadata by default 2024-02-07 16:07:59 +03:00
gdlbo 8eb7ded6ba fixed autoupdate filters 2024-02-07 15:49:04 +03:00
gdlbo 1f9c6fd80c added disabling sponsorpost 2024-02-07 15:47:05 +03:00
gdlbo 4ec84e3754 cleanup build gradle 2024-02-07 14:58:06 +03:00
gdlbo 3b8af6e477 Merge branch 'main' into main 2024-02-07 13:57:33 +03:00
gdlbo ad3f70f4ac Sponsorpost integration (#1553) 2024-02-07 13:55:53 +03:00
gdlbo 611f4d3159 updated run configs 2024-02-06 18:37:30 +03:00
aliveoutside cb38660a3a code reformat 2024-02-05 21:37:18 +03:00
aliveoutside f6f0fb3d08 fix onSuccess not being called on finish 2024-02-05 21:15:10 +03:00
sunmisc dab50535ef optimize 2024-02-05 18:28:28 +03:00
sunmisc d9300fc621 may have fixed the race on CIPHER.init 2024-02-05 18:07:23 +03:00
sunmisc 48eba3ccbb the track is already being downloaded in another thread 2024-02-05 16:07:24 +03:00
sunmisc 2336da53be immediately concatenate bytes without creating files 2024-02-05 15:22:45 +03:00
mexanoz 7319714140 fix setting metadata for certain tracks (#1507) 2024-02-04 15:10:34 +03:00
gdlbo 857db4879a vkui theming fixes
thx @awers24
2024-02-02 14:27:35 +03:00
gdlbo b5db820733 new feature
useless
2024-02-01 23:40:10 +03:00
gdlbo c668105003 some translations update 2024-01-31 11:46:46 +03:00
gdlbo dd6bbcbc2c fix auto translation 2024-01-31 11:42:07 +03:00
gdlbo 4ed0749027 Revert "Profiles/Groups resp fixes (#1532)"
This reverts commit a6b56e0e87.
2024-01-31 03:22:06 +03:00
gdlbo 3b834c2688 Update Genius.java 2024-01-31 01:56:10 +03:00
gdlbo 0c5a40bedc enable stories 2024-01-31 00:46:57 +03:00
gdlbo 8146b863b1 fix crash 2024-01-30 12:21:53 +03:00
gdlbo c64f8a1b2d disabler force traffic saver 2024-01-30 11:47:08 +03:00
gdlbo dd6916c08a removed random proxy 2024-01-30 11:35:49 +03:00
gdlbo 58c14fbd4f added all server features disabler for users who doesnt have unlimited internet 2024-01-30 11:28:38 +03:00
gdlbo 3b830f5e2e added fragile user data pref 2024-01-30 11:08:19 +03:00
gdlbo c304313be9 updated deps 2024-01-13 19:14:51 +03:00
Denis 3068ff76c1 Fix crash (#1542) 2023-12-31 13:04:55 +03:00
gdlbo a6b56e0e87 Profiles/Groups resp fixes (#1532) 2023-12-13 23:08:32 +03:00
gdlbo 5360ca6c5e updated translations 2023-12-13 23:07:40 +03:00
gdlbo f2ac845797 Update FFMpeg.java 2023-10-14 16:05:51 +03:00
gdlbo a85e55a594 meta data pref 2023-10-12 22:45:43 +03:00
gdlbo e2775db4e5 fixed english translation 2023-10-12 20:19:17 +03:00
gdlbo 589e8928a0 Bitrate upgrade (#1498)
libvkmedia.so armv7/arm64
2023-10-12 19:57:00 +03:00
gdlbo cd189b1dda fixed id3v2 meta 2023-10-10 23:15:11 +03:00
gdlbo 14c111a3e6 some music fixes 2023-10-10 22:53:16 +03:00
gdlbo b506c56346 fixed compiling issue 2023-10-07 03:13:24 +03:00
gdlbo bbbc94e510 added facebook graph disabler 2023-10-07 03:07:31 +03:00
gdlbo 8cb2c380e7 fixed adding "null" to track name 2023-10-06 12:44:55 +03:00
gdlbo b9879f9ed7 updated appcenter 2023-10-04 19:44:24 +03:00
gdlbo cf05e051e8 added summary for advanced online info 2023-10-04 13:47:47 +03:00
gdlbo 963c4d81ba upped ver 2023-10-04 13:37:20 +03:00
gdlbo 099c699702 added vkn online fix disabler 2023-10-04 13:37:07 +03:00
gdlbo 1117fc19d4 added force tablet mode 2023-10-02 16:28:08 +03:00
gdlbo 78dca26063 fix #1489 2023-10-02 14:52:52 +03:00
gdlbo f5180f61d5 fixed empty text from ssfs 2023-09-30 14:37:49 +03:00
gdlbo 540fffdcc9 fixed download issue 2023-09-30 03:15:14 +03:00
gdlbo 5357c60eaa Update TelegramStickersGrabber.java 2023-09-30 02:33:52 +03:00
gdlbo c59f29121f Refactoted Telegram Stickers downloader and settings (#1485) 2023-09-29 19:58:33 +03:00
gdlbo bd38cab377 autoupdated disabler 2023-09-28 17:40:49 +03:00
gdlbo 0058431590 deleted useless summ and updated translation 2023-09-28 17:01:38 +03:00
gdlbo bf4cf2be53 adfree + free from server connection (#1481) 2023-09-28 16:10:30 +03:00
gdlbo aac86a9952 Update InstallGMSFragment.java
added inotia00 microg information
2023-09-28 11:09:40 +03:00
gdlbo ae00e72bac added more microg instructions 2023-09-27 23:55:56 +03:00
gdlbo fea984f6fd fixed null foaf crash app 2023-09-27 23:34:37 +03:00
gdlbo d870c2f984 Update VTSettings.java 2023-09-27 22:12:12 +03:00
gdlbo 66e2ae5b93 conflicts resolved 2023-09-25 15:17:29 +03:00
gdlbo af2296e8ff Merge branch 'main' into playlist_caching 2023-09-25 15:05:42 +03:00
gdlbo f7f43928cf cleanup and refactor code
also fixed ts merging
2023-09-25 14:44:27 +03:00
Vologhat 1accb45f12 Merge remote-tracking branch 'origin/main' 2023-09-24 18:43:40 +03:00
Vologhat d9f53920e5 optimize music cache logic 2023-09-24 18:39:12 +03:00
gdlbo 76d91332d7 added recommendation icons 2023-09-23 16:04:45 +03:00
gdlbo 728cbde683 added xiaomi battery dozing information 2023-09-23 14:58:13 +03:00
gdlbo 60c98cd998 some settings improves 2023-09-23 14:50:18 +03:00
gdlbo f1d106e0f0 added gms info in device info
installed microg/gms and push service
2023-09-23 11:17:43 +03:00
gdlbo c92194e90b app.revanced.android.gms support (#1474) 2023-09-23 10:53:05 +03:00
gdlbo 91683af807 deleted useless file 2023-09-23 01:50:43 +03:00
gdlbo c5c898aa4e added recommendations for app fixes + added more gmsless instructions 2023-09-23 01:22:13 +03:00
gdlbo 25634b2469 cache info/clean improvements 2023-09-22 23:27:08 +03:00
gdlbo b8e764d09d Update AboutAppFragment.smali 2023-09-15 19:31:09 +03:00
gdlbo 17ed32857a fixed information about cache settings 2023-09-15 19:05:34 +03:00
gdlbo d631d07e0f removed useless update toast 2023-09-15 19:03:14 +03:00
gdlbo 2ec4c69832 added "Data and storage" item to settings 2023-09-15 18:40:38 +03:00
abslike 6426c965c6 Fix button black text in bots 2023-09-15 18:38:27 +03:00
gdlbo 433811531a Merge branch 'main' into playlist_caching 2023-09-15 18:13:33 +03:00
gdlbo 4f6d410185 stories disabled by default 2023-09-15 18:09:29 +03:00
gdlbo 18a899f439 updated and refactored vt settings 2023-09-15 18:08:28 +03:00
gdlbo 7e5b3fe233 superapp audio item fix 2023-09-14 12:08:16 +03:00
gdlbo 4abd0eec6c fix compile 2023-09-14 11:28:08 +03:00
gdlbo cf17ed64c7 fixed invert in cached playlist 2023-07-30 16:49:30 +03:00
gdlbo a2dd7c1e06 fixed strange code 2023-07-29 17:06:12 +03:00
gdlbo 021fe66bec invert cached tracks list 2023-07-29 16:48:30 +03:00
gdlbo 1b3896f4ca fix colors issue 2023-07-28 19:01:57 +03:00
gdlbo fc9ccf66d1 fix #1409 2023-07-28 18:49:11 +03:00
Vologhat d205f2894b rewrite StickersFragment.java
- move add tgs action to toolbar
- some optimizations
2023-07-14 12:53:57 +03:00
Vologhat 7afc04b2b2 fix tgs packs download 2023-07-14 12:48:45 +03:00
Vologhat 584ee96ee0 fix #1406 2023-07-14 12:37:50 +03:00
gdlbo 66f7ea1b79 Update VTVerifications.java 2023-07-11 21:47:53 +03:00
gdlbo 7b0a83458b Update README.md 2023-07-08 18:03:41 +03:00
gdlbo cdaf7909d4 fix old android crash accenting 2023-07-04 23:23:18 +03:00
gdlbo c70ab388c1 fixed account saving 2023-07-04 22:55:22 +03:00
gdlbo 50c66c2703 fix music metrics 2023-07-04 19:56:12 +03:00
gdlbo 357e520bb0 some code fixes 2023-07-04 18:57:06 +03:00
gdlbo 6379ce5741 cleanup code 2023-07-04 13:42:04 +03:00
Vologhat 02807739a1 fix #1394 2023-07-02 22:32:26 +03:00
gdlbo 40635ab75a fix #1388 2023-07-02 13:44:47 +03:00
gdlbo 32e1bfe6fd fix messages color accent 2023-07-02 00:04:10 +03:00
gdlbo 281a0e8499 updated translations 2023-07-01 18:45:45 +03:00
gdlbo 220c61afe1 Update preferences_themes.xml 2023-07-01 18:33:46 +03:00
Vologhat 7a632d5224 Update preferences_themes.xml 2023-07-01 18:31:23 +03:00
Vologhat 3218c1a46b add optionally theme cache invalidation every update 2023-07-01 18:29:47 +03:00
Vologhat b2028d86ba add optionally theme cache invalidation every update 2023-07-01 18:24:42 +03:00
gdlbo 45ed5879e6 Update build.gradle 2023-07-01 17:46:06 +03:00
gdlbo 48479dd064 removed old color engine prefs 2023-07-01 17:33:45 +03:00
gdlbo 3b556b81a1 Update StandartFilter.txt 2023-07-01 17:33:05 +03:00
gdlbo 526c9a607a auto refactor imports 2023-07-01 16:42:33 +03:00
Vologhat 81752895ab Revert "auto refactor code"
This reverts commit ad970d097a.
2023-07-01 16:39:36 +03:00
gdlbo ad970d097a auto refactor code 2023-07-01 16:38:42 +03:00
gdlbo bc2010b49d gradle downgrade 2023-07-01 16:34:27 +03:00
Vologhat ff4a4094ff add type validating of resource value 2023-07-01 16:15:20 +03:00
abslike 3629e71098 Update monet dark and amoled theme (nomilkshake) (#1378) 2023-06-27 22:21:45 +03:00
Vologhat b955ac355f fix #1386 2023-06-27 22:17:43 +03:00
Vologhat 409279eca0 fix apk path building 2023-06-27 17:32:28 +03:00
Vologhat ea0a0f1d93 Optimize build scripts
- rewrite the"exportDex" task group
- move  the logic of deleting temporary files into a gradle script
- fix "APPCENTER_KEY" property defining while debug building
2023-06-27 16:42:07 +03:00
gdlbo 3cc81dfddc fix #1376 2023-06-19 05:06:22 +03:00
gdlbo 86041e6031 invert exceptions filters 2023-06-19 01:40:22 +03:00
gdlbo e140ce2661 fixed monet msg colors 2023-06-19 00:46:27 +03:00
gdlbo f6f15132a2 Update build.yml 2023-06-18 18:35:53 +03:00
gdlbo e73751ecf3 Update build.yml 2023-06-18 18:26:12 +03:00
gdlbo 3c0516b4cd cleanup ThemesUtils.java 2023-06-18 18:20:48 +03:00
gdlbo 38d9f4a818 update gradle 2023-06-18 18:20:25 +03:00
gdlbo 207fa4f2e7 fixed memories vkui 2023-06-18 02:38:39 +03:00
abslikeandabslike f9f778b8f4 Updating light nonmilkshake theme (#1374)
Co-authored-by: abslike <abslike@vk.com>
2023-06-17 21:46:13 +03:00
gdlbo 962dcdec67 fixed search engine issue 2023-06-16 22:45:02 +03:00
gdlbo 486026b8aa updated translations 2023-06-14 17:36:34 +03:00
gdlbo e78d08fb94 added frag title for data sett 2023-06-12 16:32:47 +03:00
gdlbo a5e81dbd87 some data sett updated 2023-06-12 16:30:22 +03:00
gdlbo 49051dae9a added filtered buttons for profile #767 2023-06-12 15:28:37 +03:00
gdlbo 94236c87e3 fix incorrect sig 2023-06-10 20:32:06 +03:00
gdlbo 449ba8c768 cleanup NewsFeedFiltersUtils.java 2023-06-10 20:08:28 +03:00
gdlbo 0da81c01e9 fixed monet theme applying 2023-06-10 20:08:13 +03:00
gdlbo b4a0b7e1ea Fixed false filtering of users' own messages 2023-06-10 18:06:13 +03:00
abslikeandabslike f2209eca49 Updating Light and Dark milkshake monet theme (#1359)
Co-authored-by: abslike <abslike@vk.com>
2023-06-10 15:08:13 +03:00
gdlbo 4a004971c6 Update CatalogJsonInjector.java 2023-05-30 19:25:30 +03:00
gdlbo e400b8761c fixed false injection cache playlist in old audio catalog 2023-05-30 17:17:59 +03:00
Vologhat ff8e7b7f41 fix incorrect displaying of selected image searcher 2023-05-30 12:31:21 +03:00
gdlbo 2c501606be amoled vkui issues fix 2023-05-28 18:02:09 +03:00
gdlbo 73420fe1c4 updated vkui injection for new versions 2023-05-28 15:50:11 +03:00
Vologhat 337f81346c replace microsoft icon to bing icon 2023-05-27 20:13:04 +03:00
Vologhat 06f9a4545b added sauceNAO and trace.moe icons 2023-05-27 14:36:09 +03:00
gdlbo fa99df93c4 refactoring some code 2023-05-26 21:03:23 +03:00
Vologhat adcd2472d5 rewrite and fix PhotoViewer hook
- optimize code
- create SearchEngine enum for image searchers
- fix "search photo" on non-monet theme
- fix icon colors in photo viewer context menu
2023-05-26 16:46:43 +03:00
gdlbo 7527b9a07e added update button for all non-dev users 2023-05-26 14:47:49 +03:00
gdlbo 800c223baa fixed dropdown monet icons in menu 2023-05-26 14:37:06 +03:00
gdlbo d1123d3ccf Update MediaNative.java 2023-05-25 23:18:15 +03:00
gdlbo f61fc99d9c Revert "upgraded voice bitrate"
This reverts commit 64303b1709.
2023-05-25 23:04:09 +03:00
gdlbo 60653cc739 medianative converted to java 2023-05-25 23:03:00 +03:00
gdlbo d25bba10b0 fixed profiles crash 2023-05-25 17:06:48 +03:00
gdlbo 3b67ecaa5e deleted voip libs
-10 mb
2023-05-25 17:06:29 +03:00
gdlbo 6600f32392 some settings update 2023-05-25 16:48:04 +03:00
gdlbo 64303b1709 upgraded voice bitrate
opus_encoder_ctl

16000 -> 48000
2023-05-24 20:34:57 +03:00
gdlbo 5c7e50d61e delete pin fix
good fix
2023-05-24 15:18:39 +03:00
gdlbo f68ad105a6 Update Preferences.java 2023-05-24 01:32:02 +03:00
gdlbo 164479da1d disabled profiles/groups rename 2023-05-24 01:00:53 +03:00
gdlbo d93bf41d28 fixed discover bug 2023-05-23 23:43:23 +03:00
gdlbo 38a2b3f2b2 cleanup banner msg code 2023-05-23 23:17:20 +03:00
gdlbo 342534676c fixed another strange issue with banners 2023-05-23 23:08:53 +03:00
gdlbo 56a6a6de03 fixed messages issue 2023-05-23 23:03:10 +03:00
gdlbo d1445c2801 disable language change for miui
https://xiaomi.eu/community/threads/miui-14-per-app-language-support.69022

china number one!!!!!!!!!
2023-05-23 22:28:13 +03:00
gdlbo 6d3ddede38 Update WebViewColoringUtils.java 2023-05-22 19:01:39 +03:00
gdlbo 08dae9b342 Experimental proguard config with hooks refactoring (#1323)
fixed telegram stickers issue
speedup webview coloring
refactored VKUIHook
fixed duplication of eruda
added a check for duplication of adding styles for coloring vkui
2023-05-22 18:48:15 +03:00
gdlbo 8244b31631 fixed issue with aidl 2023-05-21 03:10:23 +03:00
gdlbo 8b29870df9 fixed vk classes crash 2023-05-21 02:57:07 +03:00
gdlbo ab1d0bde0d Update TelegramStickersGrabber.java 2023-05-21 00:59:40 +03:00
gdlbo 782f8a881d updated desugar runtime library 2023-05-21 00:58:39 +03:00
gdlbo b712630809 fixed logging issues 2023-05-21 00:58:19 +03:00
gdlbo b6698287a0 Update proguard-rules.pro 2023-05-21 00:21:59 +03:00
aliveoutside 1b5bfb0ea6 daily mix fix 2023-05-20 19:02:01 +03:00
gdlbo 59d5b83d7b fixed catalog injectors issues
cache playlist fixed
daily mix fixed
2023-05-20 16:32:33 +03:00
gdlbo 9c127931cf updated translations 2023-05-20 14:47:29 +03:00
gdlbo 6f552ee0cb some code cleanup 2023-05-20 14:36:47 +03:00
gdlbo ae1520e189 added logs shrink for release builds 2023-05-20 13:52:43 +03:00
gdlbo 8fae9e952f updated gradle version 2023-05-20 13:52:00 +03:00
gdlbo e6047ffe8a updated appcenter libs 2023-05-20 13:51:26 +03:00
gdlbo b987643ad7 Update arrays.xml 2023-05-19 20:19:43 +03:00
gdlbo b93ce2dd95 fixed issues with translation 2023-05-19 20:18:23 +03:00
gdlbo 1c845c5b34 added more translations 2023-05-19 20:13:51 +03:00
Denis c4af19432d Add compression types translation (#1312) 2023-05-19 19:49:23 +03:00
gdlbo 0c289f8bbb added support for many attachments 10+ (#1311) 2023-05-19 19:34:30 +03:00
Saoqq c68d424b59 Update ffmpeg libs (#1057)
* Update ffmpeg libs

* Bring back metadata writing
2023-05-17 17:45:45 +03:00
aliveoutside 46bac84276 fix daily mix title in playlist view 2023-05-17 11:39:50 +03:00
aliveoutside 16260c5666 temp daily mix fix 2023-05-17 10:55:02 +03:00
gdlbo 594f24d640 Refactored issues with photo compression (#1300)
Added new ways of compression
Added photo copy button
2023-05-15 21:28:45 +03:00
Denis 0deafb5606 Update features.md - editing the list of functions (#1297) 2023-05-15 12:19:27 +03:00
gdlbo 2e3b45a88f Revert "Multithreading verifs downloading"
This reverts commit 0e94efc4f7.
2023-05-14 17:30:14 +03:00
gdlbo 5a7a628117 Revert "added multithread download for some large methods"
This reverts commit 34a0615003.
2023-05-14 17:30:14 +03:00
gdlbo 2d96619c67 Create Multithreading.java 2023-05-13 15:43:29 +03:00
gdlbo 34a0615003 added multithread download for some large methods 2023-05-13 15:07:27 +03:00
gdlbo 0e94efc4f7 Multithreading verifs downloading 2023-05-13 14:38:29 +03:00
gdlbo a11a88e23f moved debug activation to long click of logo/appver 2023-05-12 22:22:27 +03:00
gdlbo 72eb342b98 updated version.properties parsing + cleanup code 2023-05-12 22:00:09 +03:00
gdlbo edb011ccbf Update About.java 2023-05-12 21:19:08 +03:00
gdlbo 8b32e16428 check_for_slow_speed (#1293) 2023-05-12 19:51:27 +03:00
gdlbo d4b457b01d fixed detection admin for kick button 2023-05-12 19:50:48 +03:00
Denis 3d61780240 Sort features in README.md (#1294) 2023-05-12 16:01:09 +03:00
gdlbo 44092032ca Update Requests.java 2023-05-10 19:47:00 +03:00
gdlbo 2800ccdaea refactored dnr/dnt modules 2023-05-10 19:40:33 +03:00
gdlbo a0cce72c64 kick button for admins in chats 2023-05-10 18:01:19 +03:00
gdlbo cbca43c929 Update README.md 2023-05-10 14:14:56 +03:00
gdlbo 0324c9edb7 updated docs for building 2023-05-10 14:13:17 +03:00
Denis c7d34ece2f added features in features list and sorting (#1119)
* added feature text feature

* Add new features in list

* fix text

* Update features.md

* Add dev feature "sync account" and backup settings

* Update - Add genius

* Update features.md - delete unreleased features
2023-05-09 19:38:34 +03:00
gdlbo 0f5ccfd5ba fix empty playlist adding to offline catalog 2023-05-08 21:25:15 +03:00
gdlbo 38d775a538 init 2023-05-08 21:14:49 +03:00
gdlbo 681dd177b8 fixed offline genius issue 2023-05-07 12:09:18 +03:00
gdlbo 4405b04285 removed useless conv bar recomms 2023-05-06 23:40:04 +03:00
gdlbo 3c4e981839 fixed lyrics for genius 2023-05-06 23:27:30 +03:00
gdlbo e318634e56 fix some genius issues 2023-05-06 22:49:12 +03:00
aliveoutside 5ea515f6f4 fetch the lyrics from genius even when vk didn't provide them 2023-05-06 19:23:22 +03:00
gdlbo 22cd1a8e22 some audio code cleanup 2023-05-06 12:24:37 +03:00
gdlbo ba0169cb4c fixed thumbnails in online cache catalog and shuffle 2023-05-06 11:51:23 +03:00
gdlbo 5f0458c587 refactored cache tracklist injector and added shuffle button 2023-05-06 11:03:40 +03:00
gdlbo 7b840a1721 added app category for oreo+ 2023-05-05 23:00:07 +03:00
gdlbo 78036bb3db deleted snet 2023-05-05 22:59:24 +03:00
gdlbo da52a7557f cleanup code 2023-05-05 20:48:39 +03:00
Vologhat 6eae209d04 fixed building drm key url and AES-128 decoding 2023-05-05 20:48:39 +03:00
Vologhat 683f0346e2 added null-check of base uri,fixed filename comparator 2023-05-05 20:48:39 +03:00
gdlbo c95b4e7f86 added new cache insert track method 2023-05-05 20:48:39 +03:00
Vologhat 64c55d4b01 init 2023-05-05 20:48:39 +03:00
gdlbo cc55d5ddaa new btn injector for groups 2023-04-29 19:24:55 +03:00
gdlbo 522290e4a5 Update GroupsCatalogInjector.java 2023-04-28 22:26:38 +03:00
gdlbo 78191b716a Update GroupsCatalogInjector.java 2023-04-28 22:16:20 +03:00
gdlbo dcf14266f2 cleanup catalog json injector 2023-04-28 22:11:53 +03:00
gdlbo a295e11a9d fixed issues with music 2023-04-28 21:58:14 +03:00
gdlbo 67ba4319f8 fixed groups fix for catalogs 2023-04-28 21:30:33 +03:00
aliveoutside f4d4c622eb fix #1235 2023-04-28 21:19:50 +03:00
gdlbo 0d61904b19 Update GmsUtils.java 2023-04-26 19:41:22 +03:00
gdlbo 8d9d025f11 some cleanup 2023-04-23 17:22:41 +03:00
gdlbo 6cac613f70 fixed microg pushes 2023-04-20 18:24:29 +03:00
gdlbo a5d0f1b730 microg settings and force microg usage 2023-04-20 16:14:52 +03:00
gdlbo 47465356fa fixed some spoof issues 2023-04-20 15:44:49 +03:00
gdlbo 4c6eed7dad added support for microg 2023-04-20 15:36:09 +03:00
gdlbo 7bdc8b21e4 updated jars 2023-04-20 15:31:59 +03:00
1079 changed files with 46344 additions and 32881 deletions
+59 -51
View File
@@ -1,49 +1,22 @@
### Все функции модификации
### Весь функционал модификации
* Лента
* Блокировка рекламы в ленте
* Блокировка постов через фильтры
* Возможность добавлять в вайтлист фильтров группы, чтобы в них не удалялись посты
# Реклама
* Блокировка рекламы в ленте +
* Блокировка историй с возможной рекламой +
* Блокировка постов:
* С источником
* С MiniApp
* Через фильтры групп, слов
* Возможность добавлять в вайтлист фильтров группы, чтобы в них не удалялись посты+
* Возможность отключить рекомендации от ВК
* Медиаконтент
* Отключение отправки метрики
# Интерфейс и внешний вид
* Округление дат
* 1 января в 4:20
* 1 января в 4:20:00
* 1 января 2000 в 4:20:00
* Округление чисел
* Сокращение постов **(Показать полностью)**
* Результаты опросов без участия
* Возможность указывать папку для загрузки фото/видео/музыки/всего
* Интеграция с <a href="https://www.last.fm/">LAST.FM</a> (а также возможность скробблинга)
* Возможность **кешировать музыку / автокешировать ее при прослушивании**
* Возможность выбирать раздел музыки, открываемый по умолчанию
* Возможность скачивать видео, очищать историю просмотра видео
* Возможность найти фотографию через поисковые системы
* Интеграция с <a href="https://vkx.app/">VK X</a>
* Возможность кешировать треки в приложении
* Сообщения
* Обои в сообщениях
* Возможность выключения кнопки записи голосовых сообщений
* Возможность скрыть блок рекомендаций в сообщениях
* Режим **VK Me**
* Возможность вернуть вкладку уведомлений для режима VK Me
* Встроенный переводчик текста для сообщений
* Возможность выбрать язык перевода
* Автоматический перевод ваших сообщений на выбранный язык
* Альтернативный дизайн эмодзи меню
* Возможность использовать системные эмодзи
* Возможнсть использовать Enter с аппаратной клавиатуры
* Панель ввода из приложения VK для iOS
* Telegram Стикеры
* Возможность загружать свои стикерпаки из Telegram во ВКонтакте
* Внешний вид
* Возможность отключать новый дизайн версий VK (Milkshake)
* Название заголовков в докбаре
* Возможность использовать акцент для иконок
* Возможность отключить счётчики в докбаре
* Смена иконки приложения (содержит много уникальных иконок для донатеров)
* Возможность покрасить панель навигации, веб-страницы
* Интерфейс
* Округление чисел
* Возможность отключить блок историй наверху ленты
* Возможность отключить боковую камеру в разделе «Новости»
* Возможность отключить блок «Что у вас нового?» в разделе «Новости»
@@ -56,23 +29,58 @@
* Возможность отключить сокращение названий разделов в меню
* Возможность скрыть блок рекомендаций друзей в разделе «Друзья»
* Возможность скрыть подписи в настройках
* Редактор докбара
* Редактор элементов Superapp
* Активность
* Анимированный экран входа для Android 12+
* Редактор докбара
* Редактор элементов Superapp
* Возможность отключать новый дизайн версий VK (Milkshake)
* Название заголовков в докбаре
* Возможность использовать свой акцент для иконок
* Возможность отключить счётчики в докбаре
* Смена иконки приложения (содержит много уникальных иконок для донатеров)
* Возможность покрасить панель навигации, веб-страницы
* Обои в сообщениях
* Возможность выключения кнопки записи голосовых сообщений
* Возможность скрыть блок рекомендаций в сообщениях
* Режим **VK Me**
* Возможность вернуть вкладку уведомлений для режима VK Me
* Встроенный переводчик текста для сообщений
* Альтернативный дизайн эмодзи меню
* Возможность использовать системные эмодзи
* Панель ввода из приложения VK для iOS
* Сокращение постов **(Показать полностью)**
* Результаты опросов без участия
# Медиаконтент
* Интеграция с <a href="https://www.last.fm/">LAST.FM</a> (а также возможность скробблинга)
* Возможность **кешировать музыку / автокешировать ее при прослушивании**
* Возможность выбирать раздел музыки, открываемый по умолчанию
* Возможность скачивать видео, очищать историю просмотра видео
* Возможность найти фотографию через поисковые системы ?
* Интеграция с <a href="https://vkx.app/">VK X</a>
* Поддержка новых текстов в музыке (и с Genius в том числе)
# Сообщения
* Шифрование сообщений
* Возможность видеть удаленные сообщения
* Функционал, позволяющий оставаться в «оффлайне» (принудительный оффлайн/не отправлять статус онлайна и т.п.)
* Нечиталка/неписалка для ЛС/бесед/ботов/групп
* Возможность загружать свои стикерпаки из Telegram во ВКонтакте
* Возможнсть использовать Enter с аппаратной клавиатуры
# Активность
* Возможность сохранять удалённые сообщения
* Функционал, позволяющий оставаться в «оффлайне» (принудительный оффлайн/не отправлять статус онлайна и т.п.)
* Возможность отключить информацию о том, что вы просмотрели историю/прослушали голосовое сообщение
* Возможность видеть онлайн пользователей, если у вас скрыт онлайн через VK Messenger
* Возможность сохранять настройки бесшумных сообщений/сообщений-бомбочек
* Отключить уведомление о том, что вы сделали скриншот в беседе
* Отключение редиректа через away.php
* Отключение отправки метрики
* Прокси
* Отключение бизнес-уведомлений
# Прокси
* Поддержка сторонних прокси, таких как: Zaborona, VikaMobile, рандомные прокси и прочие
* Возможность использовать **HTTP/HTTPS/SOCKS** прокси, менять домены **API/OAuth/Static**
* Другое
* Синхронизация контактов с VK
* Очистка кеша приложения (**всё/стикеры/изображения/видео/сообщения/WebView**)
* Возможность использовать **HTTP/HTTPS/SOCKS** прокси, менять домены **API/OAuth/Static**
# Другое
* Автоочистка кеша при достижении указанного вами значения (**100/500 MB, 1/2/5 GB**)
* Функционал для **дебага приложения/бэкап возможности/верификации VTosters**
* Возможность создания резервной копии (бекапа) настроек и аккаунтов
+14 -5
View File
@@ -21,14 +21,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 14
- name: Checkout
uses: actions/checkout@v3
- name: Set up NDK
uses: nttld/setup-ndk@v1
with:
ndk-version: r25c
add-to-path: false
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '14'
java-version: '17'
distribution: 'adopt'
- uses: actions/checkout@v3
- name: Download tools
run: |
sudo apt update
@@ -47,6 +54,8 @@ jobs:
java -jar scripts/apktool.jar b smali -o smali/VTLite.apk --use-aapt2 -p scripts/framework/
zipalign -p -f 4 smali/VTLite.apk smali/VTLite.apk_aligned
mv smali/VTLite.apk_aligned smali/VTLite.apk
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- name: Sign and verify the apk
run: |
+28
View File
@@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RClass Regeneration" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/rclass_regenerator.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
<configuration default="false" name="RClass Regeneration" type="BatchConfigurationType" factoryName="Batch">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PARENT_ENVS" value="true" />
<module name="" />
<option name="SCRIPT_NAME" value="./scripts/rclass_regenerator.bat" />
<option name="PARAMETERS" value="" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Update APKs Jars (MacOS)" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/update_jars_brew.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Update APKs Jars" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/scripts/update_jars.sh" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndInstallVTLBeta]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndInstallVTLBeta" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndInstallVTLDev]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndInstallVTLDev" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndInstallVTLRelease]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndInstallVTLRelease" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndLaunchVTLBeta]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndLaunchVTLBeta" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndLaunchVTLDev]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndLaunchVTLDev" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildAndLaunchVTLRelease]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildAndLaunchVTLRelease" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildVTLBeta]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildVTLBeta" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildVTLDev]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildVTLDev" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:buildVTLRelease]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:buildVTLRelease" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:exportDexBeta]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:exportDexBeta" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:exportDexDev]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:exportDexDev" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:exportDexRelease]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:exportDexRelease" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:prepareDexForMergeDev]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:prepareDexForMergeDev" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lite [app:prepareDexForMergeRelease]" type="GradleRunConfiguration" factoryName="Gradle" nameIsGenerated="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="app:prepareDexForMergeRelease" />
</list>
</option>
<option name="vmOptions" value="-Xms4g -Xmx4g" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
+13 -4
View File
@@ -37,7 +37,7 @@
* Стабильные сборки находятся в [Releases](https://github.com/vtosters/lite/releases)
Зеркало на нашем [<img src="https://git.maki.su/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/gdlbo/lite)
Зеркало на нашем [<img src="https://gitea.com/assets/img/logo.svg" align="center" width="20" height="20"/> Git](https://git.maki.su/vtosters/lite)
### Быстрая навигация
@@ -129,6 +129,15 @@
Для сборки необходимо вызвать одну из следующих gradle tasks:
+ `./gradlew buildVTL` - соберет и подпишет APK
+ `./gradlew buildAndInstallVTL` - соберет, подпишет и попытается установить через ADB
+ [FOR DEVS] `./gradlew prepareDexForMerge` - соберет все необходимое для сборки в `smali/`
+ `./gradlew buildVTL + <Build Type>` - соберет и подпишет APK
+ `./gradlew buildAndInstallVTL + <Build Type>` - соберет, подпишет и попытается установить через ADB
+ `./gradlew buildAndLaunchVTL + <Build Type>` - соберет, подпишет, попытается установить через ADB и запустит на устройстве
+ [FOR DEVS] `./gradlew prepareDexForMerge + <Build Type>` - соберет все необходимое для сборки в `smali/`
+ [FOR DEVS] `./gradlew exportDex + <Build Type>` - экспортирование дексов из собранного приложения для переноса в `smali/`
```
Build types:
Dev
Beta
Release
```
+65 -75
View File
@@ -1,8 +1,6 @@
import org.apache.tools.ant.taskdefs.condition.Os
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
plugins {
id 'com.android.application'
@@ -21,6 +19,10 @@ cargo {
def smaliFolder = projectDir.toPath().resolve("../smali").normalize()
def scriptsFolder = projectDir.toPath().resolve("../scripts").normalize()
//you can add own folders with decompiled smali code
def dexes = smaliFolder.toFile()
.listFiles({ f -> f.isDirectory() && f.name.startsWith("smali") } as FileFilter)
.size()
versionFile {
file = projectDir.toPath().resolve("src/main/assets/version.properties").toFile()
@@ -50,15 +52,22 @@ android {
}
buildTypes {
release {
minifyEnabled true
buildConfigField 'String', 'APPCENTER_KEY', '""'
proguardFile 'proguard-rules.pro'
}
rc {
minifyEnabled true
buildConfigField 'String', 'APPCENTER_KEY', '""'
proguardFile 'proguard-rules.pro'
}
dev {
initWith release
minifyEnabled false
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
beta {
initWith release
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
release {
minifyEnabled false
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
}
@@ -70,114 +79,96 @@ android {
}
namespace 'ru.vtosters.lite'
ndkVersion '25.2.9519653'
buildFeatures {
aidl true
}
}
task testClasses {
doLast {
println 'This is a dummy testClasses task'
}
}
dependencies {
implementation "com.microsoft.appcenter:appcenter-analytics:5.0.0"
implementation "com.microsoft.appcenter:appcenter-crashes:5.0.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.4'
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
implementation 'com.google.guava:guava:32.0.0-android'
implementation 'com.mpatric:mp3agic:0.9.1'
implementation fileTree(dir: 'libs', include: ['*.aar'])
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.guava:guava:23.3-android'
}
task generateBuildTasks() {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def buildType = output.name.toString().capitalize()
def lowerBuildType = buildType.toLowerCase()
if (buildType == "Base") return
task "exportDex$buildType"() {
dependsOn('assemble' + buildType)
doLast {
try {
var z = ""
if (lowerBuildType != "debug") {
z = "-unsigned"
}
File moduleDir = projectDir
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk"))
ZipFile file = new ZipFile(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk");
ZipEntry entry
while ((entry = inputStream.nextEntry) != null) {
if (entry.getName().equals("classes.dex")) {
print('classes.dex found!')
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes6.dex"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len)
}
outputStream.close()
}
if (entry.getName().equals("classes2.dex")) {
print('\nclasses2.dex found!')
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes7.dex"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, len)
}
outputStream.close()
}
}
for (ZipEntry e : Collections.list(file.entries())) {
if (e.getName().endsWith("assets/version.properties")) {
var is = file.getInputStream(e)
FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "version.properties"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = is.read(buffer)) > 0) {
outputStream.write(buffer, 0, len)
}
outputStream.close()
print('\nversion.properties found!')
}
}
def suffix = buildType.toLowerCase()
String apkPath = projectDir.getAbsolutePath() + "/build/outputs/apk/$suffix/app-$suffix"
def apk = new File(apkPath + "-unsigned.apk")
if (!apk.exists()) apk = new File(apkPath + ".apk")
def zip = new ZipFile(apk)
def dexCounter = dexes
zip.stream()
.filter(entry -> entry.name.matches("^classes(?:(?=\\d)2|)\\.dex\$") || entry.name == "assets/version.properties")
.forEach(entry -> {
String filename
if (entry.name.matches("^classes(?:(?=\\d)2|)\\.dex\$")) filename = "classes${++dexCounter}.dex"
else filename = "version.properties"
println("Exporting " + entry.name)
def tmpFile = new File(projectDir, filename)
try (def is = zip.getInputStream(entry); def fos = new FileOutputStream(tmpFile)) {
def buff = new byte[0x800]
int len
while ((len = is.read(buff)) > 0) fos.write(buff, 0, len)
}
});
} catch (Exception e) {
e.printStackTrace()
}
}
}
task "prepareDexForMerge$buildType"(type: Copy) {
dependsOn('exportDex' + buildType)
// Remove comment for native lib build
dependsOn('cargoBuild')
//dependsOn('cargoBuild')
into smaliFolder.toString()
from(projectDir) {
include 'classes6.dex'
include 'classes7.dex'
into '../smali'
include "classes*.dex"
}
from(projectDir) {
include 'version.properties'
into 'assets'
}
// Remove comment for native lib build
from('../native-effects/target/aarch64-linux-android/release'){
from('../native-effects/target/aarch64-linux-android/release') {
include 'libnative_effects.so'
into 'lib/arm64-v8a'
}
from('../native-effects/target/armv7-linux-androideabi/release'){
from('../native-effects/target/armv7-linux-androideabi/release') {
include 'libnative_effects.so'
into 'lib/armeabi-v7a'
}
doLast {
new File(projectDir, "classes6.dex").delete()
new File(projectDir, "classes7.dex").delete()
new File(projectDir, "version.properties").delete()
}
}
task "cleanTemporaryFiles$buildType"(type: Delete)
{
delete fileTree(dir: projectDir, include: ["*.dex", "version.properties"])
delete fileTree(dir: smaliFolder, include: ['*.dex', "assets/version.properties"])
}
task "buildVTL$buildType"(type: Exec) {
dependsOn('prepareDexForMerge' + buildType)
workingDir(projectDir.parentFile)
finalizedBy "cleanTemporaryFiles$buildType"
if (Os.isFamily("unix") || Os.isFamily("mac")) {
commandLine 'bash', '-c', './scripts/build.sh'
} else {
@@ -205,7 +196,6 @@ task generateBuildTasks() {
}
}
task generateRjava(type: Exec) {
workingDir(projectDir.parentFile)
if (Os.isFamily("unix") || Os.isFamily("mac"))
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26 -1
View File
@@ -18,4 +18,29 @@
## If you keep the line number information, uncomment this to
## hide the original source file name.
##-renamesourcefileattribute SourceFile
#-keepclassmembers class fq
#-keepclassmembers class fq
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
-repackageclasses
-allowaccessmodification
-optimizations
-optimizationpasses 5
-keepattributes SourceFile, LineNumberTable
-keep,allowoptimization class ru.vtosters.hooks.**, ru.vtosters.sponsorpost.internal.Native, ru.vtosters.lite.utils.AndroidUtils, ru.vtosters.lite.utils.NetworkUtils, ru.vtosters.lite.utils.CrashReporter, ru.vtosters.lite.ui.activities.**, ru.vtosters.lite.services.**, ru.vtosters.lite.ui.fragments.**, ru.vtosters.lite.themes.**, ru.vtosters.lite.music.hook.**, ru.vtosters.lite.music.cache.injectors.**, ru.vtosters.lite.ui.wallpapers.NativeEffects, com.aefyr.tsg.g2.TelegramStickersService {
public <fields>;
public <methods>;
public <init>(...);
}
-keep class com.vtosters.lite.** { *; }
-keep class com.vk.** { *; }
-keep class bruhcollective.itaysonlab.libvkx.client.** { *; }
+9 -9
View File
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"></application>
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"></application>
</manifest>
@@ -1,20 +1,19 @@
package bruhcollective.itaysonlab.libvkx.client;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.isValidSignature;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.RemoteException;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import com.vk.dto.music.MusicTrack;
import com.vk.music.common.MusicPlaybackLaunchContext;
import ru.vtosters.hooks.other.Preferences;
import java.util.ArrayList;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class LibVKXClient {
@SuppressLint("StaticFieldLeak")
@@ -35,7 +34,7 @@ public class LibVKXClient {
public static boolean isVkxInstalled() {
try {
getGlobalContext().getPackageManager().getPackageInfo("ua.itaysonlab.vkx", 0);
return isValidSignature();
return isValidSignature() && !Preferences.serverFeaturesDisable();
} catch (Exception unused) {
return false;
}
@@ -78,15 +77,11 @@ public class LibVKXClient {
}
}
public static void lambdaplay(List<MusicTrack> list, MusicTrack musicTrack, ILibVkxService iLibVkxService){
public static void lambdaplay(List<MusicTrack> list, MusicTrack musicTrack, ILibVkxService iLibVkxService) {
lambdaplay(list, musicTrack, iLibVkxService, null);
}
public static String asId(MusicTrack musicTrack) {
String id = musicTrack.e + "_" + musicTrack.d;
if (musicTrack.O != null && !musicTrack.O.equals("")) {
id += "_" + musicTrack.O;
}
return id;
return musicTrack.y1();
}
}
@@ -1,7 +1,5 @@
package bruhcollective.itaysonlab.libvkx.client;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -10,10 +8,11 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import java.util.List;
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
public class LibVKXClientImpl {
private ILibVkxService serviceInstance;
@@ -5,24 +5,21 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationManagerCompat;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import ru.vtosters.lite.utils.AndroidUtils;
/**
* Created by Aefyr on 20.05.2018.
*/
public class NotificationsHelper {
// private static final int NOTIFICATION_ID_BASE = 3921337;
// private static final int NOTIFICATION_ID_BASE = 3921337;
private static final String NOTIFICATION_CHANNEL_ID = "tgss_chan_v5";
private static final int MAX_UPDATE_NOTIFICATIONS_PER_SECOND = 3;
private static final int MIN_TIME_BETWEEN_UPDATE_NOTIFICATIONS = 1000 / MAX_UPDATE_NOTIFICATIONS_PER_SECOND;
@@ -3,7 +3,6 @@ package com.aefyr.tsg.g2;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -37,10 +36,8 @@ public class TelegramStickersPack extends CustomStickersPack {
public HashMap<String, List<Integer>> emojis = new HashMap<>();
public TelegramStickersPack(String id) {
final var idLowerCase = id.toLowerCase();
this.id = idLowerCase;
this.hash = idLowerCase.hashCode();
title = id;
this.id = this.title = id;
this.hash = id.hashCode();
}
public JSONObject encodeEmojis() throws JSONException {
@@ -104,7 +101,7 @@ public class TelegramStickersPack extends CustomStickersPack {
@Override
public boolean equals(Object obj) {
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equalsIgnoreCase(id);
return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equals(id);
}
@Override
@@ -8,6 +8,7 @@ import android.util.Log;
import com.aefyr.tsg.g2.sql.TelegramStickersDbHelper;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber;
import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersPackInfo;
import ru.vtosters.lite.ui.fragments.VTSettings;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
import java.io.File;
@@ -25,19 +26,13 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class TelegramStickersService {
private static final String TAG = "TGStickersService";
private static TelegramStickersService instance;
private final Context c;
private final ArrayList<TelegramStickersPack> packs;
private final ArrayList<TelegramStickersPack> activePacks;
private final ArrayList<TelegramStickersPack> inactivePacks;
private final ArrayList<StickersEventsListener> listeners;
private final HashSet<String> currentlyDownloading;
private final Handler uiThreadHandler;
private final TelegramStickersGrabber grabber;
private final TelegramStickersDbHelper dbHelper;
private final ArrayList<Runnable> queuedTasks;
private final ThreadPoolExecutor executor;
@@ -47,16 +42,14 @@ public class TelegramStickersService {
private TelegramStickersService(Context context) {
instance = this;
this.c = context.getApplicationContext();
listeners = new ArrayList<>();
currentlyDownloading = new HashSet<>();
uiThreadHandler = new Handler(Looper.getMainLooper());
grabber = new TelegramStickersGrabber(null);
dbHelper = new TelegramStickersDbHelper(c);
dbHelper = new TelegramStickersDbHelper(context);
queuedTasks = new ArrayList<>();
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
notificationsHelper = new NotificationsHelper(c);
notificationsHelper = new NotificationsHelper(context);
packs = new ArrayList<>();
activePacks = new ArrayList<>();
@@ -69,11 +62,11 @@ public class TelegramStickersService {
return instance == null ? new TelegramStickersService(c) : instance;
}
private void updatePacks(final boolean notify) {
private void updatePacks(boolean notify) {
packs.clear();
dbHelper.getAllPacks(new TelegramStickersDbHelper.PacksLoadingListener() {
@Override
public void onPackLoaded(final TelegramStickersPack pack) {
public void onPackLoaded(TelegramStickersPack pack) {
runOnUiThread(() -> {
packs.add(pack);
@@ -102,14 +95,6 @@ public class TelegramStickersService {
});
}
public void addStickersEventsListener(StickersEventsListener listener) {
listeners.add(listener);
}
public void removeStickersEventsListener(StickersEventsListener listener) {
listeners.remove(listener);
}
private void notifyPackAdded(TelegramStickersPack pack, int atIndex) {
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
if (listeners.isEmpty())
@@ -152,6 +137,7 @@ public class TelegramStickersService {
private void notifyActivePacksListChanged() {
getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD));
getGlobalContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT));
if (listeners.isEmpty())
return;
@@ -186,20 +172,16 @@ public class TelegramStickersService {
return inactivePacks;
}
public boolean isDoneLoading() {
return ready;
}
public boolean requestPackDownload(final String id, File packFolder) {
public void requestPackDownload(String id, File packFolder) {
if (currentlyDownloading.contains(id)) {
Log.e(TAG, String.format("Got request to download pack %s which is already downloading", id));
return false;
return;
}
if (!ready) {
final File folder = packFolder;
File folder = packFolder;
queuedTasks.add(() -> requestPackDownload(id, folder));
return true;
return;
}
TelegramStickersPack pack = new TelegramStickersPack(id);
@@ -219,85 +201,86 @@ public class TelegramStickersService {
notifyPackAdded(pack, packs.size() - 1);
}
final TelegramStickersPack newPack = pack;
final boolean isUpdate = update;
TelegramStickersPack newPack = pack;
boolean isUpdate = update;
currentlyDownloading.add(id.toLowerCase());
currentlyDownloading.add(id);
notificationsHelper.packStartedDownloading(newPack);
grabber.enableProxy();
grabber.grabPack(id, packFolder, pack.version, new TelegramStickersGrabber.PackDownloadListener() {
@Override
public void onPackDownloaded(TelegramStickersPackInfo packInfo, boolean newVersionFound) {
runOnUiThread(() -> {
newPack.state = TelegramStickersPack.DOWNLOADED;
notifyPackChanged(newPack, packs.indexOf(newPack));
currentlyDownloading.remove(newPack.id);
syncPack(newPack);
if (isUpdate) {
if (newPack.enabled)
notifyActivePacksListChanged();
} else {
activePacks.add(newPack);
notifyActivePacksListChanged();
}
});
notificationsHelper.packDoneDownloading(newPack, true, isUpdate, null);
}
@Override
public void onPackDownloadError(final Exception e) {
runOnUiThread(() -> {
Log.e(TAG, "Error while downloading pack " + newPack.id);
e.printStackTrace();
if (!isUpdate) {
int index = packs.indexOf(newPack);
packs.remove(index);
notifyPackRemoved(newPack, index);
} else {
// try-catch block to handle possible timeout exception
try {
grabber.grabPack(id, packFolder, pack.version, new TelegramStickersGrabber.PackDownloadListener() {
@Override
public void onPackDownloaded(TelegramStickersPackInfo packInfo, boolean newVersionFound) {
runOnUiThread(() -> {
newPack.state = TelegramStickersPack.DOWNLOADED;
notifyPackChanged(newPack, packs.indexOf(newPack));
}
currentlyDownloading.remove(newPack.id);
syncPack(newPack);
notifyPackDownloadError(newPack, e);
currentlyDownloading.remove(newPack.id);
});
notificationsHelper.packDoneDownloading(newPack, false, isUpdate, e);
}
if (isUpdate) {
if (newPack.enabled)
notifyActivePacksListChanged();
} else {
activePacks.add(newPack);
notifyActivePacksListChanged();
}
});
notificationsHelper.packDoneDownloading(newPack, true, isUpdate, null);
}
@Override
public void onGotPackInfo(final TelegramStickersPackInfo packInfo) {
runOnUiThread(() -> {
newPack.title = packInfo.title;
newPack.stickersCount = packInfo.stickersCount;
newPack.version = packInfo.version;
notifyPackChanged(newPack, packs.indexOf(newPack));
});
@Override
public void onPackDownloadError(Exception e) {
runOnUiThread(() -> {
Log.e(TAG, "Error while downloading pack " + newPack.id);
e.printStackTrace();
notificationsHelper.packDownloadUpdated(newPack, 0);
}
if (!isUpdate) {
int index = packs.indexOf(newPack);
packs.remove(index);
notifyPackRemoved(newPack, index);
} else {
newPack.state = TelegramStickersPack.DOWNLOADED;
notifyPackChanged(newPack, packs.indexOf(newPack));
}
@Override
public void onStickerDownloaded(String pack, File sticker, String boundEmoji, int stickerIndex, int downloadedStickersCount, int totalStickersCount) {
stickerIndex--;
notifyPackDownloadError(newPack, e);
currentlyDownloading.remove(newPack.id);
});
notificationsHelper.packDoneDownloading(newPack, false, isUpdate, e);
}
List<Integer> list = newPack.emojis.get(boundEmoji);
if (list == null) list = new ArrayList<>();
list.add(stickerIndex);
newPack.emojis.put(boundEmoji, list);
@Override
public void onGotPackInfo(TelegramStickersPackInfo packInfo) {
runOnUiThread(() -> {
newPack.title = packInfo.title;
newPack.stickersCount = packInfo.stickersCount;
newPack.version = packInfo.version;
notifyPackChanged(newPack, packs.indexOf(newPack));
});
notificationsHelper.packDownloadUpdated(newPack, downloadedStickersCount);
}
});
notificationsHelper.packDownloadUpdated(newPack, 0);
}
return true;
@Override
public void onStickerDownloaded(String pack, File sticker, String boundEmoji, int stickerIndex, int downloadedStickersCount, int totalStickersCount) {
stickerIndex--;
List<Integer> list = newPack.emojis.get(boundEmoji);
if (list == null) list = new ArrayList<>();
list.add(stickerIndex);
newPack.emojis.put(boundEmoji, list);
notificationsHelper.packDownloadUpdated(newPack, downloadedStickersCount);
}
});
} catch (Exception e) {
// handle other possible exceptions
e.printStackTrace();
}
}
public void setPackEnabled(final TelegramStickersPack pack, final boolean enabled, final boolean notify) {
public void setPackEnabled(TelegramStickersPack pack, boolean enabled, boolean notify) {
if (!ready) {
queuedTasks.add(() -> setPackEnabled(pack, enabled, notify));
return;
@@ -323,7 +306,7 @@ public class TelegramStickersService {
}
}
public void deletePack(final TelegramStickersPack pack) {
public void deletePack(TelegramStickersPack pack) {
if (!ready) {
queuedTasks.add(() -> deletePack(pack));
return;
@@ -332,7 +315,6 @@ public class TelegramStickersService {
boolean d = dbHelper.deletePack(pack);
Log.d(TAG, "Delete from DB=" + d);
int index = packs.indexOf(pack);
packs.remove(index);
notifyPackRemoved(pack, index);
@@ -353,7 +335,7 @@ public class TelegramStickersService {
uiThreadHandler.post(r);
}
private void syncPack(final TelegramStickersPack pack) {
private void syncPack(TelegramStickersPack pack) {
if (!ready) {
queuedTasks.add(() -> syncPack(pack));
return;
@@ -395,6 +377,4 @@ public class TelegramStickersService {
void onInactivePacksListChanged();
}
}
@@ -8,9 +8,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.util.Log;
import com.aefyr.tsg.g2.TelegramStickersPack;
import org.json.JSONException;
import org.json.JSONObject;
@@ -5,47 +5,42 @@ import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.aefyr.tsg.g2.stickersgrabber.util.Flag;
import com.aefyr.tsg.g2.stickersgrabber.util.GoalCounter;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import ru.vtosters.lite.net.NetCall;
import ru.vtosters.lite.net.NetCallback;
import ru.vtosters.lite.net.NetClient;
import ru.vtosters.lite.net.NetRequest;
import ru.vtosters.lite.net.NetResponse;
/**
* Created by Aefyr on 11.05.2018.
*/
public class TelegramStickersGrabber {
private static final String TAG = "TSG";
public static String REAL_TG_IP = null;
public static String PROXY_IP = null;
public static int PROXY_PORT = -1;
public static String PROXY_USER = null;
public static String PROXY_PASS = null;
public static boolean PROXY_SOCKS = true;
public static boolean HAS_PASS = false;
public static boolean USE_PROXY = false;
private static final OkHttpClient sClient = VtOkHttpClient.getInstance();
private static final int MAX_RETRIES = 2;
private static final AtomicInteger retryCount = new AtomicInteger();
private static final int STICKER_QUALITY = 100;
private static final String STICKER_FILE_NAME_FORMAT = "%03d.png";
private static final String STICKERS_KEY = "stickers";
private static final String FILE_ID_KEY = "file_id";
private static final String EMOJI_KEY = "emoji";
private static final String IS_ANIMATED_KEY = "is_animated";
private static final String IS_VIDEO_KEY = "is_video";
private static final String THUMB_KEY = "thumb";
private static final String NAME_KEY = "name";
private static final String TITLE_KEY = "title";
private static String BOT_API_BASE_URL;
private static String GET_STICKER_SET_URL;
private static String GET_FILE_URL;
@@ -57,17 +52,15 @@ public class TelegramStickersGrabber {
private final MessageDigest sha256;
private final Handler uiThreadHandler;
private String botApiKey = "";
private NetClient httpClient;
private String botApiKey;
public TelegramStickersGrabber(String botApiKey) {
this.botApiKey = botApiKey;
httpClient = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
uiThreadHandler = new Handler(Looper.getMainLooper());
try {
sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "No support for SHA-256");
Log.d(TAG, "No support for SHA-256");
e.printStackTrace();
throw new RuntimeException(e);
}
@@ -76,81 +69,12 @@ public class TelegramStickersGrabber {
public static void updateURLs() {
BOT_API_BASE_FILE_URL = "https://api.telegram.org/file/bot%s/%s";
BOT_API_BASE_URL = REAL_TG_IP != null ? "https://" + REAL_TG_IP + "/bot%s/" : "https://api.telegram.org/bot%s/";
BOT_API_BASE_URL = "https://api.telegram.org/bot%s/";
GET_STICKER_SET_URL = BOT_API_BASE_URL + "getStickerSet?name=%s";
GET_FILE_URL = BOT_API_BASE_URL + "getFile?file_id=%s";
}
public static boolean isTelegramBlocked() {
NetClient client = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
try {
if (PROXY_IP != null) {
Socket socket = new Socket(PROXY_IP, PROXY_PORT);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socket.getRemoteSocketAddress());
NetClient.Builder b = new NetClient.Builder().proxy(proxy);
if (PROXY_USER != null) {
b = b.proxyAuthenticator(new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray()));
}
client = b.build();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
NetRequest req = new NetRequest.Builder().url(REAL_TG_IP != null ? "https://" + REAL_TG_IP + "/test/" : "https://api.telegram.org/test/").build();
try {
NetResponse res = client.newCall(req).execute();
String s = res.getDataString();
if (!s.equals("{\"ok\":false,\"error_code\":404,\"description\":\"Not Found\"}"))
return true;
} catch (Exception e) {
return true;
}
return false;
}
public void resetProxy() {
httpClient = new NetClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build();
}
public void enableProxy() {
Thread kostil = new Thread() {
@Override
public void run() {
try {
if (PROXY_IP != null) {
Socket socket = new Socket(PROXY_IP, PROXY_PORT);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socket.getRemoteSocketAddress());
NetClient.Builder b = httpClient.newBuilder().proxy(proxy);
if (PROXY_USER != null) {
httpClient = b.proxyAuthenticator(new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray())).build();
}
httpClient = b.build();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
kostil.start();
try {
kostil.join();
Log.d(TAG, "Proxy set");
} catch (InterruptedException e) {
Log.e(TAG, "Unable to set proxy:");
e.printStackTrace();
}
}
public void grabPack(String id, File packFolder, String installedVersion, final PackDownloadListener listener) {
public void grabPack(String id, File packFolder, String installedVersion, PackDownloadListener listener) {
getPackInfo(id, packFolder, installedVersion, listener);
}
@@ -158,264 +82,247 @@ public class TelegramStickersGrabber {
botApiKey = key;
}
public void checkKey(final KeyCheckListener listener) {
public void checkKey(KeyCheckListener listener) {
Log.d(TAG, "Checking key: " + botApiKey);
NetRequest request = new NetRequest.Builder().get().url(String.format(BOT_API_BASE_URL + "getMe", botApiKey)).build();
httpClient.newCall(request).enqueue(new NetCallback() {
// Create a request object with the URL and bot API key
Request request = new Request.a()
.b(String.format(BOT_API_BASE_URL + "getMe", botApiKey))
.a();
// Create a new call object and enqueue it to run asynchronously
sClient.a(request).a(new Callback() {
@Override
public void onFailure(NetCall call, IOException e) {
e.printStackTrace();
public void a(Call call, IOException e) {
// Handle the failure case
Log.d(TAG, e.getMessage());
runOnUiThread(listener::onNetError);
}
@Override
public void onResponse(NetCall call, final NetResponse response) throws IOException {
runOnUiThread(() -> listener.onKeyChecked(response.code() == 200));
public void a(Call call, Response response) {
Log.d(TAG, response.toString());
runOnUiThread(() -> listener.onKeyChecked(response.h()));
}
});
}
private void getPackInfo(final String packName, final File packFolder, final String installedVersion, final PackDownloadListener listener) {
NetRequest packInfoRequest = new NetRequest.Builder().get().url(String.format(GET_STICKER_SET_URL, botApiKey, packName)).build();
httpClient.newCall(packInfoRequest).enqueue(new NetCallback() {
private void getPack(StickerSet set, File packFolder, TelegramStickersPackInfo packInfo, PackDownloadListener listener) {
GoalCounter downloadedStickers = new GoalCounter(set.stickers.size(), () -> {
Log.d(TAG, String.format("Pack %s has been downloaded to %s", set.id, packFolder.getAbsolutePath()));
listener.onPackDownloaded(packInfo, true);
});
Flag deathFlag = new Flag();
for (int i = 0; i < set.stickers.size(); i++) {
Sticker sticker = set.stickers.get(i);
String getFileUrl = String.format(GET_FILE_URL, botApiKey, sticker.fileId);
Request fileInfoRequest = new Request.a()
.b(getFileUrl)
.a();
int stickerIndex = i + 1;
sClient.a(fileInfoRequest).a(new Callback() {
@Override
public void a(Call call, IOException e) {
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileInfoRequest, this);
}
@Override
public void a(Call call, Response response) {
if (deathFlag.up())
return;
if (!response.h()) {
retryRequest(call, fileInfoRequest, this);
return;
}
try (ResponseBody responseBody = response.a()) {
JSONObject fileInfoResponse = new JSONObject(responseBody.g()).getJSONObject("result");
String filePath = fileInfoResponse.getString("file_path");
if (botApiKey == null) botApiKey = "";
String fileDownloadUrl = String.format(BOT_API_BASE_FILE_URL, botApiKey, filePath);
Request fileDownloadRequest = new Request.a()
.b(fileDownloadUrl)
.a();
sClient.a(fileDownloadRequest).a(new Callback() {
@Override
public void a(Call call, IOException e) {
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileDownloadRequest, this);
}
@Override
public void a(Call call, Response response) {
if (deathFlag.up())
return;
if (!response.h()) {
retryRequest(call, fileDownloadRequest, this);
return;
}
try (ResponseBody responseBody = response.a()) {
if (responseBody == null) {
handleFailure(deathFlag, listener, new TSGException("Response body for a sticker is null :/"), downloadedStickers, call, fileDownloadRequest, this);
return;
}
InputStream stickerInputStream = responseBody.a();
Bitmap stickerImage = BitmapFactory.decodeStream(stickerInputStream);
if (stickerImage == null) {
handleFailure(deathFlag, listener, new TSGException("Unable to decode sticker image"), downloadedStickers, call, fileDownloadRequest, this);
return;
}
File stickerFile = new File(packFolder, String.format(STICKER_FILE_NAME_FORMAT, stickerIndex));
try {
saveStickerImage(stickerImage, stickerFile);
} catch (TSGException e) {
throw new RuntimeException(e);
}
listener.onStickerDownloaded(set.name, stickerFile, sticker.emoji, stickerIndex,
downloadedStickers.value() + 1, set.stickers.size());
downloadedStickers.increase();
}
}
});
} catch (Exception e) {
handleFailure(deathFlag, listener, e, downloadedStickers, call, fileInfoRequest, this);
}
}
});
}
}
private void handleFailure(Flag deathFlag, PackDownloadListener listener, Exception e, GoalCounter downloadedStickers, Call call, Request request, Callback callback) {
if ((e instanceof IOException || e instanceof TSGException) && retryCount.get() > MAX_RETRIES) {
Log.e(TAG, "Unable to download sticker: " + e.getMessage());
downloadedStickers.increase();
return;
} else if (retryCount.get() < MAX_RETRIES) {
retryRequest(call, request, callback);
return;
}
if (deathFlag.up()) {
return;
}
deathFlag.raise();
listener.onPackDownloadError(e);
}
private void retryRequest(Call call, Request request, Callback callback) {
retryCount.incrementAndGet();
if (retryCount.get() > MAX_RETRIES) {
Log.e(TAG, "retryRequest: max retries");
callback.a(call, new IOException());
} else {
Log.e(TAG, "retryRequest");
sClient.a(request).a(callback);
}
}
private void saveStickerImage(Bitmap image, File file) throws TSGException {
try (FileOutputStream fos = new FileOutputStream(file)) {
image.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (IOException e) {
throw new TSGException("Unable to save sticker image");
}
}
private void getPackInfo(String packName, File packFolder, String installedVersion, PackDownloadListener listener) {
String getStickerSetUrl = String.format(GET_STICKER_SET_URL, botApiKey, packName);
Request packInfoRequest = new Request.a()
.b(getStickerSetUrl)
.a();
sClient.a(packInfoRequest).a(new Callback() {
@Override
public void onFailure(NetCall call, IOException e) {
public void a(Call call, IOException e) {
Log.d(TAG, e.getMessage());
listener.onPackDownloadError(e);
}
@Override
public void onResponse(NetCall call, NetResponse response) {
if (!response.isSuccessful()) {
if (response.getData() != null)
listener.onPackDownloadError(new TSGException(response.getDataString() + "\nURL: " + call.request().url()));
else
listener.onPackDownloadError(new TSGException("Unknown Exception, no response body"));
public void a(Call call, Response response) throws IOException {
Log.d(TAG, response.toString());
if (!response.h()) {
String errorMessage = response.a() != null ? response.a().g() + "\nURL: " + call.m0().g() : "Unknown Exception, no response body";
listener.onPackDownloadError(new TSGException(errorMessage));
return;
}
StickerSet set;
try {
JSONObject jPackInfo = new JSONObject(response.getDataString()).getJSONObject("result");
String packVersion = new String(sha256.digest(jPackInfo.toString().getBytes(StandardCharsets.UTF_8)));
try (ResponseBody responseBody = response.a()) {
JSONObject packInfoResponse = new JSONObject(responseBody.g()).getJSONObject("result");
String packVersion = new String(sha256.digest(packInfoResponse.toString().getBytes(StandardCharsets.UTF_8)));
if (packVersion.equals(installedVersion)) {
listener.onPackDownloaded(null, false);
return;
}
JSONArray jStickers = jPackInfo.getJSONArray("stickers");
JSONArray stickersArray = packInfoResponse.getJSONArray(STICKERS_KEY);
if (jStickers.length() == 0) {
if (stickersArray.length() == 0) {
listener.onPackDownloadError(new TSGException("No stickers in pack"));
return;
}
ArrayList<Sticker> stickers = new ArrayList<>(jStickers.length());
ArrayList<Sticker> stickers = parseStickers(stickersArray);
Log.d(TAG, String.format("Parsing stickers in pack %s", packName));
StickerSet set = new StickerSet(packInfoResponse.getString(NAME_KEY), packInfoResponse.getString(TITLE_KEY), packVersion, stickers);
for (int i = 0; i < jStickers.length(); i++) {
JSONObject jSticker = jStickers.getJSONObject(i);
if (jSticker.optBoolean("is_animated") || jSticker.optBoolean("is_video")) {
if (jSticker.has("thumb")) {
stickers.add(new Sticker(jSticker.getJSONObject("thumb").getString("file_id"), jSticker.getString("emoji")));
} else {
listener.onPackDownloadError(new TSGException("Animated and video stickerpacks without thumbs are not supported!"));
return;
}
} else {
stickers.add(new Sticker(jSticker.getString("file_id"), jSticker.getString("emoji")));
}
if (!packFolder.exists() && !packFolder.mkdirs()) {
// Handle the folder creation failure case
listener.onPackDownloadError(new IOException("Can't create folder for the pack!"));
return;
}
set = new StickerSet(jPackInfo.getString("name"), jPackInfo.getString("title"), packVersion, stickers);
Log.d(TAG, String.format("Got info for pack %s, now downloading stickers to %s", packName, packFolder.getAbsolutePath()));
TelegramStickersPackInfo packInfo = new TelegramStickersPackInfo(packName, set.name, set.stickers.size(), set.version);
listener.onGotPackInfo(packInfo);
getPack(set, packFolder, packInfo, listener);
} catch (Exception e) {
listener.onPackDownloadError(e);
return;
}
if (!packFolder.exists() && !packFolder.mkdirs()) {
listener.onPackDownloadError(new IOException("Can't create folder for the pack!"));
return;
}
Log.d(TAG, String.format("Got info for pack %s, now downloading stickers to %s", packName, packFolder.getAbsolutePath()));
TelegramStickersPackInfo packInfo = new TelegramStickersPackInfo(packName, set.name, set.stickers.size(), set.version);
listener.onGotPackInfo(packInfo);
getPack(set, packFolder, packInfo, listener);
}
});
}
private void getPack(final StickerSet set, final File packFolder, final TelegramStickersPackInfo packInfo, final PackDownloadListener listener) {
final GoalCounter downloadedStickers = new GoalCounter(set.stickers.size(), () -> {
Log.d(TAG, String.format("Pack %s has been downloaded to %s", set.id, packFolder.getAbsolutePath()));
private ArrayList<Sticker> parseStickers(JSONArray stickersArray) throws JSONException, TSGException {
ArrayList<Sticker> stickers = new ArrayList<>(stickersArray.length());
listener.onPackDownloaded(packInfo, true);
});
final Flag deathFlag = new Flag();
for (int i = 0; i < stickersArray.length(); i++) {
JSONObject stickerObject = stickersArray.getJSONObject(i);
boolean isAnimated = stickerObject.optBoolean(IS_ANIMATED_KEY);
boolean isVideo = stickerObject.optBoolean(IS_VIDEO_KEY);
String fileId = stickerObject.getString(FILE_ID_KEY);
String emoji = stickerObject.getString(EMOJI_KEY);
AtomicInteger retryed = new AtomicInteger();
for (int i = 0; i < set.stickers.size(); i++) {
final Sticker sticker = set.stickers.get(i);
NetRequest fileInfoRequest = new NetRequest.Builder().get().url(String.format(GET_FILE_URL, botApiKey, sticker.fileId)).build();
final int stickerIndex = i + 1;
AtomicBoolean sync = new AtomicBoolean();
httpClient.newCall(fileInfoRequest).enqueue(new NetCallback() {
@Override
public void onFailure(NetCall call, IOException e) {
if (deathFlag.up())
return;
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
if (isAnimated || isVideo) {
if (stickerObject.has(THUMB_KEY)) {
fileId = stickerObject.getJSONObject(THUMB_KEY).getString(FILE_ID_KEY);
} else {
throw new TSGException("Animated and video stickerpacks without thumbs are not supported!");
}
}
@Override
public void onResponse(NetCall call, NetResponse response) throws IOException {
if (deathFlag.up())
return;
if (response.code() != 200) {
retryed.incrementAndGet();
if (retryed.get() > 5)
onFailure(call, new IOException());
else httpClient.newCall(fileInfoRequest)
.enqueue(this);
return;
}
String filePath;
try {
JSONObject jFileInfo = new JSONObject(response.getDataString()).getJSONObject("result");
filePath = jFileInfo.getString("file_path");
} catch (Exception e) {
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
return;
}
if (botApiKey == null) botApiKey = "";
NetRequest fileDownloadRequest = new NetRequest.Builder().get().url(String.format(BOT_API_BASE_FILE_URL, botApiKey, filePath)).build();
httpClient.newCall(fileDownloadRequest).enqueue(new NetCallback() {
@Override
public void onFailure(NetCall call, IOException e) {
if (deathFlag.up())
return;
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
}
@Override
public void onResponse(NetCall call, NetResponse response) throws IOException {
if (deathFlag.up())
return;
if (response.code() != 200) {
retryed.incrementAndGet();
if (retryed.get() > 5)
onFailure(call, new IOException());
else httpClient.newCall(fileDownloadRequest)
.enqueue(this);
return;
}
if (response.getData() == null) {
deathFlag.raise();
listener.onPackDownloadError(new TSGException("Response body for a sticker is null :/"));
sync.set(true);
return;
}
InputStream stickerInputStream = response.getDataStream();
Bitmap stickerImage = BitmapFactory.decodeStream(stickerInputStream);
try {
stickerInputStream.close();
} catch (IOException e) {
e.printStackTrace();
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
return;
}
if (stickerImage == null) {
deathFlag.raise();
listener.onPackDownloadError(new TSGException("Unable to decode sticker image"));
sync.set(true);
return;
}
File stickerFile = new File(packFolder, String.format("%03d.png", stickerIndex));
FileOutputStream fileOutputStream;
try {
fileOutputStream = new FileOutputStream(stickerFile);
} catch (Exception e) {
e.printStackTrace();
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
return;
}
if (!stickerImage.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
stickerImage.recycle();
deathFlag.raise();
listener.onPackDownloadError(new TSGException("Unable to compress sticker to a png file at path: " + stickerFile.getAbsolutePath()));
sync.set(true);
return;
}
stickerImage.recycle();
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
deathFlag.raise();
listener.onPackDownloadError(e);
sync.set(true);
return;
}
listener.onStickerDownloaded(set.name, stickerFile, sticker.emoji, stickerIndex, downloadedStickers.value() + 1, set.stickers.size());
downloadedStickers.increase();
sync.set(true);
}
});
}
});
while (!sync.get())
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
}
stickers.add(new Sticker(fileId, emoji));
}
return stickers;
}
private void runOnUiThread(Runnable r) {
@@ -438,15 +345,13 @@ public class TelegramStickersGrabber {
void onNetError();
}
public class TSGException extends Exception {
private static final long serialVersionUID = 7866915509988422944L;
public static class TSGException extends Exception {
private TSGException(String message) {
super(message);
}
}
private class StickerSet {
private static class StickerSet {
String id;
String name;
String version;
@@ -460,7 +365,7 @@ public class TelegramStickersGrabber {
}
}
private class Sticker {
private static class Sticker {
String emoji;
String fileId;
@@ -1,125 +0,0 @@
package com.arthenica.ffmpegkit;
import android.os.Build;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import java.util.Collections;
import java.util.List;
public class NativeLoader {
static final String[] FFMPEG_LIBRARIES = {"avutil", "swresample", "avcodec", "avformat", "avfilter", "avdevice"};
private static void loadLibrary(String libraryName) {
System.loadLibrary(libraryName);
}
private static List<String> loadExternalLibraries() {
//return Packages.getExternalLibraries();
return Collections.emptyList();
}
private static String loadNativeAbi() {
return AbiDetect.getNativeAbi();
}
static String loadAbi() {
return AbiDetect.getAbi();
}
static String loadPackageName() {
return Packages.getPackageName();
}
static String loadVersion() {
return FFmpegKitConfig.getVersion();
}
static boolean loadIsLTSBuild() {
return AbiDetect.isNativeLTSBuild();
}
static int loadLogLevel() {
return FFmpegKitConfig.getNativeLogLevel();
}
static String loadBuildDate() {
return FFmpegKitConfig.getBuildDate();
}
static void enableRedirection() {
FFmpegKitConfig.enableRedirection();
}
static void loadFFmpegKitAbiDetect() {
loadLibrary("ffmpegkit_abidetect");
}
static boolean loadFFmpeg() {
boolean nativeFFmpegLoaded = false;
boolean nativeFFmpegTriedAndFailed = false;
if (AbiDetect.ARM_V7A.equals(loadNativeAbi())) {
try {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary + "_neon");
}
nativeFFmpegLoaded = true;
} catch (final Error e) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("NEON supported armeabi-v7a ffmpeg library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
nativeFFmpegTriedAndFailed = true;
}
}
if (!nativeFFmpegLoaded) {
for (String ffmpegLibrary : FFMPEG_LIBRARIES) {
loadLibrary(ffmpegLibrary);
}
}
return nativeFFmpegTriedAndFailed;
}
static void loadFFmpegKit(boolean nativeFFmpegTriedAndFailed) {
boolean nativeFFmpegKitLoaded = false;
if (!nativeFFmpegTriedAndFailed && "arm-v7a".equals(loadNativeAbi())) {
try {
loadLibrary("ffmpegkit_armv7a_neon");
nativeFFmpegKitLoaded = true;
AbiDetect.setArmV7aNeonLoaded();
} catch (Error e) {
Log.i("ffmpeg-kit", String.format("NEON supported armeabi-v7a ffmpegkit library not found. Loading default armeabi-v7a library.%s", Exceptions.getStackTraceString(e)));
}
}
if (!nativeFFmpegKitLoaded) {
loadLibrary("ffmpegkit");
}
}
static String getDeviceDebugInformation() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("brand: ");
stringBuilder.append(Build.BRAND);
stringBuilder.append(", model: ");
stringBuilder.append(Build.MODEL);
stringBuilder.append(", device: ");
stringBuilder.append(Build.DEVICE);
stringBuilder.append(", api level: ");
stringBuilder.append(Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 21) {
stringBuilder.append(", abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_ABIS));
stringBuilder.append(", 32bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_32_BIT_ABIS));
stringBuilder.append(", 64bit abis: ");
stringBuilder.append(FFmpegKitConfig.argumentsToString(Build.SUPPORTED_64_BIT_ABIS));
} else {
stringBuilder.append(", cpu abis: ");
stringBuilder.append(Build.CPU_ABI);
stringBuilder.append(", cpu abi2s: ");
stringBuilder.append(Build.CPU_ABI2);
}
return stringBuilder.toString();
}
}
@@ -1,732 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>Abstract class that includes common variables and methods for different <code>Exceptions</code> implementations.
*
* @author Taner Sener
* @since 0.1.0
*/
public abstract class AbstractExceptions {
/**
* <p>Default max depth used in methods which do not have a maxDepth argument.
*/
public static final int DEFAULT_MAX_DEPTH = 10;
/**
* <p>Default value for ignoring all causes when stack trace elements are printed or converted to string.
*/
public static final boolean DEFAULT_IGNORE_ALL_CAUSES = false;
/**
* <p>Stores global root package names.
*/
public static final Set<String> rootPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global group package names.
*/
public static final Set<String> groupPackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore package names.
*/
public static final Set<String> ignorePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores global ignore cause package names.
*/
public static final Set<String> ignoreCausePackageSet = Collections.synchronizedSet(new HashSet<String>());
/**
* <p>Stores the value of global ignore all causes option.
*/
public static boolean ignoreAllCauses = DEFAULT_IGNORE_ALL_CAUSES;
/**
* <p>Stores the global stack trace serializer implementation.
*/
public static StackTraceElementSerializer stackTraceElementSerializer;
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
rootPackageSet.add(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
rootPackageSet.clear();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
groupPackageSet.add(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
groupPackageSet.clear();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return stackTraceElementSerializer;
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.stackTraceElementSerializer = stackTraceElementSerializer;
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
ignorePackageSet.add(packageString);
if (ignoreCauseClasses) {
ignoreCausePackageSet.add(packageString);
}
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
ignorePackageSet.clear();
ignoreCausePackageSet.clear();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return ignoreAllCauses;
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.ignoreAllCauses = ignoreAllCauses;
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, rootPackageSet, groupPackageSet, ignorePackageSet, 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), new HashSet<String>(), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return getStackTraceString(throwable, false, Collections.singleton(rootPackage), Collections.singleton(groupPackage), new HashSet<String>(), 0, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return getStackTraceString(throwable, false, new HashSet<String>(), new HashSet<String>(), new HashSet<String>(), maxDepth, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using parameters provided.
*
* @param throwable parent throwable
* @param isCause throwable is a cause or not
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean isCause, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final int maxDepth, final boolean ignoreAllCauses) {
final StringBuilder builder = new StringBuilder();
if (throwable == null) {
return "";
}
final String className = throwable.getClass().getName();
final StackTraceElement[] stackTraceElements;
if (maxDepth > 0) {
stackTraceElements = getStackTrace(throwable, maxDepth);
} else {
stackTraceElements = getStackTrace(throwable, rootPackageSet, ignorePackageSet);
}
String message = throwable.getLocalizedMessage();
if (isEmpty(message)) {
message = throwable.getMessage();
}
/* PRINT MESSAGE */
if (isCause) {
builder.append(System.lineSeparator());
builder.append("Caused by: ");
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
} else {
builder.append(System.lineSeparator());
builder.append(className);
if (!isEmpty(message)) {
builder.append(": ");
builder.append(message);
}
}
/* PRINT SHORT STACK TRACE */
String currentGroupPackage = null;
StackTraceElement firstStackTraceElementInTheGroup = null;
int currentGroupCount = 0;
for (StackTraceElement traceElement : stackTraceElements) {
String traceElementClassName = traceElement.getClassName();
String groupPackageMatch = getContainingPackage(traceElementClassName, groupPackageSet);
if (groupPackageMatch != null) {
if (!groupPackageMatch.equals(currentGroupPackage)) {
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
currentGroupPackage = groupPackageMatch;
firstStackTraceElementInTheGroup = traceElement;
currentGroupCount = 1;
} else {
currentGroupCount++;
}
} else {
currentGroupCount = appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
builder.append(System.lineSeparator());
builder.append("\tat ");
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
builder.append(stackTraceElementSerializer.toString(traceElement));
}
currentGroupPackage = null;
}
}
appendStackTraceGroupElement(builder, currentGroupPackage, currentGroupCount, firstStackTraceElementInTheGroup);
final Throwable cause = throwable.getCause();
if (cause != null && !containsPackage(className, ignoreCausePackageSet) && !ignoreAllCauses) {
builder.append(getStackTraceString(cause, true, rootPackageSet, groupPackageSet, ignorePackageSet, maxDepth, ignoreAllCauses));
}
return builder.toString();
}
/**
* <p>Appends stack trace group information for the given parameters.
*
* @param stringBuilder string builder that group stack trace information will be appended
* @param currentGroupPackage package name of the current group
* @param numberOfElementsInTheCurrentGroup number of elements in the current group
* @param firstStackTraceElementInTheGroup first stack trace element of this group
* @return new value for the group element count
*/
public static int appendStackTraceGroupElement(final StringBuilder stringBuilder, final String currentGroupPackage, final int numberOfElementsInTheCurrentGroup, final StackTraceElement firstStackTraceElementInTheGroup) {
if (numberOfElementsInTheCurrentGroup > 0) {
if (stackTraceElementSerializer == null) {
throw new IllegalArgumentException("Stack trace element serializer not initialized.");
} else {
stringBuilder.append((numberOfElementsInTheCurrentGroup == 1) ? stackTraceElementSerializer.toString(firstStackTraceElementInTheGroup) : String.format("%s%s ... %d more", stackTraceElementSerializer.getModuleName(firstStackTraceElementInTheGroup), currentGroupPackage, (numberOfElementsInTheCurrentGroup - 1)));
}
}
return 0;
}
/**
* <p>Returns true if package of the given class name matches one of package names provided in
* <code>packageSet</code>.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return true if package of the given class name matches one of package names provided in <code>packageSet</code>,
* false otherwise
*/
public static boolean containsPackage(final String fullClassName, final Set<String> packageSet) {
return getContainingPackage(fullClassName, packageSet) != null;
}
/**
* <p>Checks if one of the package names in the <code>packageSet</code> matches the package of the given class name
* and returns the matching package name.
*
* @param fullClassName fully qualified class name
* @param packageSet set containing package names
* @return one of the package names in the <code>packageSet</code> that matches the package of the given class name
* or null if no matching package name is found in the <code>packageSet</code>
*/
public static String getContainingPackage(final String fullClassName, final Set<String> packageSet) {
for (final String parentExceptionPackage : packageSet) {
if (fullClassName.startsWith(parentExceptionPackage)) {
return parentExceptionPackage;
}
}
return null;
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
final StringBuilder messageBuilder = new StringBuilder();
getAllMessages(throwable, messageBuilder);
return messageBuilder.toString();
}
/**
* <p>Adds all messages found in the exception chain of the <code>throwable</code> into the given message builder.
*
* @param throwable parent throwable
* @param messageBuilder message builder
*/
public static void getAllMessages(final Throwable throwable, final StringBuilder messageBuilder) {
if (throwable != null) {
final String message = throwable.getMessage();
if (!isEmpty(message)) {
if (messageBuilder.length() != 0) {
messageBuilder.append(System.lineSeparator());
messageBuilder.append(" - Caused by: ");
}
messageBuilder.append(message);
}
getAllMessages(throwable.getCause(), messageBuilder);
}
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using elements found until the maxDepth and
* elements of it.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be returned
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final int maxDepth) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
if (throwable != null) {
StackTraceElement[] stackTrace = throwable.getStackTrace();
for (int i = 0; (i < stackTrace.length) && (i < maxDepth); i++) {
list.add(stackTrace[i]);
}
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Builds a smart stack trace for the given <code>throwable</code> using packages provided and returns elements
* of it.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return an array containing stack trace elements
*/
public static StackTraceElement[] getStackTrace(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> ignorePackageSet) {
final ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
final ArrayList<StackTraceElement> partialList = new ArrayList<StackTraceElement>();
if (throwable != null) {
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
final String className = stackTraceElement.getClassName();
if (!isEmpty(className)) {
if (containsPackage(className, rootPackageSet)) {
list.addAll(partialList);
list.add(stackTraceElement);
} else if (!containsPackage(className, ignorePackageSet)) {
partialList.add(stackTraceElement);
}
}
}
}
if (list.isEmpty()) {
list.addAll(partialList);
}
return list.toArray(new StackTraceElement[0]);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return containsCause(throwable, causeClass, null);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return (searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH) != null);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return getCause(throwable, DEFAULT_MAX_DEPTH);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
if (throwable == null) {
return null;
}
if (maxDepth <= 0) {
return throwable;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return throwable;
} else {
return getCause(cause, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return searchCause(throwable, causeClass, null, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link #DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return searchCause(throwable, causeClass, causeMessage, DEFAULT_MAX_DEPTH);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
if (throwable == null) {
return null;
}
if (isEmpty(causeMessage)) {
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
} else {
if (throwable.getClass().equals(causeClass) && getAllMessages(throwable).toLowerCase().contains(causeMessage.toLowerCase())) {
return throwable;
}
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, causeMessage, maxDepth - 1);
}
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
if (throwable == null) {
return null;
}
if (throwable.getClass().equals(causeClass)) {
return throwable;
}
if (maxDepth <= 0) {
return null;
}
final Throwable cause = throwable.getCause();
if (cause == null) {
return null;
} else {
return searchCause(cause, causeClass, maxDepth - 1);
}
}
/**
* <p>Checks if given string is empty or not.
*
* @param value string to check
* @return true if given string is null or has zero length after trim(), false otherwise
*/
public static boolean isEmpty(final String value) {
if (value == null) {
return true;
}
return value.trim().length() == 0;
}
/**
* <p>Returns the package name of this class.
*
* @param className fully qualified class name
* @return name of the package
*/
public static String packageName(final String className) {
if (className == null) {
return "";
}
int index = className.lastIndexOf(".");
if (index >= 0) {
return className.substring(0, index);
} else {
return "";
}
}
}
@@ -1,74 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception;
/**
* <p>Base interface for classes which can serialize {@link StackTraceElement} objects.
*
* @author Taner Sener
* @since 0.1.0
*/
public interface StackTraceElementSerializer {
/**
* <p>Converts the given <code>stackTraceElement</code> into a string.
*
* @param stackTraceElement stack trace element to convert
* @return string representing the given <code>stackTraceElement</code>
*/
String toString(final StackTraceElement stackTraceElement);
/**
* <p>Returns module name for the given <code>stackTraceElement</code>.
*
* @param stackTraceElement stack trace element to use
* @return module name of the given <code>stackTraceElement</code> or an empty string if implementation does not
* support printing module names
*/
String getModuleName(final StackTraceElement stackTraceElement);
/**
* <p>Returns string definition used to print native methods.
*
* @return string definition used to print native methods
*/
String getNativeMethodDefinition();
/**
* <p>Returns string definition used to print methods with unknown source.
*
* @return string definition used to print methods with unknown source
*/
String getUnknownSourceDefinition();
}
@@ -1,423 +0,0 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2020, Taner Sener
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.arthenica.smartexception.java;
import com.arthenica.smartexception.AbstractExceptions;
import com.arthenica.smartexception.StackTraceElementSerializer;
import java.util.Set;
/**
* <p>Utility class to handle Throwable objects and stack traces. This implementation is compatible with all Java and
* Android versions.
*
* @author Taner Sener
* @since 0.1.0
*/
public class Exceptions {
static {
AbstractExceptions.setStackTraceElementSerializer(new StackTraceElementSerializer() {
@Override
public String toString(StackTraceElement stackTraceElement) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(stackTraceElement.getClassName());
stringBuilder.append(".");
stringBuilder.append(stackTraceElement.getMethodName());
if (stackTraceElement.isNativeMethod()) {
stringBuilder.append("(Native Method)");
} else if ((stackTraceElement.getFileName() != null) && (stackTraceElement.getFileName().length() > 0)) {
stringBuilder.append("(");
stringBuilder.append(stackTraceElement.getFileName());
if (stackTraceElement.getLineNumber() >= 0) {
stringBuilder.append(":");
stringBuilder.append(stackTraceElement.getLineNumber());
}
stringBuilder.append(")");
} else {
stringBuilder.append("(Unknown Source)");
}
return stringBuilder.toString();
}
@Override
public String getModuleName(StackTraceElement stackTraceElement) {
return "";
}
@Override
public String getNativeMethodDefinition() {
return "(Native Method)";
}
@Override
public String getUnknownSourceDefinition() {
return "(Unknown Source)";
}
});
}
/**
* <p>Registers a new root package.
*
* @param packageString root package name to register
*/
public static void registerRootPackage(final String packageString) {
AbstractExceptions.registerRootPackage(packageString);
}
/**
* <p>Clears previously registered root packages.
*/
public static void clearRootPackages() {
AbstractExceptions.clearRootPackages();
}
/**
* <p>Registers a new group package.
*
* @param packageString group package name to register
*/
public static void registerGroupPackage(final String packageString) {
AbstractExceptions.registerGroupPackage(packageString);
}
/**
* <p>Clears previously registered group packages.
*/
public static void clearGroupPackages() {
AbstractExceptions.clearGroupPackages();
}
/**
* <p>Returns the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @return current stack trace serializer implementation
*/
public static StackTraceElementSerializer getStackTraceElementSerializer() {
return AbstractExceptions.getStackTraceElementSerializer();
}
/**
* <p>Sets the global stack trace serializer implementation which is used to serialize {@link StackTraceElement}
* objects in <code>getStackTraceString</code> methods.
*
* @param stackTraceElementSerializer new stack trace serializer implementation
*/
public static void setStackTraceElementSerializer(final StackTraceElementSerializer stackTraceElementSerializer) {
AbstractExceptions.setStackTraceElementSerializer(stackTraceElementSerializer);
}
/**
* <p>Registers a new ignore package.
*
* @param packageString ignore package name to register
* @param ignoreCauseClasses ignore cause classes from this package too
*/
public static void registerIgnorePackage(final String packageString, final boolean ignoreCauseClasses) {
AbstractExceptions.registerIgnorePackage(packageString, ignoreCauseClasses);
}
/**
* <p>Clears previously registered ignore packages.
*/
public static void clearIgnorePackages() {
AbstractExceptions.clearIgnorePackages();
}
/**
* <p>Returns the value of ignore all causes option.
*
* @return the value of global ignore all causes option. If value is true then stack trace elements printed or
* converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static boolean getIgnoreAllCauses() {
return AbstractExceptions.getIgnoreAllCauses();
}
/**
* <p>Sets the value of ignore all causes option.
*
* @param ignoreAllCauses new global ignore all causes option. If value is true then stack trace elements printed
* or converted to string will not include causes. If value is false causes will be appended to the stack trace of
* the main throwable.
*/
public static void setIgnoreAllCauses(final boolean ignoreAllCauses) {
AbstractExceptions.setIgnoreAllCauses(ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable) {
return AbstractExceptions.getStackTraceString(throwable);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code>.
*
* <p>This method uses root packages registered by {@link #registerRootPackage(String)}, group packages registered
* by {@link #registerGroupPackage(String)} and ignore packages registered by
* {@link #registerIgnorePackage(String, boolean)} to build the smart stack trace.
*
* @param throwable parent throwable
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using packages provided.
*
* @param throwable parent throwable
* @param rootPackageSet root packages to use for building the stack trace
* @param groupPackageSet group packages to use for building the stack trace
* @param ignorePackageSet ignore packages to use for building the stack trace
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final Set<String> rootPackageSet, final Set<String> groupPackageSet, final Set<String> ignorePackageSet, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, rootPackageSet, groupPackageSet, ignorePackageSet, ignoreAllCauses);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using root package provided.
*
* @param throwable parent throwable
* @param rootPackage root package to use for building the stack trace
* @param groupPackage group package to use for building the stack trace
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final String rootPackage, final String groupPackage) {
return AbstractExceptions.getStackTraceString(throwable, rootPackage, groupPackage);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth);
}
/**
* <p>Returns the smart stack trace for the given <code>throwable</code> using elements found until the maxDepth.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be used
* @param ignoreAllCauses ignore all causes in the exception chain
* @return a string containing the smart stack trace for the given <code>throwable</code>
*/
public static String getStackTraceString(final Throwable throwable, final int maxDepth, final boolean ignoreAllCauses) {
return AbstractExceptions.getStackTraceString(throwable, maxDepth, ignoreAllCauses);
}
/**
* <p>Returns all messages found in the exception chain of the <code>throwable</code> as a single string.
*
* @param throwable parent throwable
* @return a string containing all messages found in the exception chain
*/
public static String getAllMessages(final Throwable throwable) {
return AbstractExceptions.getAllMessages(throwable);
}
/**
* <p>Returns true if the given cause class is found in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return true if the given cause class is found in the exception chain of the <code>throwable</code>, false
* otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.containsCause(throwable, causeClass);
}
/**
* <p>Returns true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return true if the given cause class and cause message is found in the exception chain of the
* <code>throwable</code>, false otherwise
*/
public static boolean containsCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.containsCause(throwable, causeClass, causeMessage);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
*
* @param throwable parent throwable
* @return the cause of the <code>throwable</code> found or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable) {
return AbstractExceptions.getCause(throwable);
}
/**
* <p>Returns the cause of the <code>throwable</code> by walking through the exception chain.
* <br>
* <br>
* <p>Note that this method walks through the exception chain up to given <code>maxDepth</code>. If exception chain
* includes more items than <code>maxDepth</code>, cause found at <code>maxDepth</code> level is returned.
*
* @param throwable parent throwable
* @param maxDepth max depth in exception chain that will be searched
* @return the cause of the <code>throwable</code> found until <code>maxDepth</code> is reached in the exception
* chain or null if <code>throwable</code> does not have a cause
*/
public static Throwable getCause(final Throwable throwable, final int maxDepth) {
return AbstractExceptions.getCause(throwable, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass) {
return AbstractExceptions.searchCause(throwable, causeClass);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until
* {@link AbstractExceptions#DEFAULT_MAX_DEPTH}.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage);
}
/**
* <p>Searches for the given cause class and cause message in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param causeMessage message to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
* and cause message
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final String causeMessage, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, causeMessage, maxDepth);
}
/**
* <p>Searches for the given cause class in the exception chain of the <code>throwable</code>.
* <br>
* <br>
* <p>Search starts from the <code>throwable</code> itself and goes through the exception chain up until the given
* <code>maxDepth</code>.
*
* @param throwable parent throwable
* @param causeClass class to search for
* @param maxDepth max depth in exception chain that will be searched
* @return the <code>throwable</code> found or null if no class in exception chain matches the given cause class
*/
public static Throwable searchCause(final Throwable throwable, final Class<?> causeClass, final int maxDepth) {
return AbstractExceptions.searchCause(throwable, causeClass, maxDepth);
}
}
@@ -35,243 +35,289 @@ import java.util.Map.Entry;
*/
public class ArscBlamer {
/** Maps package key pool indices to blamed resources. */
private final Map<PackageChunk, List<ResourceEntry>[]> keyToBlame = new HashMap<>();
/**
* Maps package key pool indices to blamed resources.
*/
private final Map<PackageChunk, List<ResourceEntry>[]> keyToBlame = new HashMap<>();
/** Maps types to blamed resources. */
private final Map<PackageChunk, List<ResourceEntry>[]> typeToBlame = new HashMap<>();
/**
* Maps types to blamed resources.
*/
private final Map<PackageChunk, List<ResourceEntry>[]> typeToBlame = new HashMap<>();
/** Maps package to blamed resources. */
private final Multimap<PackageChunk, ResourceEntry> packageToBlame = HashMultimap.create();
/**
* Maps package to blamed resources.
*/
private final Multimap<PackageChunk, ResourceEntry> packageToBlame = HashMultimap.create();
/** Maps string indices to blamed resources. */
private final List<ResourceEntry>[] stringToBlame;
/**
* Maps string indices to blamed resources.
*/
private final List<ResourceEntry>[] stringToBlame;
/** Maps type chunk entries to blamed resources. */
private final Multimap<TypeChunk.Entry, ResourceEntry> typeEntryToBlame = HashMultimap.create();
/**
* Maps type chunk entries to blamed resources.
*/
private final Multimap<TypeChunk.Entry, ResourceEntry> typeEntryToBlame = HashMultimap.create();
/**
* This is the {@link ResourceTableChunk} inside of the resources.arsc file in the APK.
*/
private final ResourceTableChunk resourceTable;
/**
* Maps resources to the type chunk entries they reference.
*/
private Multimap<ResourceEntry, TypeChunk.Entry> resourceEntries;
/**
* Maps resources which have no base config to the type chunk entries they reference.
*/
private Multimap<ResourceEntry, TypeChunk.Entry> baselessKeys;
/**
* Contains all of the type chunks in {@link #resourceTable}.
*/
private List<TypeChunk> typeChunks;
/** Maps resources to the type chunk entries they reference. */
private Multimap<ResourceEntry, TypeChunk.Entry> resourceEntries;
/** Maps resources which have no base config to the type chunk entries they reference. */
private Multimap<ResourceEntry, TypeChunk.Entry> baselessKeys;
/** Contains all of the type chunks in {@link #resourceTable}. */
private List<TypeChunk> typeChunks;
/** This is the {@link ResourceTableChunk} inside of the resources.arsc file in the APK. */
private final ResourceTableChunk resourceTable;
/**
* Creates a new {@link ArscBlamer}.
*
* @param resourceTable The resources.arsc resource table to blame.
*/
public ArscBlamer(ResourceTableChunk resourceTable) {
this.resourceTable = resourceTable;
this.stringToBlame = createEntryListArray(resourceTable.getStringPool().getStringCount());
}
/** Generates blame mappings. */
public void blame() {
Multimap<ResourceEntry, TypeChunk.Entry> entries = getResourceEntries();
for (Entry<ResourceEntry, Collection<TypeChunk.Entry>> entry : entries.asMap().entrySet()) {
ResourceEntry resourceEntry = entry.getKey();
PackageChunk packageChunk = Preconditions.checkNotNull(
resourceTable.getPackage(resourceEntry.packageName()));
int keyCount = packageChunk.getKeyStringPool().getStringCount();
int typeCount = packageChunk.getTypeStringPool().getStringCount();
for (TypeChunk.Entry chunkEntry : entry.getValue()) {
blameKeyOrType(keyToBlame, packageChunk, chunkEntry.keyIndex(), resourceEntry, keyCount);
blameKeyOrType(typeToBlame, packageChunk, chunkEntry.parent().getId() - 1, resourceEntry,
typeCount);
blameFromTypeChunkEntry(chunkEntry);
}
blamePackage(packageChunk, resourceEntry);
/**
* Creates a new {@link ArscBlamer}.
*
* @param resourceTable The resources.arsc resource table to blame.
*/
public ArscBlamer(ResourceTableChunk resourceTable) {
this.resourceTable = resourceTable;
this.stringToBlame = createEntryListArray(resourceTable.getStringPool().getStringCount());
}
Multimaps.invertFrom(entries, typeEntryToBlame);
for (TypeChunk.Entry entry : typeEntryToBlame.keySet()) {
blameFromTypeChunkEntry(entry);
}
}
private void blameKeyOrType(Map<PackageChunk, List<ResourceEntry>[]> keyOrType,
PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) {
if (!keyOrType.containsKey(packageChunk)) {
keyOrType.put(packageChunk, createEntryListArray(entryCount));
}
keyOrType.get(packageChunk)[keyIndex].add(entry);
}
private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) {
packageToBlame.put(packageChunk, entry);
}
private void blameFromTypeChunkEntry(TypeChunk.Entry chunkEntry) {
for (BinaryResourceValue value : getAllResourceValues(chunkEntry)) {
for (ResourceEntry entry : typeEntryToBlame.get(chunkEntry)) {
switch (value.type()) {
case STRING:
blameString(value.data(), entry);
break;
default:
break;
private static List<ResourceEntry>[] createEntryListArray(int size) {
ArrayListResourceEntry[] result = new ArrayListResourceEntry[size];
for (int i = 0; i < size; ++i) {
result[i] = new ArrayListResourceEntry();
}
}
}
}
/** Returns all {@link BinaryResourceValue} for a single {@code entry}. */
private Collection<BinaryResourceValue> getAllResourceValues(TypeChunk.Entry entry) {
Set<BinaryResourceValue> values = new HashSet<BinaryResourceValue>();
BinaryResourceValue binaryResourceValue = entry.value();
if (binaryResourceValue != null) {
values.add(binaryResourceValue);
}
for (BinaryResourceValue value : entry.values().values()) {
values.add(value);
}
return values;
}
private void blameString(int stringIndex, ResourceEntry entry) {
stringToBlame[stringIndex].add(entry);
}
/** Must first call {@link #blame}. */
public Map<PackageChunk, List<ResourceEntry>[]> getKeyToBlamedResources() {
return Collections.unmodifiableMap(keyToBlame);
}
/** Must first call {@link #blame}. */
public Map<PackageChunk, List<ResourceEntry>[]> getTypeToBlamedResources() {
return Collections.unmodifiableMap(typeToBlame);
}
/** Must first call {@link #blame}. */
public Multimap<PackageChunk, ResourceEntry> getPackageToBlamedResources() {
return Multimaps.unmodifiableMultimap(packageToBlame);
}
/** Must first call {@link #blame}. */
public List<ResourceEntry>[] getStringToBlamedResources() {
return stringToBlame;
}
/** Must first call {@link #blame}. */
public Multimap<TypeChunk.Entry, ResourceEntry> getTypeEntryToBlamedResources() {
return Multimaps.unmodifiableMultimap(typeEntryToBlame);
}
/** Returns a multimap of keys for which there is no default resource. */
public Multimap<ResourceEntry, TypeChunk.Entry> getBaselessKeys() {
if (baselessKeys != null) {
return baselessKeys;
}
Multimap<ResourceEntry, TypeChunk.Entry> result = HashMultimap.create();
for (Entry<ResourceEntry, Collection<TypeChunk.Entry>> entry
: getResourceEntries().asMap().entrySet()) {
Collection<TypeChunk.Entry> chunkEntries = entry.getValue();
if (!hasBaseConfiguration(chunkEntries)) {
result.putAll(entry.getKey(), chunkEntries);
}
}
baselessKeys = result;
return result;
}
/** Returns a multimap of resource entries to the chunk entries they reference in this APK. */
public Multimap<ResourceEntry, TypeChunk.Entry> getResourceEntries() {
if (resourceEntries != null) {
return resourceEntries;
}
Multimap<ResourceEntry, TypeChunk.Entry> result = HashMultimap.create();
for (TypeChunk typeChunk : getTypeChunks()) {
for (TypeChunk.Entry entry : typeChunk.getEntries().values()) {
result.put(ResourceEntry.create(entry), entry);
}
}
resourceEntries = result;
return result;
}
/** Returns all {@link TypeChunk} in resources.arsc. */
public List<TypeChunk> getTypeChunks() {
if (typeChunks != null) {
return typeChunks;
}
List<TypeChunk> result = new ArrayList<>();
for (PackageChunk packageChunk : resourceTable.getPackages()) {
for (TypeChunk typeChunk : packageChunk.getTypeChunks()) {
result.add(typeChunk);
}
}
typeChunks = result;
return result;
}
private boolean hasBaseConfiguration(Collection<TypeChunk.Entry> entries) {
for (TypeChunk.Entry entry : entries) {
if (entry.parent().getConfiguration().isDefault()) {
return true;
}
}
return false;
}
private static List<ResourceEntry>[] createEntryListArray(int size) {
ArrayListResourceEntry[] result = new ArrayListResourceEntry[size];
for (int i = 0; i < size; ++i) {
result[i] = new ArrayListResourceEntry();
}
return result;
}
/** Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}. */
private static class ArrayListResourceEntry extends ArrayList<ResourceEntry> {
private ArrayListResourceEntry() {
super(2); // ~90-95% of these lists end up with only 1 or 2 elements.
}
}
/** Describes a single resource entry. */
public static class ResourceEntry {
private final String packageName;
private final String typeName;
private final String entryName;
static ResourceEntry create(TypeChunk.Entry entry) {
PackageChunk packageChunk = Preconditions.checkNotNull(entry.parent().getPackageChunk());
String packageName = packageChunk.getPackageName();
String typeName = entry.typeName();
String entryName = entry.key();
return new ResourceEntry(packageName, typeName, entryName);
return result;
}
private ResourceEntry(String packageName, String typeName, String entryName) {
this.packageName = packageName;
this.typeName = typeName;
this.entryName = entryName;
/**
* Generates blame mappings.
*/
public void blame() {
Multimap<ResourceEntry, TypeChunk.Entry> entries = getResourceEntries();
for (Entry<ResourceEntry, Collection<TypeChunk.Entry>> entry : entries.asMap().entrySet()) {
ResourceEntry resourceEntry = entry.getKey();
PackageChunk packageChunk = Preconditions.checkNotNull(
resourceTable.getPackage(resourceEntry.packageName()));
int keyCount = packageChunk.getKeyStringPool().getStringCount();
int typeCount = packageChunk.getTypeStringPool().getStringCount();
for (TypeChunk.Entry chunkEntry : entry.getValue()) {
blameKeyOrType(keyToBlame, packageChunk, chunkEntry.keyIndex(), resourceEntry, keyCount);
blameKeyOrType(typeToBlame, packageChunk, chunkEntry.parent().getId() - 1, resourceEntry,
typeCount);
blameFromTypeChunkEntry(chunkEntry);
}
blamePackage(packageChunk, resourceEntry);
}
Multimaps.invertFrom(entries, typeEntryToBlame);
for (TypeChunk.Entry entry : typeEntryToBlame.keySet()) {
blameFromTypeChunkEntry(entry);
}
}
public String packageName() { return packageName; }
public String typeName() { return typeName; }
public String entryName() { return entryName; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResourceEntry that = (ResourceEntry)o;
return Objects.equals(packageName, that.packageName) &&
Objects.equals(typeName, that.typeName) &&
Objects.equals(entryName, that.entryName);
private void blameKeyOrType(Map<PackageChunk, List<ResourceEntry>[]> keyOrType,
PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) {
if (!keyOrType.containsKey(packageChunk)) {
keyOrType.put(packageChunk, createEntryListArray(entryCount));
}
keyOrType.get(packageChunk)[keyIndex].add(entry);
}
@Override
public int hashCode() {
return Objects.hash(packageName, typeName, entryName);
private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) {
packageToBlame.put(packageChunk, entry);
}
private void blameFromTypeChunkEntry(TypeChunk.Entry chunkEntry) {
for (BinaryResourceValue value : getAllResourceValues(chunkEntry)) {
for (ResourceEntry entry : typeEntryToBlame.get(chunkEntry)) {
switch (value.type()) {
case STRING:
blameString(value.data(), entry);
break;
default:
break;
}
}
}
}
/**
* Returns all {@link BinaryResourceValue} for a single {@code entry}.
*/
private Collection<BinaryResourceValue> getAllResourceValues(TypeChunk.Entry entry) {
Set<BinaryResourceValue> values = new HashSet<BinaryResourceValue>();
BinaryResourceValue binaryResourceValue = entry.value();
if (binaryResourceValue != null) {
values.add(binaryResourceValue);
}
for (BinaryResourceValue value : entry.values().values()) {
values.add(value);
}
return values;
}
private void blameString(int stringIndex, ResourceEntry entry) {
stringToBlame[stringIndex].add(entry);
}
/**
* Must first call {@link #blame}.
*/
public Map<PackageChunk, List<ResourceEntry>[]> getKeyToBlamedResources() {
return Collections.unmodifiableMap(keyToBlame);
}
/**
* Must first call {@link #blame}.
*/
public Map<PackageChunk, List<ResourceEntry>[]> getTypeToBlamedResources() {
return Collections.unmodifiableMap(typeToBlame);
}
/**
* Must first call {@link #blame}.
*/
public Multimap<PackageChunk, ResourceEntry> getPackageToBlamedResources() {
return Multimaps.unmodifiableMultimap(packageToBlame);
}
/**
* Must first call {@link #blame}.
*/
public List<ResourceEntry>[] getStringToBlamedResources() {
return stringToBlame;
}
/**
* Must first call {@link #blame}.
*/
public Multimap<TypeChunk.Entry, ResourceEntry> getTypeEntryToBlamedResources() {
return Multimaps.unmodifiableMultimap(typeEntryToBlame);
}
/**
* Returns a multimap of keys for which there is no default resource.
*/
public Multimap<ResourceEntry, TypeChunk.Entry> getBaselessKeys() {
if (baselessKeys != null) {
return baselessKeys;
}
Multimap<ResourceEntry, TypeChunk.Entry> result = HashMultimap.create();
for (Entry<ResourceEntry, Collection<TypeChunk.Entry>> entry
: getResourceEntries().asMap().entrySet()) {
Collection<TypeChunk.Entry> chunkEntries = entry.getValue();
if (!hasBaseConfiguration(chunkEntries)) {
result.putAll(entry.getKey(), chunkEntries);
}
}
baselessKeys = result;
return result;
}
/**
* Returns a multimap of resource entries to the chunk entries they reference in this APK.
*/
public Multimap<ResourceEntry, TypeChunk.Entry> getResourceEntries() {
if (resourceEntries != null) {
return resourceEntries;
}
Multimap<ResourceEntry, TypeChunk.Entry> result = HashMultimap.create();
for (TypeChunk typeChunk : getTypeChunks()) {
for (TypeChunk.Entry entry : typeChunk.getEntries().values()) {
result.put(ResourceEntry.create(entry), entry);
}
}
resourceEntries = result;
return result;
}
/**
* Returns all {@link TypeChunk} in resources.arsc.
*/
public List<TypeChunk> getTypeChunks() {
if (typeChunks != null) {
return typeChunks;
}
List<TypeChunk> result = new ArrayList<>();
for (PackageChunk packageChunk : resourceTable.getPackages()) {
for (TypeChunk typeChunk : packageChunk.getTypeChunks()) {
result.add(typeChunk);
}
}
typeChunks = result;
return result;
}
private boolean hasBaseConfiguration(Collection<TypeChunk.Entry> entries) {
for (TypeChunk.Entry entry : entries) {
if (entry.parent().getConfiguration().isDefault()) {
return true;
}
}
return false;
}
/**
* Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}.
*/
private static class ArrayListResourceEntry extends ArrayList<ResourceEntry> {
private ArrayListResourceEntry() {
super(2); // ~90-95% of these lists end up with only 1 or 2 elements.
}
}
/**
* Describes a single resource entry.
*/
public static class ResourceEntry {
private final String packageName;
private final String typeName;
private final String entryName;
private ResourceEntry(String packageName, String typeName, String entryName) {
this.packageName = packageName;
this.typeName = typeName;
this.entryName = entryName;
}
static ResourceEntry create(TypeChunk.Entry entry) {
PackageChunk packageChunk = Preconditions.checkNotNull(entry.parent().getPackageChunk());
String packageName = packageChunk.getPackageName();
String typeName = entry.typeName();
String entryName = entry.key();
return new ResourceEntry(packageName, typeName, entryName);
}
public String packageName() {
return packageName;
}
public String typeName() {
return typeName;
}
public String entryName() {
return entryName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResourceEntry that = (ResourceEntry) o;
return Objects.equals(packageName, that.packageName) &&
Objects.equals(typeName, that.typeName) &&
Objects.equals(entryName, that.entryName);
}
@Override
public int hashCode() {
return Objects.hash(packageName, typeName, entryName);
}
}
}
}
@@ -31,6 +31,148 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
* Describes a particular resource configuration.
*/
public class BinaryResourceConfiguration implements SerializableResource {
/**
* The below constants are from android.content.res.Configuration.
*/
private static final int DENSITY_DPI_UNDEFINED = 0;
private static final int DENSITY_DPI_LDPI = 120;
private static final int DENSITY_DPI_MDPI = 160;
private static final int DENSITY_DPI_TVDPI = 213;
private static final int DENSITY_DPI_HDPI = 240;
private static final int DENSITY_DPI_XHDPI = 320;
private static final int DENSITY_DPI_XXHDPI = 480;
private static final int DENSITY_DPI_XXXHDPI = 640;
private static final int DENSITY_DPI_ANY = 0xFFFE;
private static final int DENSITY_DPI_NONE = 0xFFFF;
private static final Map<Integer, String> DENSITY_DPI_VALUES =
ImmutableMap.<Integer, String>builder()
.put(DENSITY_DPI_UNDEFINED, "")
.put(DENSITY_DPI_LDPI, "ldpi")
.put(DENSITY_DPI_MDPI, "mdpi")
.put(DENSITY_DPI_TVDPI, "tvdpi")
.put(DENSITY_DPI_HDPI, "hdpi")
.put(DENSITY_DPI_XHDPI, "xhdpi")
.put(DENSITY_DPI_XXHDPI, "xxhdpi")
.put(DENSITY_DPI_XXXHDPI, "xxxhdpi")
.put(DENSITY_DPI_ANY, "anydpi")
.put(DENSITY_DPI_NONE, "nodpi")
.build();
private static final int KEYBOARD_NOKEYS = 1;
private static final int KEYBOARD_QWERTY = 2;
private static final int KEYBOARD_12KEY = 3;
private static final Map<Integer, String> KEYBOARD_VALUES = ImmutableMap.of(
KEYBOARD_NOKEYS, "nokeys",
KEYBOARD_QWERTY, "qwerty",
KEYBOARD_12KEY, "12key");
private static final int KEYBOARDHIDDEN_MASK = 0x03;
private static final int KEYBOARDHIDDEN_NO = 1;
private static final int KEYBOARDHIDDEN_YES = 2;
private static final int KEYBOARDHIDDEN_SOFT = 3;
private static final Map<Integer, String> KEYBOARDHIDDEN_VALUES = ImmutableMap.of(
KEYBOARDHIDDEN_NO, "keysexposed",
KEYBOARDHIDDEN_YES, "keyshidden",
KEYBOARDHIDDEN_SOFT, "keyssoft");
private static final int NAVIGATION_NONAV = 1;
private static final int NAVIGATION_DPAD = 2;
private static final int NAVIGATION_TRACKBALL = 3;
private static final int NAVIGATION_WHEEL = 4;
private static final Map<Integer, String> NAVIGATION_VALUES = ImmutableMap.of(
NAVIGATION_NONAV, "nonav",
NAVIGATION_DPAD, "dpad",
NAVIGATION_TRACKBALL, "trackball",
NAVIGATION_WHEEL, "wheel");
private static final int NAVIGATIONHIDDEN_MASK = 0x0C;
private static final int NAVIGATIONHIDDEN_NO = 0x04;
private static final int NAVIGATIONHIDDEN_YES = 0x08;
private static final Map<Integer, String> NAVIGATIONHIDDEN_VALUES = ImmutableMap.of(
NAVIGATIONHIDDEN_NO, "navexposed",
NAVIGATIONHIDDEN_YES, "navhidden");
private static final int ORIENTATION_PORTRAIT = 0x01;
private static final int ORIENTATION_LANDSCAPE = 0x02;
private static final Map<Integer, String> ORIENTATION_VALUES = ImmutableMap.of(
ORIENTATION_PORTRAIT, "port",
ORIENTATION_LANDSCAPE, "land");
private static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0;
private static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
private static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
private static final Map<Integer, String> SCREENLAYOUT_LAYOUTDIR_VALUES = ImmutableMap.of(
SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr",
SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl");
private static final int SCREENLAYOUT_LONG_MASK = 0x30;
private static final int SCREENLAYOUT_LONG_NO = 0x10;
private static final int SCREENLAYOUT_LONG_YES = 0x20;
private static final Map<Integer, String> SCREENLAYOUT_LONG_VALUES = ImmutableMap.of(
SCREENLAYOUT_LONG_NO, "notlong",
SCREENLAYOUT_LONG_YES, "long");
private static final int SCREENLAYOUT_ROUND_MASK = 0x03;
private static final int SCREENLAYOUT_ROUND_NO = 0x01;
private static final int SCREENLAYOUT_ROUND_YES = 0x02;
private static final Map<Integer, String> SCREENLAYOUT_ROUND_VALUES = ImmutableMap.of(
SCREENLAYOUT_ROUND_NO, "notround",
SCREENLAYOUT_ROUND_YES, "round");
private static final int SCREENLAYOUT_SIZE_MASK = 0x0F;
private static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
private static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
private static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
private static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
private static final Map<Integer, String> SCREENLAYOUT_SIZE_VALUES = ImmutableMap.of(
SCREENLAYOUT_SIZE_SMALL, "small",
SCREENLAYOUT_SIZE_NORMAL, "normal",
SCREENLAYOUT_SIZE_LARGE, "large",
SCREENLAYOUT_SIZE_XLARGE, "xlarge");
private static final int TOUCHSCREEN_NOTOUCH = 1;
private static final int TOUCHSCREEN_FINGER = 3;
private static final Map<Integer, String> TOUCHSCREEN_VALUES = ImmutableMap.of(
TOUCHSCREEN_NOTOUCH, "notouch",
TOUCHSCREEN_FINGER, "finger");
private static final int UI_MODE_NIGHT_MASK = 0x30;
private static final int UI_MODE_NIGHT_NO = 0x10;
private static final int UI_MODE_NIGHT_YES = 0x20;
private static final Map<Integer, String> UI_MODE_NIGHT_VALUES = ImmutableMap.of(
UI_MODE_NIGHT_NO, "notnight",
UI_MODE_NIGHT_YES, "night");
private static final int UI_MODE_TYPE_MASK = 0x0F;
private static final int UI_MODE_TYPE_DESK = 0x02;
private static final int UI_MODE_TYPE_CAR = 0x03;
private static final int UI_MODE_TYPE_TELEVISION = 0x04;
private static final int UI_MODE_TYPE_APPLIANCE = 0x05;
private static final int UI_MODE_TYPE_WATCH = 0x06;
private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
// start - miui
private static final int UI_MODE_TYPE_GODZILLAUI = 0x0b;
private static final int UI_MODE_TYPE_SMALLUI = 0x0c;
private static final int UI_MODE_TYPE_MEDIUMUI = 0x0d;
private static final int UI_MODE_TYPE_LARGEUI = 0x0e;
private static final int UI_MODE_TYPE_HUGEUI = 0x0f;
private static final Map<Integer, String> UI_MODE_TYPE_VALUES =
ImmutableMap.<Integer, String>builder()
.put(UI_MODE_TYPE_DESK, "desk")
.put(UI_MODE_TYPE_CAR, "car")
.put(UI_MODE_TYPE_TELEVISION, "television")
.put(UI_MODE_TYPE_APPLIANCE, "appliance")
.put(UI_MODE_TYPE_WATCH, "watch")
.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset")
.put(UI_MODE_TYPE_GODZILLAUI, "godzillaui")
.put(UI_MODE_TYPE_SMALLUI, "smallui")
.put(UI_MODE_TYPE_MEDIUMUI, "mediumui")
.put(UI_MODE_TYPE_LARGEUI, "largeui")
.put(UI_MODE_TYPE_HUGEUI, "hugeui").build();
/**
* The minimum size in bytes that this configuration must be to contain screen config info.
*/
private static final int SCREEN_CONFIG_MIN_SIZE = 32;
/**
* The minimum size in bytes that this configuration must be to contain screen dp info.
*/
private static final int SCREEN_DP_MIN_SIZE = 36;
/**
* The minimum size in bytes that this configuration must be to contain locale info.
*/
private static final int LOCALE_MIN_SIZE = 48;
/**
* The minimum size in bytes that this config must be to contain the screenConfig extension.
*/
private static final int SCREEN_CONFIG_EXTENSION_MIN_SIZE = 52;
private final int size;
private final int mcc;
private final int mnc;
@@ -53,9 +195,60 @@ public class BinaryResourceConfiguration implements SerializableResource {
private final int screenHeightDp;
private final byte[] localeScript;
private final byte[] localeVariant;
// end - miui
private final int screenLayout2;
private final byte[] unknown;
private BinaryResourceConfiguration(int size,
int mcc,
int mnc,
byte[] language,
byte[] region,
int orientation,
int touchscreen,
int density,
int keyboard,
int navigation,
int inputFlags,
int screenWidth,
int screenHeight,
int sdkVersion,
int minorVersion,
int screenLayout,
int uiMode,
int smallestScreenWidthDp,
int screenWidthDp,
int screenHeightDp,
byte[] localeScript,
byte[] localeVariant,
int screenLayout2,
byte[] unknown) {
this.size = size;
this.mcc = mcc;
this.mnc = mnc;
this.language = language;
this.region = region;
this.orientation = orientation;
this.touchscreen = touchscreen;
this.density = density;
this.keyboard = keyboard;
this.navigation = navigation;
this.inputFlags = inputFlags;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.sdkVersion = sdkVersion;
this.minorVersion = minorVersion;
this.screenLayout = screenLayout;
this.uiMode = uiMode;
this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
this.localeScript = localeScript;
this.localeVariant = localeVariant;
this.screenLayout2 = screenLayout2;
this.unknown = unknown;
}
static BinaryResourceConfiguration create(ByteBuffer buffer) {
int startPosition = buffer.position(); // The starting buffer position to calculate bytes read.
int size = buffer.getInt();
@@ -121,245 +314,6 @@ public class BinaryResourceConfiguration implements SerializableResource {
screenHeightDp, localeScript, localeVariant, screenLayout2, unknown);
}
private BinaryResourceConfiguration(int size,
int mcc,
int mnc,
byte[] language,
byte[] region,
int orientation,
int touchscreen,
int density,
int keyboard,
int navigation,
int inputFlags,
int screenWidth,
int screenHeight,
int sdkVersion,
int minorVersion,
int screenLayout,
int uiMode,
int smallestScreenWidthDp,
int screenWidthDp,
int screenHeightDp,
byte[] localeScript,
byte[] localeVariant,
int screenLayout2,
byte[] unknown) {
this.size = size;
this.mcc = mcc;
this.mnc = mnc;
this.language = language;
this.region = region;
this.orientation = orientation;
this.touchscreen = touchscreen;
this.density = density;
this.keyboard = keyboard;
this.navigation = navigation;
this.inputFlags = inputFlags;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.sdkVersion = sdkVersion;
this.minorVersion = minorVersion;
this.screenLayout = screenLayout;
this.uiMode = uiMode;
this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
this.localeScript = localeScript;
this.localeVariant = localeVariant;
this.screenLayout2 = screenLayout2;
this.unknown = unknown;
}
/**
* The different types of configs that can be present in a {@link BinaryResourceConfiguration}.
*/
public enum Type {
MCC,
MNC,
LANGUAGE_STRING,
REGION_STRING,
SCREEN_LAYOUT_DIRECTION,
SMALLEST_SCREEN_WIDTH_DP,
SCREEN_WIDTH_DP,
SCREEN_HEIGHT_DP,
SCREEN_LAYOUT_SIZE,
SCREEN_LAYOUT_LONG,
SCREEN_LAYOUT_ROUND,
ORIENTATION,
UI_MODE_TYPE,
UI_MODE_NIGHT,
DENSITY_DPI,
TOUCHSCREEN,
KEYBOARD_HIDDEN,
KEYBOARD,
NAVIGATION_HIDDEN,
NAVIGATION,
SDK_VERSION
}
/**
* The below constants are from android.content.res.Configuration.
*/
private static final int DENSITY_DPI_UNDEFINED = 0;
private static final int DENSITY_DPI_LDPI = 120;
private static final int DENSITY_DPI_MDPI = 160;
private static final int DENSITY_DPI_TVDPI = 213;
private static final int DENSITY_DPI_HDPI = 240;
private static final int DENSITY_DPI_XHDPI = 320;
private static final int DENSITY_DPI_XXHDPI = 480;
private static final int DENSITY_DPI_XXXHDPI = 640;
private static final int DENSITY_DPI_ANY = 0xFFFE;
private static final int DENSITY_DPI_NONE = 0xFFFF;
private static final Map<Integer, String> DENSITY_DPI_VALUES =
ImmutableMap.<Integer, String>builder()
.put(DENSITY_DPI_UNDEFINED, "")
.put(DENSITY_DPI_LDPI, "ldpi")
.put(DENSITY_DPI_MDPI, "mdpi")
.put(DENSITY_DPI_TVDPI, "tvdpi")
.put(DENSITY_DPI_HDPI, "hdpi")
.put(DENSITY_DPI_XHDPI, "xhdpi")
.put(DENSITY_DPI_XXHDPI, "xxhdpi")
.put(DENSITY_DPI_XXXHDPI, "xxxhdpi")
.put(DENSITY_DPI_ANY, "anydpi")
.put(DENSITY_DPI_NONE, "nodpi")
.build();
private static final int KEYBOARD_NOKEYS = 1;
private static final int KEYBOARD_QWERTY = 2;
private static final int KEYBOARD_12KEY = 3;
private static final Map<Integer, String> KEYBOARD_VALUES = ImmutableMap.of(
KEYBOARD_NOKEYS, "nokeys",
KEYBOARD_QWERTY, "qwerty",
KEYBOARD_12KEY, "12key");
private static final int KEYBOARDHIDDEN_MASK = 0x03;
private static final int KEYBOARDHIDDEN_NO = 1;
private static final int KEYBOARDHIDDEN_YES = 2;
private static final int KEYBOARDHIDDEN_SOFT = 3;
private static final Map<Integer, String> KEYBOARDHIDDEN_VALUES = ImmutableMap.of(
KEYBOARDHIDDEN_NO, "keysexposed",
KEYBOARDHIDDEN_YES, "keyshidden",
KEYBOARDHIDDEN_SOFT, "keyssoft");
private static final int NAVIGATION_NONAV = 1;
private static final int NAVIGATION_DPAD = 2;
private static final int NAVIGATION_TRACKBALL = 3;
private static final int NAVIGATION_WHEEL = 4;
private static final Map<Integer, String> NAVIGATION_VALUES = ImmutableMap.of(
NAVIGATION_NONAV, "nonav",
NAVIGATION_DPAD, "dpad",
NAVIGATION_TRACKBALL, "trackball",
NAVIGATION_WHEEL, "wheel");
private static final int NAVIGATIONHIDDEN_MASK = 0x0C;
private static final int NAVIGATIONHIDDEN_NO = 0x04;
private static final int NAVIGATIONHIDDEN_YES = 0x08;
private static final Map<Integer, String> NAVIGATIONHIDDEN_VALUES = ImmutableMap.of(
NAVIGATIONHIDDEN_NO, "navexposed",
NAVIGATIONHIDDEN_YES, "navhidden");
private static final int ORIENTATION_PORTRAIT = 0x01;
private static final int ORIENTATION_LANDSCAPE = 0x02;
private static final Map<Integer, String> ORIENTATION_VALUES = ImmutableMap.of(
ORIENTATION_PORTRAIT, "port",
ORIENTATION_LANDSCAPE, "land");
private static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0;
private static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
private static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
private static final Map<Integer, String> SCREENLAYOUT_LAYOUTDIR_VALUES = ImmutableMap.of(
SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr",
SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl");
private static final int SCREENLAYOUT_LONG_MASK = 0x30;
private static final int SCREENLAYOUT_LONG_NO = 0x10;
private static final int SCREENLAYOUT_LONG_YES = 0x20;
private static final Map<Integer, String> SCREENLAYOUT_LONG_VALUES = ImmutableMap.of(
SCREENLAYOUT_LONG_NO, "notlong",
SCREENLAYOUT_LONG_YES, "long");
private static final int SCREENLAYOUT_ROUND_MASK = 0x03;
private static final int SCREENLAYOUT_ROUND_NO = 0x01;
private static final int SCREENLAYOUT_ROUND_YES = 0x02;
private static final Map<Integer, String> SCREENLAYOUT_ROUND_VALUES = ImmutableMap.of(
SCREENLAYOUT_ROUND_NO, "notround",
SCREENLAYOUT_ROUND_YES, "round");
private static final int SCREENLAYOUT_SIZE_MASK = 0x0F;
private static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
private static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
private static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
private static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
private static final Map<Integer, String> SCREENLAYOUT_SIZE_VALUES = ImmutableMap.of(
SCREENLAYOUT_SIZE_SMALL, "small",
SCREENLAYOUT_SIZE_NORMAL, "normal",
SCREENLAYOUT_SIZE_LARGE, "large",
SCREENLAYOUT_SIZE_XLARGE, "xlarge");
private static final int TOUCHSCREEN_NOTOUCH = 1;
private static final int TOUCHSCREEN_FINGER = 3;
private static final Map<Integer, String> TOUCHSCREEN_VALUES = ImmutableMap.of(
TOUCHSCREEN_NOTOUCH, "notouch",
TOUCHSCREEN_FINGER, "finger");
private static final int UI_MODE_NIGHT_MASK = 0x30;
private static final int UI_MODE_NIGHT_NO = 0x10;
private static final int UI_MODE_NIGHT_YES = 0x20;
private static final Map<Integer, String> UI_MODE_NIGHT_VALUES = ImmutableMap.of(
UI_MODE_NIGHT_NO, "notnight",
UI_MODE_NIGHT_YES, "night");
private static final int UI_MODE_TYPE_MASK = 0x0F;
private static final int UI_MODE_TYPE_DESK = 0x02;
private static final int UI_MODE_TYPE_CAR = 0x03;
private static final int UI_MODE_TYPE_TELEVISION = 0x04;
private static final int UI_MODE_TYPE_APPLIANCE = 0x05;
private static final int UI_MODE_TYPE_WATCH = 0x06;
private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
// start - miui
private static final int UI_MODE_TYPE_GODZILLAUI = 0x0b;
private static final int UI_MODE_TYPE_SMALLUI = 0x0c;
private static final int UI_MODE_TYPE_MEDIUMUI = 0x0d;
private static final int UI_MODE_TYPE_LARGEUI = 0x0e;
private static final int UI_MODE_TYPE_HUGEUI = 0x0f;
// end - miui
private static final Map<Integer, String> UI_MODE_TYPE_VALUES =
ImmutableMap.<Integer, String>builder()
.put(UI_MODE_TYPE_DESK, "desk")
.put(UI_MODE_TYPE_CAR, "car")
.put(UI_MODE_TYPE_TELEVISION, "television")
.put(UI_MODE_TYPE_APPLIANCE, "appliance")
.put(UI_MODE_TYPE_WATCH, "watch")
.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset")
.put(UI_MODE_TYPE_GODZILLAUI, "godzillaui")
.put(UI_MODE_TYPE_SMALLUI, "smallui")
.put(UI_MODE_TYPE_MEDIUMUI, "mediumui")
.put(UI_MODE_TYPE_LARGEUI, "largeui")
.put(UI_MODE_TYPE_HUGEUI, "hugeui").build();
/**
* The minimum size in bytes that this configuration must be to contain screen config info.
*/
private static final int SCREEN_CONFIG_MIN_SIZE = 32;
/**
* The minimum size in bytes that this configuration must be to contain screen dp info.
*/
private static final int SCREEN_DP_MIN_SIZE = 36;
/**
* The minimum size in bytes that this configuration must be to contain locale info.
*/
private static final int LOCALE_MIN_SIZE = 48;
/**
* The minimum size in bytes that this config must be to contain the screenConfig extension.
*/
private static final int SCREEN_CONFIG_EXTENSION_MIN_SIZE = 52;
/**
* The number of bytes that this resource configuration takes up.
*/
@@ -708,7 +662,7 @@ public class BinaryResourceConfiguration implements SerializableResource {
Map<Type, String> result = new LinkedHashMap<>(); // Preserve order for #toString().
result.put(Type.MCC, mcc() != 0 ? "mcc" + mcc() : "");
result.put(Type.MNC, mnc() != 0 ? "mnc" + mnc() : "");
result.put(Type.LANGUAGE_STRING, !languageString().isEmpty() ? "" + languageString() : "");
result.put(Type.LANGUAGE_STRING, !languageString().isEmpty() ? languageString() : "");
result.put(Type.REGION_STRING, !regionString().isEmpty() ? "r" + regionString() : "");
result.put(Type.SCREEN_LAYOUT_DIRECTION,
getOrDefault(SCREENLAYOUT_LAYOUTDIR_VALUES, screenLayoutDirection(), ""));
@@ -742,4 +696,31 @@ public class BinaryResourceConfiguration implements SerializableResource {
V value = map.get(key);
return value != null ? value : defaultValue;
}
/**
* The different types of configs that can be present in a {@link BinaryResourceConfiguration}.
*/
public enum Type {
MCC,
MNC,
LANGUAGE_STRING,
REGION_STRING,
SCREEN_LAYOUT_DIRECTION,
SMALLEST_SCREEN_WIDTH_DP,
SCREEN_WIDTH_DP,
SCREEN_HEIGHT_DP,
SCREEN_LAYOUT_SIZE,
SCREEN_LAYOUT_LONG,
SCREEN_LAYOUT_ROUND,
ORIENTATION,
UI_MODE_TYPE,
UI_MODE_NIGHT,
DENSITY_DPI,
TOUCHSCREEN,
KEYBOARD_HIDDEN,
KEYBOARD,
NAVIGATION_HIDDEN,
NAVIGATION,
SDK_VERSION
}
}
@@ -27,48 +27,54 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Given an arsc file, maps the contents of the file. */
/**
* Given an arsc file, maps the contents of the file.
*/
public final class BinaryResourceFile implements SerializableResource {
/** The chunks contained in this resource file. */
private final List<Chunk> chunks = new ArrayList<>();
/**
* The chunks contained in this resource file.
*/
private final List<Chunk> chunks = new ArrayList<>();
public BinaryResourceFile(byte[] buf) {
ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
while (buffer.remaining() > 0) {
chunks.add(Chunk.newInstance(buffer));
public BinaryResourceFile(byte[] buf) {
ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
while (buffer.remaining() > 0) {
chunks.add(Chunk.newInstance(buffer));
}
}
}
/**
* Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile}
* representing the contents of the stream.
*
* @param is The input stream to read from.
* @return BinaryResourceFile represented by the @{link InputStream}.
* @throws IOException
*/
public static BinaryResourceFile fromInputStream(InputStream is) throws IOException {
byte[] buf = ByteStreams.toByteArray(is);
return new BinaryResourceFile(buf);
}
/**
* Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile}
* representing the contents of the stream.
*
* @param is The input stream to read from.
* @return BinaryResourceFile represented by the @{link InputStream}.
* @throws IOException
*/
public static BinaryResourceFile fromInputStream(InputStream is) throws IOException {
byte[] buf = ByteStreams.toByteArray(is);
return new BinaryResourceFile(buf);
}
/** Returns the chunks in this resource file. */
public List<Chunk> getChunks() {
return Collections.unmodifiableList(chunks);
}
/**
* Returns the chunks in this resource file.
*/
public List<Chunk> getChunks() {
return Collections.unmodifiableList(chunks);
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
for (Chunk chunk : chunks) {
output.write(chunk.toByteArray(shrink));
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
for (Chunk chunk : chunks) {
output.write(chunk.toByteArray(shrink));
}
return output.toByteArray();
}
return output.toByteArray();
}
}
@@ -27,73 +27,97 @@ import java.util.Objects;
*/
public class BinaryResourceIdentifier {
/** The {@link PackageChunk} id mask for a packed resource id of the form 0xpptteeee. */
private static final int PACKAGE_ID_MASK = 0xFF000000;
private static final int PACKAGE_ID_SHIFT = 24;
/**
* The {@link PackageChunk} id mask for a packed resource id of the form 0xpptteeee.
*/
private static final int PACKAGE_ID_MASK = 0xFF000000;
private static final int PACKAGE_ID_SHIFT = 24;
/** The {@link TypeChunk} id mask for a packed resource id of the form 0xpptteeee. */
private static final int TYPE_ID_MASK = 0x00FF0000;
private static final int TYPE_ID_SHIFT = 16;
/**
* The {@link TypeChunk} id mask for a packed resource id of the form 0xpptteeee.
*/
private static final int TYPE_ID_MASK = 0x00FF0000;
private static final int TYPE_ID_SHIFT = 16;
/** The {@link TypeChunk.Entry} id mask for a packed resource id of the form 0xpptteeee. */
private static final int ENTRY_ID_MASK = 0xFFFF;
private static final int ENTRY_ID_SHIFT = 0;
/**
* The {@link TypeChunk.Entry} id mask for a packed resource id of the form 0xpptteeee.
*/
private static final int ENTRY_ID_MASK = 0xFFFF;
private static final int ENTRY_ID_SHIFT = 0;
private final int packageId;
private final int typeId;
private final int entryId;
private final int packageId;
private final int typeId;
private final int entryId;
/** Returns a {@link BinaryResourceIdentifier} from a {@code resourceId} of the form 0xpptteeee. */
public static BinaryResourceIdentifier create(int resourceId) {
int packageId = (resourceId & PACKAGE_ID_MASK) >>> PACKAGE_ID_SHIFT;
int typeId = (resourceId & TYPE_ID_MASK) >>> TYPE_ID_SHIFT;
int entryId = (resourceId & ENTRY_ID_MASK) >>> ENTRY_ID_SHIFT;
return create(packageId, typeId, entryId);
}
private BinaryResourceIdentifier(int packageId, int typeId, int entryId) {
this.packageId = packageId;
this.typeId = typeId;
this.entryId = entryId;
}
/** Returns a {@link BinaryResourceIdentifier} with the given identifiers. */
public static BinaryResourceIdentifier create(int packageId, int typeId, int entryId) {
Preconditions.checkState((packageId & 0xFF) == packageId, "packageId must be <= 0xFF.");
Preconditions.checkState((typeId & 0xFF) == typeId, "typeId must be <= 0xFF.");
Preconditions.checkState((entryId & 0xFFFF) == entryId, "entryId must be <= 0xFFFF.");
return new BinaryResourceIdentifier(packageId, typeId, entryId);
}
/**
* Returns a {@link BinaryResourceIdentifier} from a {@code resourceId} of the form 0xpptteeee.
*/
public static BinaryResourceIdentifier create(int resourceId) {
int packageId = (resourceId & PACKAGE_ID_MASK) >>> PACKAGE_ID_SHIFT;
int typeId = (resourceId & TYPE_ID_MASK) >>> TYPE_ID_SHIFT;
int entryId = (resourceId & ENTRY_ID_MASK) >>> ENTRY_ID_SHIFT;
return create(packageId, typeId, entryId);
}
private BinaryResourceIdentifier(int packageId, int typeId, int entryId) {
this.packageId = packageId;
this.typeId = typeId;
this.entryId = entryId;
}
/**
* Returns a {@link BinaryResourceIdentifier} with the given identifiers.
*/
public static BinaryResourceIdentifier create(int packageId, int typeId, int entryId) {
Preconditions.checkState((packageId & 0xFF) == packageId, "packageId must be <= 0xFF.");
Preconditions.checkState((typeId & 0xFF) == typeId, "typeId must be <= 0xFF.");
Preconditions.checkState((entryId & 0xFFFF) == entryId, "entryId must be <= 0xFFFF.");
return new BinaryResourceIdentifier(packageId, typeId, entryId);
}
/** The (1-based) id of the {@link PackageChunk} containing this resource. */
public int packageId() { return packageId; }
/**
* The (1-based) id of the {@link PackageChunk} containing this resource.
*/
public int packageId() {
return packageId;
}
/** The (1-based) id of the {@link TypeChunk} containing this resource. */
public int typeId() { return typeId; }
/**
* The (1-based) id of the {@link TypeChunk} containing this resource.
*/
public int typeId() {
return typeId;
}
/** The (0-based) index of the entry in a {@link TypeChunk} containing this resource. */
public int entryId() { return entryId; }
/**
* The (0-based) index of the entry in a {@link TypeChunk} containing this resource.
*/
public int entryId() {
return entryId;
}
public int resId() { return packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId; }
public int resId() {
return packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryResourceIdentifier that = (BinaryResourceIdentifier)o;
return packageId == that.packageId &&
typeId == that.typeId &&
entryId == that.entryId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryResourceIdentifier that = (BinaryResourceIdentifier) o;
return packageId == that.packageId &&
typeId == that.typeId &&
entryId == that.entryId;
}
@Override
public int hashCode() {
return Objects.hash(packageId, typeId, entryId);
}
@Override
public int hashCode() {
return Objects.hash(packageId, typeId, entryId);
}
@Override
public String toString() {
int v = packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId;
return String.format("0x%1$08x", v);
}
@Override
public String toString() {
int v = packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId;
return String.format("0x%1$08x", v);
}
}
@@ -26,139 +26,144 @@ import java.nio.charset.Charset;
import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;
/** Provides utilities to decode/encode a String packed in an arsc resource file. */
/**
* Provides utilities to decode/encode a String packed in an arsc resource file.
*/
public final class BinaryResourceString {
/** Type of {@link BinaryResourceString} to encode / decode. */
public enum Type {
UTF8(UTF_8),
UTF16(UTF_16LE);
private BinaryResourceString() {
} // Private constructor
private final Charset charset;
Type(Charset charset) {
this.charset = charset;
/**
* Given a buffer and an offset into the buffer, returns a String. The {@code offset} is the
* 0-based byte offset from the start of the buffer where the string resides. This should be the
* location in memory where the string's character count, followed by its byte count, and then
* followed by the actual string is located.
*
* <p>Here's an example UTF-8-encoded string of ab©:
* <pre>
* 03 04 61 62 C2 A9 00
* ^ Offset should be here
* </pre>
*
* @param buffer The buffer containing the string to decode.
* @param offset Offset into the buffer where the string resides.
* @param type The encoding type that the {@link BinaryResourceString} is encoded in.
* @return The decoded string.
*/
public static String decodeString(ByteBuffer buffer, int offset, Type type) {
int length;
int characterCount = decodeLength(buffer, offset, type);
offset += computeLengthOffset(characterCount, type);
// UTF-8 strings have 2 lengths: the number of characters, and then the encoding length.
// UTF-16 strings, however, only have 1 length: the number of characters.
if (type == Type.UTF8) {
length = decodeLength(buffer, offset, type);
offset += computeLengthOffset(length, type);
} else {
length = characterCount * 2;
}
return new String(buffer.array(), offset, length, type.charset());
}
public Charset charset() {
return charset;
/**
* Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string.
* Strings are prefixed by 2 values. The first is the number of characters in the string.
* The second is the encoding length (number of bytes in the string).
*
* <p>Here's an example UTF-8-encoded string of ab©:
* <pre>03 04 61 62 C2 A9 00</pre>
*
* @param str The string to be encoded.
* @param type The encoding type that the {@link BinaryResourceString} should be encoded in.
* @return The encoded string.
*/
public static byte[] encodeString(String str, Type type) {
byte[] bytes = str.getBytes(type.charset());
// The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator.
ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5);
encodeLength(output, str.length(), type);
if (type == Type.UTF8) { // Only UTF-8 strings have the encoding length.
encodeLength(output, bytes.length, type);
}
output.write(bytes);
// NULL-terminate the string
if (type == Type.UTF8) {
output.write(0);
} else {
output.writeShort(0);
}
return output.toByteArray();
}
}
private BinaryResourceString() {} // Private constructor
/**
* Given a buffer and an offset into the buffer, returns a String. The {@code offset} is the
* 0-based byte offset from the start of the buffer where the string resides. This should be the
* location in memory where the string's character count, followed by its byte count, and then
* followed by the actual string is located.
*
* <p>Here's an example UTF-8-encoded string of ab©:
* <pre>
* 03 04 61 62 C2 A9 00
* ^ Offset should be here
* </pre>
*
* @param buffer The buffer containing the string to decode.
* @param offset Offset into the buffer where the string resides.
* @param type The encoding type that the {@link BinaryResourceString} is encoded in.
* @return The decoded string.
*/
public static String decodeString(ByteBuffer buffer, int offset, Type type) {
int length;
int characterCount = decodeLength(buffer, offset, type);
offset += computeLengthOffset(characterCount, type);
// UTF-8 strings have 2 lengths: the number of characters, and then the encoding length.
// UTF-16 strings, however, only have 1 length: the number of characters.
if (type == Type.UTF8) {
length = decodeLength(buffer, offset, type);
offset += computeLengthOffset(length, type);
} else {
length = characterCount * 2;
private static void encodeLength(ByteArrayDataOutput output, int length, Type type) {
if (length < 0) {
output.write(0);
return;
}
if (type == Type.UTF8) {
if (length > 0x7F) {
output.write(((length & 0x7F00) >> 8) | 0x80);
}
output.write(length & 0xFF);
} else { // UTF-16
// TODO(acornwall): Replace output with a little-endian output.
if (length > 0x7FFF) {
int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000;
output.write(highBytes & 0xFF);
output.write((highBytes & 0xFF00) >> 8);
}
int lowBytes = length & 0xFFFF;
output.write(lowBytes & 0xFF);
output.write((lowBytes & 0xFF00) >> 8);
}
}
return new String(buffer.array(), offset, length, type.charset());
}
/**
* Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string.
* Strings are prefixed by 2 values. The first is the number of characters in the string.
* The second is the encoding length (number of bytes in the string).
*
* <p>Here's an example UTF-8-encoded string of ab©:
* <pre>03 04 61 62 C2 A9 00</pre>
*
* @param str The string to be encoded.
* @param type The encoding type that the {@link BinaryResourceString} should be encoded in.
* @return The encoded string.
*/
public static byte[] encodeString(String str, Type type) {
byte[] bytes = str.getBytes(type.charset());
// The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator.
ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5);
encodeLength(output, str.length(), type);
if (type == Type.UTF8) { // Only UTF-8 strings have the encoding length.
encodeLength(output, bytes.length, type);
private static int computeLengthOffset(int length, Type type) {
return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1);
}
output.write(bytes);
// NULL-terminate the string
if (type == Type.UTF8) {
output.write(0);
} else {
output.writeShort(0);
}
return output.toByteArray();
}
private static void encodeLength(ByteArrayDataOutput output, int length, Type type) {
if (length < 0) {
output.write(0);
return;
private static int decodeLength(ByteBuffer buffer, int offset, Type type) {
return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset);
}
if (type == Type.UTF8) {
if (length > 0x7F) {
output.write(((length & 0x7F00) >> 8) | 0x80);
}
output.write(length & 0xFF);
} else { // UTF-16
// TODO(acornwall): Replace output with a little-endian output.
if (length > 0x7FFF) {
int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000;
output.write(highBytes & 0xFF);
output.write((highBytes & 0xFF00) >> 8);
}
int lowBytes = length & 0xFFFF;
output.write(lowBytes & 0xFF);
output.write((lowBytes & 0xFF00) >> 8);
private static int decodeLengthUTF8(ByteBuffer buffer, int offset) {
// UTF-8 strings use a clever variant of the 7-bit integer for packing the string length.
// If the first byte is >= 0x80, then a second byte follows. For these values, the length
// is WORD-length in big-endian & 0x7FFF.
int length = UnsignedBytes.toInt(buffer.get(offset));
if ((length & 0x80) != 0) {
length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1));
}
return length;
}
}
private static int computeLengthOffset(int length, Type type) {
return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1);
}
private static int decodeLength(ByteBuffer buffer, int offset, Type type) {
return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset);
}
private static int decodeLengthUTF8(ByteBuffer buffer, int offset) {
// UTF-8 strings use a clever variant of the 7-bit integer for packing the string length.
// If the first byte is >= 0x80, then a second byte follows. For these values, the length
// is WORD-length in big-endian & 0x7FFF.
int length = UnsignedBytes.toInt(buffer.get(offset));
if ((length & 0x80) != 0) {
length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1));
private static int decodeLengthUTF16(ByteBuffer buffer, int offset) {
// UTF-16 strings use a clever variant of the 7-bit integer for packing the string length.
// If the first word is >= 0x8000, then a second word follows. For these values, the length
// is DWORD-length in big-endian & 0x7FFFFFFF.
int length = (buffer.getShort(offset) & 0xFFFF);
if ((length & 0x8000) != 0) {
length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF);
}
return length;
}
return length;
}
private static int decodeLengthUTF16(ByteBuffer buffer, int offset) {
// UTF-16 strings use a clever variant of the 7-bit integer for packing the string length.
// If the first word is >= 0x8000, then a second word follows. For these values, the length
// is DWORD-length in big-endian & 0x7FFFFFFF.
int length = (buffer.getShort(offset) & 0xFFFF);
if ((length & 0x8000) != 0) {
length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF);
/**
* Type of {@link BinaryResourceString} to encode / decode.
*/
public enum Type {
UTF8(UTF_8),
UTF16(UTF_16LE);
private final Charset charset;
Type(Charset charset) {
this.charset = charset;
}
public Charset charset() {
return charset;
}
}
return length;
}
}
@@ -26,126 +26,174 @@ import java.nio.ByteOrder;
import java.util.Map;
import java.util.Objects;
/** Represents a single typed resource value. */
/**
* Represents a single typed resource value.
*/
public class BinaryResourceValue implements SerializableResource {
/** Resource type codes. */
public enum Type {
/** {@code data} is either 0 (undefined) or 1 (empty). */
NULL(0x00),
/** {@code data} holds a {@link ResourceTableChunk} entry reference. */
REFERENCE(0x01),
/** {@code data} holds an attribute resource identifier. */
ATTRIBUTE(0x02),
/** {@code data} holds an index into the containing resource table's string pool. */
STRING(0x03),
/** {@code data} holds a single-precision floating point number. */
FLOAT(0x04),
/** {@code data} holds a complex number encoding a dimension value, such as "100in". */
DIMENSION(0x05),
/** {@code data} holds a complex number encoding a fraction of a container. */
FRACTION(0x06),
/** {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. */
DYNAMIC_REFERENCE(0x07),
/** {@code data} holds a dynamic attribute resource identifier. */
DYNAMIC_ATTRIBUTE(0x08),
/** {@code data} is a raw integer value of the form n..n. */
INT_DEC(0x10),
/** {@code data} is a raw integer value of the form 0xn..n. */
INT_HEX(0x11),
/** {@code data} is either 0 (false) or 1 (true). */
INT_BOOLEAN(0x12),
/** {@code data} is a raw integer value of the form #aarrggbb. */
INT_COLOR_ARGB8(0x1c),
/** {@code data} is a raw integer value of the form #rrggbb. */
INT_COLOR_RGB8(0x1d),
/** {@code data} is a raw integer value of the form #argb. */
INT_COLOR_ARGB4(0x1e),
/** {@code data} is a raw integer value of the form #rgb. */
INT_COLOR_RGB4(0x1f);
private final byte code;
private static final Map<Byte, Type> FROM_BYTE;
static {
Builder<Byte, Type> builder = ImmutableMap.builder();
for (Type type : values()) {
builder.put(type.code(), type);
}
FROM_BYTE = builder.build();
/**
* The serialized size in bytes of a {@link BinaryResourceValue}.
*/
public static final int SIZE = 8;
public int size;
public Type type;
public int data;
public BinaryResourceValue(int size, Type type, int data) {
this.size = size;
this.type = type;
this.data = data;
}
Type(int code) {
this.code = UnsignedBytes.checkedCast(code);
public static BinaryResourceValue create(ByteBuffer buffer) {
int size = (buffer.getShort() & 0xFFFF);
buffer.get(); // Unused
Type type = Type.fromCode(buffer.get());
int data = buffer.getInt();
return new BinaryResourceValue(size, type, data);
}
public byte code() {
return code;
/**
* The length in bytes of this value.
*/
public int size() {
return size;
}
public static Type fromCode(byte code) {
return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code);
/**
* The raw data type of this value.
*/
public Type type() {
return type;
}
}
/** The serialized size in bytes of a {@link BinaryResourceValue}. */
public static final int SIZE = 8;
/**
* The actual 4-byte value; interpretation of the value depends on {@code dataType}.
*/
public int data() {
return data;
}
private final int size;
private final Type type;
private final int data;
@Override
public byte[] toByteArray() {
return toByteArray(false);
}
public static BinaryResourceValue create(ByteBuffer buffer) {
int size = (buffer.getShort() & 0xFFFF);
buffer.get(); // Unused
Type type = Type.fromCode(buffer.get());
int data = buffer.getInt();
return new BinaryResourceValue(size, type, data);
}
@Override
public byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) size());
buffer.put((byte) 0); // Unused
buffer.put(type().code());
buffer.putInt(data());
return buffer.array();
}
public BinaryResourceValue(int size, Type type, int data) {
this.size = size;
this.type = type;
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryResourceValue that = (BinaryResourceValue) o;
return size == that.size &&
data == that.data &&
type == that.type;
}
/** The length in bytes of this value. */
public int size() { return size; }
@Override
public int hashCode() {
return Objects.hash(size, type, data);
}
/** The raw data type of this value. */
public Type type() { return type; }
/**
* Resource type codes.
*/
public enum Type {
/**
* {@code data} is either 0 (undefined) or 1 (empty).
*/
NULL(0x00),
/**
* {@code data} holds a {@link ResourceTableChunk} entry reference.
*/
REFERENCE(0x01),
/**
* {@code data} holds an attribute resource identifier.
*/
ATTRIBUTE(0x02),
/**
* {@code data} holds an index into the containing resource table's string pool.
*/
STRING(0x03),
/**
* {@code data} holds a single-precision floating point number.
*/
FLOAT(0x04),
/**
* {@code data} holds a complex number encoding a dimension value, such as "100in".
*/
DIMENSION(0x05),
/**
* {@code data} holds a complex number encoding a fraction of a container.
*/
FRACTION(0x06),
/**
* {@code data} holds a dynamic {@link ResourceTableChunk} entry reference.
*/
DYNAMIC_REFERENCE(0x07),
/**
* {@code data} holds a dynamic attribute resource identifier.
*/
DYNAMIC_ATTRIBUTE(0x08),
/**
* {@code data} is a raw integer value of the form n..n.
*/
INT_DEC(0x10),
/**
* {@code data} is a raw integer value of the form 0xn..n.
*/
INT_HEX(0x11),
/**
* {@code data} is either 0 (false) or 1 (true).
*/
INT_BOOLEAN(0x12),
/**
* {@code data} is a raw integer value of the form #aarrggbb.
*/
INT_COLOR_ARGB8(0x1c),
/**
* {@code data} is a raw integer value of the form #rrggbb.
*/
INT_COLOR_RGB8(0x1d),
/**
* {@code data} is a raw integer value of the form #argb.
*/
INT_COLOR_ARGB4(0x1e),
/**
* {@code data} is a raw integer value of the form #rgb.
*/
INT_COLOR_RGB4(0x1f);
/** The actual 4-byte value; interpretation of the value depends on {@code dataType}. */
public int data() { return data; }
private static final Map<Byte, Type> FROM_BYTE;
@Override
public byte[] toByteArray() {
return toByteArray(false);
}
static {
Builder<Byte, Type> builder = ImmutableMap.builder();
for (Type type : values()) {
builder.put(type.code(), type);
}
FROM_BYTE = builder.build();
}
@Override
public byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) size());
buffer.put((byte) 0); // Unused
buffer.put(type().code());
buffer.putInt(data());
return buffer.array();
}
private final byte code;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryResourceValue that = (BinaryResourceValue)o;
return size == that.size &&
data == that.data &&
type == that.type;
}
Type(int code) {
this.code = UnsignedBytes.checkedCast(code);
}
@Override
public int hashCode() {
return Objects.hash(size, type, data);
}
public static Type fromCode(byte code) {
return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code);
}
public byte code() {
return code;
}
}
}
@@ -30,272 +30,290 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
/** Represents a generic chunk. */
/**
* Represents a generic chunk.
*/
public abstract class Chunk implements SerializableResource {
/** Types of chunks that can exist. */
public enum Type {
NULL(0x0000),
STRING_POOL(0x0001),
TABLE(0x0002),
XML(0x0003),
XML_START_NAMESPACE(0x0100),
XML_END_NAMESPACE(0x0101),
XML_START_ELEMENT(0x0102),
XML_END_ELEMENT(0x0103),
XML_CDATA(0x0104),
XML_RESOURCE_MAP(0x0180),
TABLE_PACKAGE(0x0200),
TABLE_TYPE(0x0201),
TABLE_TYPE_SPEC(0x0202),
TABLE_LIBRARY(0x0203);
/**
* The byte boundary to pad chunks on.
*/
public static final int PAD_BOUNDARY = 4;
/**
* The number of bytes in every chunk that describes chunk type, header size, and chunk size.
*/
public static final int METADATA_SIZE = 8;
/**
* The offset in bytes, from the start of the chunk, where the chunk size can be found.
*/
private static final int CHUNK_SIZE_OFFSET = 4;
/**
* Size of the chunk header in bytes.
*/
protected final int headerSize;
/**
* headerSize + dataSize. The total size of this chunk.
*/
protected final int chunkSize;
/**
* Offset of this chunk from the start of the file.
*/
protected final int offset;
/**
* The parent to this chunk, if any.
*/
@Nullable
private final Chunk parent;
private final short code;
private static final Map<Short, Type> FROM_SHORT;
static {
Builder<Short, Type> builder = ImmutableMap.builder();
for (Type type : values()) {
builder.put(type.code(), type);
}
FROM_SHORT = builder.build();
protected Chunk(ByteBuffer buffer, @Nullable Chunk parent) {
this.parent = parent;
offset = buffer.position() - 2;
headerSize = (buffer.getShort() & 0xFFFF);
chunkSize = buffer.getInt();
}
Type(int code) {
this.code = Shorts.checkedCast(code);
/**
* Creates a new chunk whose contents start at {@code buffer}'s current position.
*
* @param buffer A buffer positioned at the start of a chunk.
* @return new chunk
*/
public static Chunk newInstance(ByteBuffer buffer) {
return newInstance(buffer, null);
}
public short code() {
return code;
/**
* Creates a new chunk whose contents start at {@code buffer}'s current position.
*
* @param buffer A buffer positioned at the start of a chunk.
* @param parent The parent to this chunk (or null if there's no parent).
* @return new chunk
*/
public static Chunk newInstance(ByteBuffer buffer, @Nullable Chunk parent) {
Chunk result;
Type type = Type.fromCode(buffer.getShort());
switch (type) {
case STRING_POOL:
result = new StringPoolChunk(buffer, parent);
break;
case TABLE:
result = new ResourceTableChunk(buffer, parent);
break;
case XML:
result = new XmlChunk(buffer, parent);
break;
case XML_START_NAMESPACE:
result = new XmlNamespaceStartChunk(buffer, parent);
break;
case XML_END_NAMESPACE:
result = new XmlNamespaceEndChunk(buffer, parent);
break;
case XML_START_ELEMENT:
result = new XmlStartElementChunk(buffer, parent);
break;
case XML_END_ELEMENT:
result = new XmlEndElementChunk(buffer, parent);
break;
case XML_CDATA:
result = new XmlCdataChunk(buffer, parent);
break;
case XML_RESOURCE_MAP:
result = new XmlResourceMapChunk(buffer, parent);
break;
case TABLE_PACKAGE:
result = new PackageChunk(buffer, parent);
break;
case TABLE_TYPE:
result = new TypeChunk(buffer, parent);
break;
case TABLE_TYPE_SPEC:
result = new TypeSpecChunk(buffer, parent);
break;
case TABLE_LIBRARY:
result = new LibraryChunk(buffer, parent);
break;
default:
result = new UnknownChunk(buffer, parent);
}
result.init(buffer);
result.seekToEndOfChunk(buffer);
return result;
}
public static Type fromCode(short code) {
return Preconditions.checkNotNull(FROM_SHORT.get(code), "Unknown chunk type: %s", code);
}
}
/** The byte boundary to pad chunks on. */
public static final int PAD_BOUNDARY = 4;
/** The number of bytes in every chunk that describes chunk type, header size, and chunk size. */
public static final int METADATA_SIZE = 8;
/** The offset in bytes, from the start of the chunk, where the chunk size can be found. */
private static final int CHUNK_SIZE_OFFSET = 4;
/** The parent to this chunk, if any. */
@Nullable
private final Chunk parent;
/** Size of the chunk header in bytes. */
protected final int headerSize;
/** headerSize + dataSize. The total size of this chunk. */
protected final int chunkSize;
/** Offset of this chunk from the start of the file. */
protected final int offset;
protected Chunk(ByteBuffer buffer, @Nullable Chunk parent) {
this.parent = parent;
offset = buffer.position() - 2;
headerSize = (buffer.getShort() & 0xFFFF);
chunkSize = buffer.getInt();
}
/**
* Finishes initialization of a chunk. This should be called immediately after the constructor.
* This is separate from the constructor so that the header of a chunk can be fully initialized
* before the payload of that chunk is initialized for chunks that require such behavior.
*
* @param buffer The buffer that the payload will be initialized from.
*/
protected void init(ByteBuffer buffer) {}
/**
* Returns the parent to this chunk, if any. A parent is a chunk whose payload contains this
* chunk. If there's no parent, null is returned.
*/
@Nullable
public Chunk getParent() {
return parent;
}
protected abstract Type getType();
/** Returns the size of this chunk's header. */
public final int getHeaderSize() {
return headerSize;
}
/**
* Returns the size of this chunk when it was first read from a buffer. A chunk's size can deviate
* from this value when its data is modified (e.g. adding an entry, changing a string).
*
* <p>A chunk's current size can be determined from the length of the byte array returned from
* {@link #toByteArray}.
*/
public final int getOriginalChunkSize() {
return chunkSize;
}
/**
* Reposition the buffer after this chunk. Use this at the end of a Chunk constructor.
* @param buffer The buffer to be repositioned.
*/
private final void seekToEndOfChunk(ByteBuffer buffer) {
buffer.position(offset + chunkSize);
}
/**
* Writes the type and header size. We don't know how big this chunk will be (it could be
* different since the last time we checked), so this needs to be passed in.
*
* @param output The buffer that will be written to.
* @param chunkSize The total size of this chunk in bytes, including the header.
*/
protected final void writeHeader(ByteBuffer output, int chunkSize) {
int start = output.position();
output.putShort(getType().code());
output.putShort((short) headerSize);
output.putInt(chunkSize);
writeHeader(output);
int headerBytes = output.position() - start;
Preconditions.checkState(headerBytes == getHeaderSize(),
"Written header is wrong size. Got %s, want %s", headerBytes, getHeaderSize());
}
/**
* Writes the remaining header (after the type, {@code headerSize}, and {@code chunkSize}).
*
* @param output The buffer that the header will be written to.
*/
protected void writeHeader(ByteBuffer output) {}
/**
* Writes the chunk payload. The payload is data in a chunk which is not in
* the first {@code headerSize} bytes of the chunk.
*
* @param output The stream that the payload will be written to.
* @param header The already-written header. This can be modified to fix payload offsets.
* @param shrink True if this payload should be optimized for size.
* @throws IOException Thrown if {@code output} could not be written to (out of memory).
*/
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {}
/**
* Pads {@code output} until {@code currentLength} is on a 4-byte boundary.
*
* @param output The {@link DataOutput} that will be padded.
* @param currentLength The current length, in bytes, of {@code output}
* @return The new length of {@code output}
* @throws IOException Thrown if {@code output} could not be written to.
*/
protected int writePad(DataOutput output, int currentLength) throws IOException {
while (currentLength % PAD_BOUNDARY != 0) {
output.write(0);
++currentLength;
}
return currentLength;
}
@Override
public final byte[] toByteArray() throws IOException {
return toByteArray(false);
}
/**
* Converts this chunk into an array of bytes representation. Normally you will not need to
* override this method unless your header changes based on the contents / size of the payload.
*/
@Override
public final byte[] toByteArray(boolean shrink) throws IOException {
ByteBuffer header = ByteBuffer.allocate(getHeaderSize()).order(ByteOrder.LITTLE_ENDIAN);
writeHeader(header, 0); // The chunk size isn't known yet. This will be filled in later.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
writePayload(payload, header, shrink);
/**
* Finishes initialization of a chunk. This should be called immediately after the constructor.
* This is separate from the constructor so that the header of a chunk can be fully initialized
* before the payload of that chunk is initialized for chunks that require such behavior.
*
* @param buffer The buffer that the payload will be initialized from.
*/
protected void init(ByteBuffer buffer) {
}
byte[] payloadBytes = baos.toByteArray();
int chunkSize = getHeaderSize() + payloadBytes.length;
header.putInt(CHUNK_SIZE_OFFSET, chunkSize);
// Combine results
ByteBuffer result = ByteBuffer.allocate(chunkSize).order(ByteOrder.LITTLE_ENDIAN);
result.put(header.array());
result.put(payloadBytes);
return result.array();
}
/**
* Creates a new chunk whose contents start at {@code buffer}'s current position.
*
* @param buffer A buffer positioned at the start of a chunk.
* @return new chunk
*/
public static Chunk newInstance(ByteBuffer buffer) {
return newInstance(buffer, null);
}
/**
* Creates a new chunk whose contents start at {@code buffer}'s current position.
*
* @param buffer A buffer positioned at the start of a chunk.
* @param parent The parent to this chunk (or null if there's no parent).
* @return new chunk
*/
public static Chunk newInstance(ByteBuffer buffer, @Nullable Chunk parent) {
Chunk result;
Type type = Type.fromCode(buffer.getShort());
switch (type) {
case STRING_POOL:
result = new StringPoolChunk(buffer, parent);
break;
case TABLE:
result = new ResourceTableChunk(buffer, parent);
break;
case XML:
result = new XmlChunk(buffer, parent);
break;
case XML_START_NAMESPACE:
result = new XmlNamespaceStartChunk(buffer, parent);
break;
case XML_END_NAMESPACE:
result = new XmlNamespaceEndChunk(buffer, parent);
break;
case XML_START_ELEMENT:
result = new XmlStartElementChunk(buffer, parent);
break;
case XML_END_ELEMENT:
result = new XmlEndElementChunk(buffer, parent);
break;
case XML_CDATA:
result = new XmlCdataChunk(buffer, parent);
break;
case XML_RESOURCE_MAP:
result = new XmlResourceMapChunk(buffer, parent);
break;
case TABLE_PACKAGE:
result = new PackageChunk(buffer, parent);
break;
case TABLE_TYPE:
result = new TypeChunk(buffer, parent);
break;
case TABLE_TYPE_SPEC:
result = new TypeSpecChunk(buffer, parent);
break;
case TABLE_LIBRARY:
result = new LibraryChunk(buffer, parent);
break;
default:
result = new UnknownChunk(buffer, parent);
/**
* Returns the parent to this chunk, if any. A parent is a chunk whose payload contains this
* chunk. If there's no parent, null is returned.
*/
@Nullable
public Chunk getParent() {
return parent;
}
protected abstract Type getType();
/**
* Returns the size of this chunk's header.
*/
public final int getHeaderSize() {
return headerSize;
}
/**
* Returns the size of this chunk when it was first read from a buffer. A chunk's size can deviate
* from this value when its data is modified (e.g. adding an entry, changing a string).
*
* <p>A chunk's current size can be determined from the length of the byte array returned from
* {@link #toByteArray}.
*/
public final int getOriginalChunkSize() {
return chunkSize;
}
/**
* Reposition the buffer after this chunk. Use this at the end of a Chunk constructor.
*
* @param buffer The buffer to be repositioned.
*/
private final void seekToEndOfChunk(ByteBuffer buffer) {
buffer.position(offset + chunkSize);
}
/**
* Writes the type and header size. We don't know how big this chunk will be (it could be
* different since the last time we checked), so this needs to be passed in.
*
* @param output The buffer that will be written to.
* @param chunkSize The total size of this chunk in bytes, including the header.
*/
protected final void writeHeader(ByteBuffer output, int chunkSize) {
int start = output.position();
output.putShort(getType().code());
output.putShort((short) headerSize);
output.putInt(chunkSize);
writeHeader(output);
int headerBytes = output.position() - start;
Preconditions.checkState(headerBytes == getHeaderSize(),
"Written header is wrong size. Got %s, want %s", headerBytes, getHeaderSize());
}
/**
* Writes the remaining header (after the type, {@code headerSize}, and {@code chunkSize}).
*
* @param output The buffer that the header will be written to.
*/
protected void writeHeader(ByteBuffer output) {
}
/**
* Writes the chunk payload. The payload is data in a chunk which is not in
* the first {@code headerSize} bytes of the chunk.
*
* @param output The stream that the payload will be written to.
* @param header The already-written header. This can be modified to fix payload offsets.
* @param shrink True if this payload should be optimized for size.
* @throws IOException Thrown if {@code output} could not be written to (out of memory).
*/
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
}
/**
* Pads {@code output} until {@code currentLength} is on a 4-byte boundary.
*
* @param output The {@link DataOutput} that will be padded.
* @param currentLength The current length, in bytes, of {@code output}
* @return The new length of {@code output}
* @throws IOException Thrown if {@code output} could not be written to.
*/
protected int writePad(DataOutput output, int currentLength) throws IOException {
while (currentLength % PAD_BOUNDARY != 0) {
output.write(0);
++currentLength;
}
return currentLength;
}
@Override
public final byte[] toByteArray() throws IOException {
return toByteArray(false);
}
/**
* Converts this chunk into an array of bytes representation. Normally you will not need to
* override this method unless your header changes based on the contents / size of the payload.
*/
@Override
public final byte[] toByteArray(boolean shrink) throws IOException {
ByteBuffer header = ByteBuffer.allocate(getHeaderSize()).order(ByteOrder.LITTLE_ENDIAN);
writeHeader(header, 0); // The chunk size isn't known yet. This will be filled in later.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
writePayload(payload, header, shrink);
}
byte[] payloadBytes = baos.toByteArray();
int chunkSize = getHeaderSize() + payloadBytes.length;
header.putInt(CHUNK_SIZE_OFFSET, chunkSize);
// Combine results
ByteBuffer result = ByteBuffer.allocate(chunkSize).order(ByteOrder.LITTLE_ENDIAN);
result.put(header.array());
result.put(payloadBytes);
return result.array();
}
/**
* Types of chunks that can exist.
*/
public enum Type {
NULL(0x0000),
STRING_POOL(0x0001),
TABLE(0x0002),
XML(0x0003),
XML_START_NAMESPACE(0x0100),
XML_END_NAMESPACE(0x0101),
XML_START_ELEMENT(0x0102),
XML_END_ELEMENT(0x0103),
XML_CDATA(0x0104),
XML_RESOURCE_MAP(0x0180),
TABLE_PACKAGE(0x0200),
TABLE_TYPE(0x0201),
TABLE_TYPE_SPEC(0x0202),
TABLE_LIBRARY(0x0203);
private static final Map<Short, Type> FROM_SHORT;
static {
Builder<Short, Type> builder = ImmutableMap.builder();
for (Type type : values()) {
builder.put(type.code(), type);
}
FROM_SHORT = builder.build();
}
private final short code;
Type(int code) {
this.code = Shorts.checkedCast(code);
}
public static Type fromCode(short code) {
return Preconditions.checkNotNull(FROM_SHORT.get(code), "Unknown chunk type: %s", code);
}
public short code() {
return code;
}
}
result.init(buffer);
result.seekToEndOfChunk(buffer);
return result;
}
}
@@ -24,50 +24,52 @@ import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
/** Represents a chunk whose payload is a list of sub-chunks. */
/**
* Represents a chunk whose payload is a list of sub-chunks.
*/
public abstract class ChunkWithChunks extends Chunk {
private final Map<Integer, Chunk> chunks = new LinkedHashMap<>();
private final Map<Integer, Chunk> chunks = new LinkedHashMap<>();
protected ChunkWithChunks(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
chunks.clear();
int start = this.offset + getHeaderSize();
int offset = start;
int end = this.offset + getOriginalChunkSize();
int position = buffer.position();
buffer.position(start);
while (offset < end) {
Chunk chunk = Chunk.newInstance(buffer, this);
chunks.put(offset, chunk);
offset += chunk.getOriginalChunkSize();
protected ChunkWithChunks(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
buffer.position(position);
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
chunks.clear();
int start = this.offset + getHeaderSize();
int offset = start;
int end = this.offset + getOriginalChunkSize();
int position = buffer.position();
buffer.position(start);
/**
* Retrieves the @{code chunks} contained in this chunk.
*
* @return map of buffer offset -> chunk contained in this chunk.
*/
public final Map<Integer, Chunk> getChunks() {
return chunks;
}
while (offset < end) {
Chunk chunk = Chunk.newInstance(buffer, this);
chunks.put(offset, chunk);
offset += chunk.getOriginalChunkSize();
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (Chunk chunk : getChunks().values()) {
byte[] chunkBytes = chunk.toByteArray(shrink);
output.write(chunkBytes);
writePad(output, chunkBytes.length);
buffer.position(position);
}
/**
* Retrieves the @{code chunks} contained in this chunk.
*
* @return map of buffer offset -> chunk contained in this chunk.
*/
public final Map<Integer, Chunk> getChunks() {
return chunks;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (Chunk chunk : getChunks().values()) {
byte[] chunkBytes = chunk.toByteArray(shrink);
output.write(chunkBytes);
writePad(output, chunkBytes.length);
}
}
}
}
@@ -17,6 +17,7 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -32,105 +33,121 @@ import java.util.Objects;
*/
public final class LibraryChunk extends Chunk {
/** The number of resources of this type at creation time. */
private final int entryCount;
/**
* The number of resources of this type at creation time.
*/
private final int entryCount;
/** The libraries used in this chunk (package id + name). */
private final List<Entry> entries = new ArrayList<>();
/**
* The libraries used in this chunk (package id + name).
*/
private final List<Entry> entries = new ArrayList<>();
protected LibraryChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
entryCount = buffer.getInt();
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
entries.addAll(enumerateEntries(buffer));
}
private List<Entry> enumerateEntries(ByteBuffer buffer) {
List<Entry> result = new ArrayList<>(entryCount);
int offset = this.offset + getHeaderSize();
int endOffset = offset + Entry.SIZE * entryCount;
while (offset < endOffset) {
result.add(Entry.create(buffer, offset));
offset += Entry.SIZE;
}
return result;
}
@Override
protected Type getType() {
return Type.TABLE_LIBRARY;
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(entries.size());
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (Entry entry : entries) {
output.write(entry.toByteArray(shrink));
}
}
/** A shared library package-id to package name entry. */
protected static class Entry implements SerializableResource {
/** Library entries only contain a package ID (4 bytes) and a package name. */
private static final int SIZE = 4 + PackageUtils.PACKAGE_NAME_SIZE;
private final int packageId;
private final String packageName;
static Entry create(ByteBuffer buffer, int offset) {
int packageId = buffer.getInt(offset);
String packageName = PackageUtils.readPackageName(buffer, offset + 4);
return new Entry(packageId, packageName);
}
private Entry(int packageId, String packageName) {
this.packageId = packageId;
this.packageName = packageName;
}
/** The id assigned to the shared library at build time. */
public int packageId() { return packageId; }
/** The package name of the shared library. */
public String packageName() { return packageName; }
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
LibraryChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
entryCount = buffer.getInt();
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(packageId());
PackageUtils.writePackageName(buffer, packageName());
return buffer.array();
protected void init(ByteBuffer buffer) {
super.init(buffer);
entries.addAll(enumerateEntries(buffer));
}
private List<Entry> enumerateEntries(ByteBuffer buffer) {
List<Entry> result = new ArrayList<>(entryCount);
int offset = this.offset + getHeaderSize();
int endOffset = offset + Entry.SIZE * entryCount;
while (offset < endOffset) {
result.add(Entry.create(buffer, offset));
offset += Entry.SIZE;
}
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry)o;
return packageId == entry.packageId &&
Objects.equals(packageName, entry.packageName);
protected Type getType() {
return Type.TABLE_LIBRARY;
}
@Override
public int hashCode() {
return Objects.hash(packageId, packageName);
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(entries.size());
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (Entry entry : entries) {
output.write(entry.toByteArray(shrink));
}
}
/**
* A shared library package-id to package name entry.
*/
protected static class Entry implements SerializableResource {
/**
* Library entries only contain a package ID (4 bytes) and a package name.
*/
private static final int SIZE = 4 + PackageUtils.PACKAGE_NAME_SIZE;
private final int packageId;
private final String packageName;
private Entry(int packageId, String packageName) {
this.packageId = packageId;
this.packageName = packageName;
}
static Entry create(ByteBuffer buffer, int offset) {
int packageId = buffer.getInt(offset);
String packageName = PackageUtils.readPackageName(buffer, offset + 4);
return new Entry(packageId, packageName);
}
/**
* The id assigned to the shared library at build time.
*/
public int packageId() {
return packageId;
}
/**
* The package name of the shared library.
*/
public String packageName() {
return packageName;
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(packageId());
PackageUtils.writePackageName(buffer, packageName());
return buffer.array();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return packageId == entry.packageId &&
Objects.equals(packageName, entry.packageName);
}
@Override
public int hashCode() {
return Objects.hash(packageId, packageName);
}
}
}
}
@@ -16,11 +16,11 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -29,211 +29,241 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/** A package chunk is a collection of resource data types within a package. */
/**
* A package chunk is a collection of resource data types within a package.
*/
public final class PackageChunk extends ChunkWithChunks {
/** Offset in bytes, from the start of the chunk, where {@code typeStringsOffset} can be found. */
private static final int TYPE_OFFSET_OFFSET = 268;
/**
* Offset in bytes, from the start of the chunk, where {@code typeStringsOffset} can be found.
*/
private static final int TYPE_OFFSET_OFFSET = 268;
/** Offset in bytes, from the start of the chunk, where {@code keyStringsOffset} can be found. */
private static final int KEY_OFFSET_OFFSET = 276;
/**
* Offset in bytes, from the start of the chunk, where {@code keyStringsOffset} can be found.
*/
private static final int KEY_OFFSET_OFFSET = 276;
/**
* The offset (from {@code offset}) in the original buffer where type strings start.
*/
private final int typeStringsOffset;
/**
* The index into the type string pool of the last public type.
*/
private final int lastPublicType;
/**
* An offset to the string pool that contains the key strings for this package.
*/
private final int keyStringsOffset;
/**
* The index into the key string pool of the last public key.
*/
private final int lastPublicKey;
/**
* An offset to the type ID(s). This is undocumented in the original code.
*/
private final int typeIdOffset;
/**
* Contains a mapping of a type index to its {@link TypeSpecChunk}.
*/
private final Map<Integer, TypeSpecChunk> typeSpecs = new HashMap<>();
/**
* Contains a mapping of a type index to all of the {@link TypeChunk} with that index.
*/
private final Multimap<Integer, TypeChunk> types = ArrayListMultimap.create();
/**
* The package id if this is a base package, or 0 if not a base package.
*/
private int id;
/**
* The name of the package.
*/
private String packageName;
/**
* May contain a library chunk for mapping dynamic references to resolved references.
*/
private Optional<LibraryChunk> libraryChunk = Optional.empty();
/** The package id if this is a base package, or 0 if not a base package. */
private int id;
/** The name of the package. */
private String packageName;
/** The offset (from {@code offset}) in the original buffer where type strings start. */
private final int typeStringsOffset;
/** The index into the type string pool of the last public type. */
private final int lastPublicType;
/** An offset to the string pool that contains the key strings for this package. */
private final int keyStringsOffset;
/** The index into the key string pool of the last public key. */
private final int lastPublicKey;
/** An offset to the type ID(s). This is undocumented in the original code. */
private final int typeIdOffset;
/** Contains a mapping of a type index to its {@link TypeSpecChunk}. */
private final Map<Integer, TypeSpecChunk> typeSpecs = new HashMap<>();
/** Contains a mapping of a type index to all of the {@link TypeChunk} with that index. */
private final Multimap<Integer, TypeChunk> types = ArrayListMultimap.create();
/** May contain a library chunk for mapping dynamic references to resolved references. */
private Optional<LibraryChunk> libraryChunk = Optional.empty();
protected PackageChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = buffer.getInt();
packageName = PackageUtils.readPackageName(buffer, buffer.position());
typeStringsOffset = buffer.getInt();
lastPublicType = buffer.getInt();
keyStringsOffset = buffer.getInt();
lastPublicKey = buffer.getInt();
typeIdOffset = buffer.getInt();
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof TypeChunk) {
TypeChunk typeChunk = (TypeChunk) chunk;
types.put(typeChunk.getId(), typeChunk);
} else if (chunk instanceof TypeSpecChunk) {
TypeSpecChunk typeSpecChunk = (TypeSpecChunk) chunk;
typeSpecs.put(typeSpecChunk.getId(), typeSpecChunk);
} else if (chunk instanceof LibraryChunk) {
if (libraryChunk.isPresent()) {
throw new IllegalStateException(
"Multiple library chunks present in package chunk.");
}
libraryChunk = Optional.of((LibraryChunk) chunk);
} else if (!(chunk instanceof StringPoolChunk)) {
throw new IllegalStateException(
String.format("PackageChunk contains an unexpected chunk: %s", chunk.getClass()));
}
PackageChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = buffer.getInt();
packageName = PackageUtils.readPackageName(buffer, buffer.position());
typeStringsOffset = buffer.getInt();
lastPublicType = buffer.getInt();
keyStringsOffset = buffer.getInt();
lastPublicKey = buffer.getInt();
typeIdOffset = buffer.getInt();
}
}
/** Returns the package id if this is a base package, or 0 if not a base package. */
public int getId() {
return id;
}
/** Sets the package id */
public void setId(int id) {
this.id = id;
}
/**
* Returns the string pool that contains the names of the resources in this package.
*/
public StringPoolChunk getKeyStringPool() {
Chunk chunk = Preconditions.checkNotNull(getChunks().get(keyStringsOffset + offset));
Preconditions.checkState(chunk instanceof StringPoolChunk, "Key string pool not found.");
return (StringPoolChunk) chunk;
}
/**
* Get the type string for a specific id, e.g., (e.g. string, attr, id).
*
* @param id The id to get the type for.
* @return The type string.
*/
public String getTypeString(int id) {
StringPoolChunk typePool = getTypeStringPool();
Preconditions.checkNotNull(typePool, "Package has no type pool.");
Preconditions.checkState(typePool.getStyleCount() >= id, "No type for id: " + id);
return typePool.getString(id - 1); // - 1 here to convert to 0-based index
}
/**
* Returns the string pool that contains the type strings for this package, such as "layout",
* "string", "color".
*/
public StringPoolChunk getTypeStringPool() {
Chunk chunk = Preconditions.checkNotNull(getChunks().get(typeStringsOffset + offset));
Preconditions.checkState(chunk instanceof StringPoolChunk, "Type string pool not found.");
return (StringPoolChunk) chunk;
}
/** Returns all {@link TypeChunk} in this package. */
public Collection<TypeChunk> getTypeChunks() {
return types.values();
}
/**
* For a given type id, returns the {@link TypeChunk} objects that match that id. The type id is
* the 1-based index of the type in the type string pool (returned by {@link #getTypeStringPool}).
*
* @param id The 1-based type id to return {@link TypeChunk} objects for.
* @return The matching {@link TypeChunk} objects, or an empty collection if there are none.
*/
public Collection<TypeChunk> getTypeChunks(int id) {
return types.get(id);
}
/**
* For a given type, returns the {@link TypeChunk} objects that match that type
* (e.g. "attr", "id", "string", ...).
*
* @param type The type to return {@link TypeChunk} objects for.
* @return The matching {@link TypeChunk} objects, or an empty collection if there are none.
*/
public Collection<TypeChunk> getTypeChunks(String type) {
StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool());
return getTypeChunks(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based
}
/** Returns all {@link TypeSpecChunk} in this package. */
public Collection<TypeSpecChunk> getTypeSpecChunks() {
return typeSpecs.values();
}
/** For a given (1-based) type id, returns the {@link TypeSpecChunk} matching it. */
public TypeSpecChunk getTypeSpecChunk(int id) {
return Preconditions.checkNotNull(typeSpecs.get(id));
}
/**
* For a given {@code type}, returns the {@link TypeSpecChunk} that matches it
* (e.g. "attr", "id", "string", ...).
*/
public TypeSpecChunk getTypeSpecChunk(String type) {
StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool());
return getTypeSpecChunk(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based
}
/** Returns the name of this package. */
public String getPackageName() {
return packageName;
}
/** Set the package name */
public void setPackageName(String packageName) {
this.packageName = packageName;
}
@Override
protected Type getType() {
return Type.TABLE_PACKAGE;
}
@Override
protected void writeHeader(ByteBuffer output) {
output.putInt(id);
PackageUtils.writePackageName(output, packageName);
output.putInt(0); // typeStringsOffset. This value can't be computed here.
output.putInt(lastPublicType);
output.putInt(0); // keyStringsOffset. This value can't be computed here.
output.putInt(lastPublicKey);
output.putInt(typeIdOffset);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
int typeOffset = typeStringsOffset;
int keyOffset = keyStringsOffset;
int payloadOffset = 0;
for (Chunk chunk : getChunks().values()) {
if (chunk == getTypeStringPool()) {
typeOffset = payloadOffset + getHeaderSize();
} else if (chunk == getKeyStringPool()) {
keyOffset = payloadOffset + getHeaderSize();
}
byte[] chunkBytes = chunk.toByteArray(shrink);
output.write(chunkBytes);
payloadOffset = writePad(output, chunkBytes.length);
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof TypeChunk) {
TypeChunk typeChunk = (TypeChunk) chunk;
types.put(typeChunk.getId(), typeChunk);
} else if (chunk instanceof TypeSpecChunk) {
TypeSpecChunk typeSpecChunk = (TypeSpecChunk) chunk;
typeSpecs.put(typeSpecChunk.getId(), typeSpecChunk);
} else if (chunk instanceof LibraryChunk) {
if (libraryChunk.isPresent()) {
throw new IllegalStateException(
"Multiple library chunks present in package chunk.");
}
libraryChunk = Optional.of((LibraryChunk) chunk);
} else if (!(chunk instanceof StringPoolChunk)) {
throw new IllegalStateException(
String.format("PackageChunk contains an unexpected chunk: %s", chunk.getClass()));
}
}
}
/**
* Returns the package id if this is a base package, or 0 if not a base package.
*/
public int getId() {
return id;
}
/**
* Sets the package id
*/
public void setId(int id) {
this.id = id;
}
/**
* Returns the string pool that contains the names of the resources in this package.
*/
public StringPoolChunk getKeyStringPool() {
Chunk chunk = Preconditions.checkNotNull(getChunks().get(keyStringsOffset + offset));
Preconditions.checkState(chunk instanceof StringPoolChunk, "Key string pool not found.");
return (StringPoolChunk) chunk;
}
/**
* Get the type string for a specific id, e.g., (e.g. string, attr, id).
*
* @param id The id to get the type for.
* @return The type string.
*/
public String getTypeString(int id) {
StringPoolChunk typePool = getTypeStringPool();
Preconditions.checkNotNull(typePool, "Package has no type pool.");
Preconditions.checkState(typePool.getStyleCount() >= id, "No type for id: " + id);
return typePool.getString(id - 1); // - 1 here to convert to 0-based index
}
/**
* Returns the string pool that contains the type strings for this package, such as "layout",
* "string", "color".
*/
public StringPoolChunk getTypeStringPool() {
Chunk chunk = Preconditions.checkNotNull(getChunks().get(typeStringsOffset + offset));
Preconditions.checkState(chunk instanceof StringPoolChunk, "Type string pool not found.");
return (StringPoolChunk) chunk;
}
/**
* Returns all {@link TypeChunk} in this package.
*/
public Collection<TypeChunk> getTypeChunks() {
return types.values();
}
/**
* For a given type id, returns the {@link TypeChunk} objects that match that id. The type id is
* the 1-based index of the type in the type string pool (returned by {@link #getTypeStringPool}).
*
* @param id The 1-based type id to return {@link TypeChunk} objects for.
* @return The matching {@link TypeChunk} objects, or an empty collection if there are none.
*/
public Collection<TypeChunk> getTypeChunks(int id) {
return types.get(id);
}
/**
* For a given type, returns the {@link TypeChunk} objects that match that type
* (e.g. "attr", "id", "string", ...).
*
* @param type The type to return {@link TypeChunk} objects for.
* @return The matching {@link TypeChunk} objects, or an empty collection if there are none.
*/
public Collection<TypeChunk> getTypeChunks(String type) {
StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool());
return getTypeChunks(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based
}
/**
* Returns all {@link TypeSpecChunk} in this package.
*/
public Collection<TypeSpecChunk> getTypeSpecChunks() {
return typeSpecs.values();
}
/**
* For a given (1-based) type id, returns the {@link TypeSpecChunk} matching it.
*/
public TypeSpecChunk getTypeSpecChunk(int id) {
return Preconditions.checkNotNull(typeSpecs.get(id));
}
/**
* For a given {@code type}, returns the {@link TypeSpecChunk} that matches it
* (e.g. "attr", "id", "string", ...).
*/
public TypeSpecChunk getTypeSpecChunk(String type) {
StringPoolChunk typeStringPool = Preconditions.checkNotNull(getTypeStringPool());
return getTypeSpecChunk(typeStringPool.indexOf(type) + 1); // Convert 0-based index to 1-based
}
/**
* Returns the name of this package.
*/
public String getPackageName() {
return packageName;
}
/**
* Set the package name
*/
public void setPackageName(String packageName) {
this.packageName = packageName;
}
@Override
protected Type getType() {
return Type.TABLE_PACKAGE;
}
@Override
protected void writeHeader(ByteBuffer output) {
output.putInt(id);
PackageUtils.writePackageName(output, packageName);
output.putInt(0); // typeStringsOffset. This value can't be computed here.
output.putInt(lastPublicType);
output.putInt(0); // keyStringsOffset. This value can't be computed here.
output.putInt(lastPublicKey);
output.putInt(typeIdOffset);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
int typeOffset = typeStringsOffset;
int keyOffset = keyStringsOffset;
int payloadOffset = 0;
for (Chunk chunk : getChunks().values()) {
if (chunk == getTypeStringPool()) {
typeOffset = payloadOffset + getHeaderSize();
} else if (chunk == getKeyStringPool()) {
keyOffset = payloadOffset + getHeaderSize();
}
byte[] chunkBytes = chunk.toByteArray(shrink);
output.write(chunkBytes);
payloadOffset = writePad(output, chunkBytes.length);
}
header.putInt(TYPE_OFFSET_OFFSET, typeOffset);
header.putInt(KEY_OFFSET_OFFSET, keyOffset);
}
header.putInt(TYPE_OFFSET_OFFSET, typeOffset);
header.putInt(KEY_OFFSET_OFFSET, keyOffset);
}
}
@@ -20,48 +20,53 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/** Provides utility methods for package names. */
/**
* Provides utility methods for package names.
*/
public final class PackageUtils {
public static final int PACKAGE_NAME_SIZE = 256;
public static final int PACKAGE_NAME_SIZE = 256;
private PackageUtils() {} // Prevent instantiation
private PackageUtils() {
} // Prevent instantiation
/**
* Reads the package name from the buffer and repositions the buffer to point directly after
* the package name.
* @param buffer The buffer containing the package name.
* @param offset The offset in the buffer to read from.
* @return The package name.
*/
public static String readPackageName(ByteBuffer buffer, int offset) {
byte[] data = buffer.array();
int length = 0;
// Look for the null terminator for the string instead of using the entire buffer.
// It's UTF-16 so check 2 bytes at a time to see if its double 0.
for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) {
if (data[i] == 0 && data[i + 1] == 0) {
length = i - offset;
break;
}
/**
* Reads the package name from the buffer and repositions the buffer to point directly after
* the package name.
*
* @param buffer The buffer containing the package name.
* @param offset The offset in the buffer to read from.
* @return The package name.
*/
public static String readPackageName(ByteBuffer buffer, int offset) {
byte[] data = buffer.array();
int length = 0;
// Look for the null terminator for the string instead of using the entire buffer.
// It's UTF-16 so check 2 bytes at a time to see if its double 0.
for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) {
if (data[i] == 0 && data[i + 1] == 0) {
length = i - offset;
break;
}
}
Charset utf16 = StandardCharsets.UTF_16LE;
String str = new String(data, offset, length, utf16);
buffer.position(offset + PACKAGE_NAME_SIZE);
return str;
}
Charset utf16 = StandardCharsets.UTF_16LE;
String str = new String(data, offset, length, utf16);
buffer.position(offset + PACKAGE_NAME_SIZE);
return str;
}
/**
* Writes the provided package name to the buffer in UTF-16.
* @param buffer The buffer that will be written to.
* @param packageName The package name that will be written to the buffer.
*/
public static void writePackageName(ByteBuffer buffer, String packageName) {
byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE);
buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE));
if (nameBytes.length < PACKAGE_NAME_SIZE) {
// pad out the remaining space with an empty array.
buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]);
/**
* Writes the provided package name to the buffer in UTF-16.
*
* @param buffer The buffer that will be written to.
* @param packageName The package name that will be written to the buffer.
*/
public static void writePackageName(ByteBuffer buffer, String packageName) {
byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE);
buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE));
if (nameBytes.length < PACKAGE_NAME_SIZE) {
// pad out the remaining space with an empty array.
buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]);
}
}
}
}
@@ -26,255 +26,276 @@ import java.util.Map.Entry;
/**
* Calculates extra information about an {@link ResourceEntry}, such as the total
* APK size the entry is responsible for.
*
* <p>
* This class is not thread-safe.
*/
public class ResourceEntryStatsCollector {
/** The size in bytes of an offset in a chunk. */
private static final int OFFSET_SIZE = 4;
/**
* The size in bytes of an offset in a chunk.
*/
private static final int OFFSET_SIZE = 4;
/** The size in bytes of overhead for styles, if present, in {@link StringPoolChunk}. */
private static final int STYLE_OVERHEAD = 8;
/**
* The size in bytes of overhead for styles, if present, in {@link StringPoolChunk}.
*/
private static final int STYLE_OVERHEAD = 8;
/**
* The number of bytes, in addition to the header, that the {@link PackageChunk} has in overhead
* excluding the chunks it contains.
*/
private static final int PACKAGE_CHUNK_OVERHEAD = 8;
/**
* The number of bytes, in addition to the header, that the {@link PackageChunk} has in overhead
* excluding the chunks it contains.
*/
private static final int PACKAGE_CHUNK_OVERHEAD = 8;
private final Map<ResourceEntry, ResourceStatistics> stats = new HashMap<>();
private final Map<ResourceEntry, ResourceStatistics> stats = new HashMap<>();
private final ArscBlamer blamer;
private final ArscBlamer blamer;
private final ResourceTableChunk resourceTable;
private final ResourceTableChunk resourceTable;
/**
* Creates a new {@link ResourceEntryStatsCollector}.
*
* @param blamer The blamer that maps resource entries to what they use.
* @param resourceTable The resource table that {@code blamer} is blamed on.
*/
public ResourceEntryStatsCollector(ArscBlamer blamer, ResourceTableChunk resourceTable) {
this.resourceTable = resourceTable;
this.blamer = blamer;
}
public void compute() throws IOException {
Preconditions.checkState(stats.isEmpty(), "Must only call #compute once.");
blamer.blame();
computeStringPoolSizes();
computePackageSizes();
}
/** Returns entries for which there are computed stats. Must first call {@link #compute}. */
public Map<ResourceEntry, ResourceStatistics> getStats() {
Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first.");
return Collections.unmodifiableMap(stats);
}
/** Returns computed stats for a given entry. Must first call {@link #compute}. */
public ResourceStatistics getStats(ResourceEntry entry) {
Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first.");
return stats.containsKey(entry) ? stats.get(entry) : ResourceStatistics.EMPTY;
}
private void computeStringPoolSizes() throws IOException {
computePoolSizes(resourceTable.getStringPool(), blamer.getStringToBlamedResources());
}
private void computePackageSizes() throws IOException {
computeTypePoolSizes();
computeKeyPoolSizes();
computeTypeSpecSizes();
computeTypeChunkSizes();
computePackageChunkSizes();
}
private void computeTypePoolSizes() throws IOException {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getTypeToBlamedResources().entrySet()) {
computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue());
}
}
private void computeKeyPoolSizes() throws IOException {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getKeyToBlamedResources().entrySet()) {
computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue());
}
}
private void computeTypeSpecSizes() {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getTypeToBlamedResources().entrySet()) {
computeTypeSpecSizes(entry.getKey(), entry.getValue());
}
}
private void computeTypeChunkSizes() {
for (Entry<TypeChunk.Entry, Collection<ResourceEntry>> entry
: blamer.getTypeEntryToBlamedResources().asMap().entrySet()) {
TypeChunk.Entry chunkEntry = entry.getKey();
TypeChunk typeChunk = chunkEntry.parent();
int size = chunkEntry.size() + OFFSET_SIZE;
int count = typeChunk.getEntries().size();
int nullEntries = typeChunk.getTotalEntryCount() - typeChunk.getEntries().size();
int overhead = typeChunk.getHeaderSize() + nullEntries * OFFSET_SIZE;
addSizes(entry.getValue(), overhead, size, count);
}
}
private void computePackageChunkSizes() {
for (Entry<PackageChunk, Collection<ResourceEntry>> entry
: blamer.getPackageToBlamedResources().asMap().entrySet()) {
int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD;
addSizes(entry.getValue(), overhead, 0, 1);
}
}
private void computePoolSizes(StringPoolChunk stringPool,
List<ResourceEntry>[] usages) throws IOException {
int overhead = stringPool.getHeaderSize();
if (stringPool.getStyleCount() > 0) {
overhead += STYLE_OVERHEAD;
/**
* Creates a new {@link ResourceEntryStatsCollector}.
*
* @param blamer The blamer that maps resource entries to what they use.
* @param resourceTable The resource table that {@code blamer} is blamed on.
*/
public ResourceEntryStatsCollector(ArscBlamer blamer, ResourceTableChunk resourceTable) {
this.resourceTable = resourceTable;
this.blamer = blamer;
}
// We have to iterate over the indices of the string pool, because it is possible that there are
// indices which have *no* associated resource entry (i.e. references from XML files without an
// entry in R).
int count = 0;
for (int i = 0; i < usages.length; ++i) {
if (usages[i].isEmpty()) {
overhead += computeStringAndStyleSize(stringPool, i);
} else {
++count;
}
public void compute() throws IOException {
Preconditions.checkState(stats.isEmpty(), "Must only call #compute once.");
blamer.blame();
computeStringPoolSizes();
computePackageSizes();
}
// Now that we know the number of actual entries, we can compute the size.
for (int i = 0; i < usages.length; ++i) {
if (usages[i].isEmpty()) {
continue;
}
int size = computeStringAndStyleSize(stringPool, i);
addSizes(usages[i], overhead, size, count);
}
}
private void computeTypeSpecSizes(PackageChunk packageChunk,
List<ResourceEntry>[] usages) {
for (int i = 0; i < usages.length; ++i) {
// The 1 here is to convert back to a 1-based index.
TypeSpecChunk typeSpec = packageChunk.getTypeSpecChunk(i + 1);
// TypeSpecChunk entries share everything equally.
addSizes(usages[i], typeSpec.getOriginalChunkSize(), 0, 1);
}
}
/**
* Given an {@code index} into a {@code stringPool}, return string's total size in bytes plus its
* style, if any.
*
* @param stringPool The string pool containing the {@code index}.
* @param index The (0-based) index of the string and (optional) style.
* @throws IOException Thrown if the style's length could not be computed.
*/
private int computeStringAndStyleSize(StringPoolChunk stringPool, int index)
throws IOException {
return computeStringSize(stringPool, index) + computeStyleSize(stringPool, index);
}
/** Given an {@code index} into a {@code stringPool}, return string's total size in bytes. */
private int computeStringSize(StringPoolChunk stringPool, int index) {
String string = stringPool.getString(index);
int result = BinaryResourceString.encodeString(string, stringPool.getStringType()).length;
result += OFFSET_SIZE;
return result;
}
/**
* Given an {@code index} into a {@code stringPool}, return style's total size in bytes or 0 if
* there's no style at that index.
*
* @throws IOException Thrown if the style's length could not be computed.
*/
private int computeStyleSize(StringPoolChunk stringPool, int index) throws IOException {
if (index >= stringPool.getStyleCount()) { // No style at index
return 0;
}
return stringPool.getStyle(index).toByteArray().length + OFFSET_SIZE;
}
/**
* Adds to the {@link #stats} of {@code entries} that reference a value in a chunk the bytes it's
* responsible for. This should only be called once per chunk-value pair.
*
* @param entries The resource entries referencing a single value in a chunk.
* @param overhead The number of bytes of overhead of a chunk. Typically the header size.
* @param size The size in bytes of a value in a chunk that {@code entries} reference.
* @param count The total number of values in the chunk.
*/
private void addSizes(Collection<ResourceEntry> entries, int overhead, int size, int count) {
int usageCount = entries.size();
for (ResourceEntry resourceEntry : entries) {
// TODO(acornwall): Replace with Java 8's #getOrDefault when possible.
if (!stats.containsKey(resourceEntry)) {
stats.put(resourceEntry, new ResourceStatistics());
}
ResourceStatistics resourceStats = stats.get(resourceEntry);
if (usageCount == 1) {
resourceStats.addPrivateSize(size);
} else {
resourceStats.addSharedSize(size);
}
// Special case: If the chunk only has one relevant value, removing this entry will remove the
// entire chunk.
if (usageCount == 1 && count == 1) {
resourceStats.addPrivateSize(overhead);
}
resourceStats.addProportionalSize(size, usageCount);
resourceStats.addProportionalSize(overhead, usageCount * count);
}
}
/** Stats for an individual {@link ResourceEntry}. */
public static class ResourceStatistics {
/** The empty, immutable instance of ResourceStatistics which contains 0 for all values. */
public static final ResourceStatistics EMPTY = new ResourceStatistics();
private int privateSize = 0;
private int sharedSize = 0;
private double proportionalSize = 0;
private ResourceStatistics() {}
/** The number of bytes that would be freed if this resource was removed. */
public int getPrivateSize() {
return privateSize;
/**
* Returns entries for which there are computed stats. Must first call {@link #compute}.
*/
public Map<ResourceEntry, ResourceStatistics> getStats() {
Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first.");
return Collections.unmodifiableMap(stats);
}
/** The number of bytes taken up by this resource that are also shared with other resources. */
public int getSharedSize() {
return sharedSize;
/**
* Returns computed stats for a given entry. Must first call {@link #compute}.
*/
public ResourceStatistics getStats(ResourceEntry entry) {
Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first.");
return stats.containsKey(entry) ? stats.get(entry) : ResourceStatistics.EMPTY;
}
/** The total size this resource is responsible for. */
public double getProportionalSize() {
return proportionalSize;
private void computeStringPoolSizes() throws IOException {
computePoolSizes(resourceTable.getStringPool(), blamer.getStringToBlamedResources());
}
private void addPrivateSize(int privateSize) {
this.privateSize += privateSize;
private void computePackageSizes() throws IOException {
computeTypePoolSizes();
computeKeyPoolSizes();
computeTypeSpecSizes();
computeTypeChunkSizes();
computePackageChunkSizes();
}
private void addSharedSize(int sharedSize) {
this.sharedSize += sharedSize;
private void computeTypePoolSizes() throws IOException {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getTypeToBlamedResources().entrySet()) {
computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue());
}
}
private void addProportionalSize(int numerator, int denominator) {
this.proportionalSize += 1.0 * numerator / denominator;
private void computeKeyPoolSizes() throws IOException {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getKeyToBlamedResources().entrySet()) {
computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue());
}
}
private void computeTypeSpecSizes() {
for (Entry<PackageChunk, List<ResourceEntry>[]> entry
: blamer.getTypeToBlamedResources().entrySet()) {
computeTypeSpecSizes(entry.getKey(), entry.getValue());
}
}
private void computeTypeChunkSizes() {
for (Entry<TypeChunk.Entry, Collection<ResourceEntry>> entry
: blamer.getTypeEntryToBlamedResources().asMap().entrySet()) {
TypeChunk.Entry chunkEntry = entry.getKey();
TypeChunk typeChunk = chunkEntry.parent();
int size = chunkEntry.size() + OFFSET_SIZE;
int count = typeChunk.getEntries().size();
int nullEntries = typeChunk.getTotalEntryCount() - typeChunk.getEntries().size();
int overhead = typeChunk.getHeaderSize() + nullEntries * OFFSET_SIZE;
addSizes(entry.getValue(), overhead, size, count);
}
}
private void computePackageChunkSizes() {
for (Entry<PackageChunk, Collection<ResourceEntry>> entry
: blamer.getPackageToBlamedResources().asMap().entrySet()) {
int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD;
addSizes(entry.getValue(), overhead, 0, 1);
}
}
private void computePoolSizes(StringPoolChunk stringPool,
List<ResourceEntry>[] usages) throws IOException {
int overhead = stringPool.getHeaderSize();
if (stringPool.getStyleCount() > 0) {
overhead += STYLE_OVERHEAD;
}
// We have to iterate over the indices of the string pool, because it is possible that there are
// indices which have *no* associated resource entry (i.e. references from XML files without an
// entry in R).
int count = 0;
for (int i = 0; i < usages.length; ++i) {
if (usages[i].isEmpty()) {
overhead += computeStringAndStyleSize(stringPool, i);
} else {
++count;
}
}
// Now that we know the number of actual entries, we can compute the size.
for (int i = 0; i < usages.length; ++i) {
if (usages[i].isEmpty()) {
continue;
}
int size = computeStringAndStyleSize(stringPool, i);
addSizes(usages[i], overhead, size, count);
}
}
private void computeTypeSpecSizes(PackageChunk packageChunk,
List<ResourceEntry>[] usages) {
for (int i = 0; i < usages.length; ++i) {
// The 1 here is to convert back to a 1-based index.
TypeSpecChunk typeSpec = packageChunk.getTypeSpecChunk(i + 1);
// TypeSpecChunk entries share everything equally.
addSizes(usages[i], typeSpec.getOriginalChunkSize(), 0, 1);
}
}
/**
* Given an {@code index} into a {@code stringPool}, return string's total size in bytes plus its
* style, if any.
*
* @param stringPool The string pool containing the {@code index}.
* @param index The (0-based) index of the string and (optional) style.
* @throws IOException Thrown if the style's length could not be computed.
*/
private int computeStringAndStyleSize(StringPoolChunk stringPool, int index)
throws IOException {
return computeStringSize(stringPool, index) + computeStyleSize(stringPool, index);
}
/**
* Given an {@code index} into a {@code stringPool}, return string's total size in bytes.
*/
private int computeStringSize(StringPoolChunk stringPool, int index) {
String string = stringPool.getString(index);
int result = BinaryResourceString.encodeString(string, stringPool.getStringType()).length;
result += OFFSET_SIZE;
return result;
}
/**
* Given an {@code index} into a {@code stringPool}, return style's total size in bytes or 0 if
* there's no style at that index.
*
* @throws IOException Thrown if the style's length could not be computed.
*/
private int computeStyleSize(StringPoolChunk stringPool, int index) throws IOException {
if (index >= stringPool.getStyleCount()) { // No style at index
return 0;
}
return stringPool.getStyle(index).toByteArray().length + OFFSET_SIZE;
}
/**
* Adds to the {@link #stats} of {@code entries} that reference a value in a chunk the bytes it's
* responsible for. This should only be called once per chunk-value pair.
*
* @param entries The resource entries referencing a single value in a chunk.
* @param overhead The number of bytes of overhead of a chunk. Typically the header size.
* @param size The size in bytes of a value in a chunk that {@code entries} reference.
* @param count The total number of values in the chunk.
*/
private void addSizes(Collection<ResourceEntry> entries, int overhead, int size, int count) {
int usageCount = entries.size();
for (ResourceEntry resourceEntry : entries) {
// TODO(acornwall): Replace with Java 8's #getOrDefault when possible.
if (!stats.containsKey(resourceEntry)) {
stats.put(resourceEntry, new ResourceStatistics());
}
ResourceStatistics resourceStats = stats.get(resourceEntry);
if (usageCount == 1) {
resourceStats.addPrivateSize(size);
} else {
resourceStats.addSharedSize(size);
}
// Special case: If the chunk only has one relevant value, removing this entry will remove the
// entire chunk.
if (usageCount == 1 && count == 1) {
resourceStats.addPrivateSize(overhead);
}
resourceStats.addProportionalSize(size, usageCount);
resourceStats.addProportionalSize(overhead, usageCount * count);
}
}
/**
* Stats for an individual {@link ResourceEntry}.
*/
public static class ResourceStatistics {
/**
* The empty, immutable instance of ResourceStatistics which contains 0 for all values.
*/
public static final ResourceStatistics EMPTY = new ResourceStatistics();
private int privateSize = 0;
private int sharedSize = 0;
private double proportionalSize = 0;
private ResourceStatistics() {
}
/**
* The number of bytes that would be freed if this resource was removed.
*/
public int getPrivateSize() {
return privateSize;
}
/**
* The number of bytes taken up by this resource that are also shared with other resources.
*/
public int getSharedSize() {
return sharedSize;
}
/**
* The total size this resource is responsible for.
*/
public double getProportionalSize() {
return proportionalSize;
}
private void addPrivateSize(int privateSize) {
this.privateSize += privateSize;
}
private void addSharedSize(int sharedSize) {
this.sharedSize += sharedSize;
}
private void addProportionalSize(int numerator, int denominator) {
this.proportionalSize += 1.0 * numerator / denominator;
}
}
}
}
@@ -16,9 +16,9 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
@@ -36,57 +36,66 @@ import java.util.Map;
*/
public final class ResourceTableChunk extends ChunkWithChunks {
/** A string pool containing all string resource values in the entire resource table. */
private StringPoolChunk stringPool;
/**
* The packages contained in this resource table.
*/
private final Map<String, PackageChunk> packages = new HashMap<>();
/**
* A string pool containing all string resource values in the entire resource table.
*/
private StringPoolChunk stringPool;
/** The packages contained in this resource table. */
private final Map<String, PackageChunk> packages = new HashMap<>();
protected ResourceTableChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
// packageCount. We ignore this, because we already know how many chunks we have.
Preconditions.checkState(buffer.getInt() >= 1, "ResourceTableChunk package count was < 1.");
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
packages.clear();
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof PackageChunk) {
PackageChunk packageChunk = (PackageChunk) chunk;
packages.put(packageChunk.getPackageName(), packageChunk);
} else if (chunk instanceof StringPoolChunk) {
stringPool = (StringPoolChunk) chunk;
}
ResourceTableChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
// packageCount. We ignore this, because we already know how many chunks we have.
Preconditions.checkState(buffer.getInt() >= 1, "ResourceTableChunk package count was < 1.");
}
Preconditions.checkNotNull(stringPool, "ResourceTableChunk must have a string pool.");
}
/** Returns the string pool containing all string resource values in the resource table. */
public StringPoolChunk getStringPool() {
return stringPool;
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
packages.clear();
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof PackageChunk) {
PackageChunk packageChunk = (PackageChunk) chunk;
packages.put(packageChunk.getPackageName(), packageChunk);
} else if (chunk instanceof StringPoolChunk) {
stringPool = (StringPoolChunk) chunk;
}
}
Preconditions.checkNotNull(stringPool, "ResourceTableChunk must have a string pool.");
}
/** Returns the package with the given {@code packageName}. Else, returns null. */
@Nullable
public PackageChunk getPackage(String packageName) {
return packages.get(packageName);
}
/**
* Returns the string pool containing all string resource values in the resource table.
*/
public StringPoolChunk getStringPool() {
return stringPool;
}
/** Returns the packages contained in this resource table. */
public Collection<PackageChunk> getPackages() {
return Collections.unmodifiableCollection(packages.values());
}
/**
* Returns the package with the given {@code packageName}. Else, returns null.
*/
@Nullable
public PackageChunk getPackage(String packageName) {
return packages.get(packageName);
}
@Override
protected Type getType() {
return Type.TABLE;
}
/**
* Returns the packages contained in this resource table.
*/
public Collection<PackageChunk> getPackages() {
return Collections.unmodifiableCollection(packages.values());
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(packages.size());
}
@Override
protected Type getType() {
return Type.TABLE;
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(packages.size());
}
}
@@ -22,20 +22,22 @@ import java.io.IOException;
* A resource, typically a @{link Chunk}, that can be converted to an array of bytes.
*/
public interface SerializableResource {
/**
* Converts this resource into an array of bytes representation.
* @return An array of bytes representing this resource.
* @throws IOException
*/
byte[] toByteArray() throws IOException;
/**
* Converts this resource into an array of bytes representation.
* @param shrink True if, when converting to a byte array, this resource can modify the returned
* bytes in an effort to reduce the size.
* @return An array of bytes representing this resource.
* @throws IOException
*/
byte[] toByteArray(boolean shrink) throws IOException;
/**
* Converts this resource into an array of bytes representation.
*
* @return An array of bytes representing this resource.
* @throws IOException
*/
byte[] toByteArray() throws IOException;
/**
* Converts this resource into an array of bytes representation.
*
* @param shrink True if, when converting to a byte array, this resource can modify the returned
* bytes in an effort to reduce the size.
* @return An array of bytes representing this resource.
* @throws IOException
*/
byte[] toByteArray(boolean shrink) throws IOException;
}
@@ -16,11 +16,11 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.io.LittleEndianDataOutputStream;
import androidx.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.IOException;
@@ -28,396 +28,416 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
/** Represents a string pool structure. */
/**
* Represents a string pool structure.
*/
public final class StringPoolChunk extends Chunk {
// These are the defined flags for the "flags" field of ResourceStringPoolHeader
private static final int SORTED_FLAG = 1 << 0;
private static final int UTF8_FLAG = 1 << 8;
// These are the defined flags for the "flags" field of ResourceStringPoolHeader
private static final int SORTED_FLAG = 1 << 0;
private static final int UTF8_FLAG = 1 << 8;
/** The offset from the start of the header that the stylesStart field is at. */
private static final int STYLE_START_OFFSET = 24;
/**
* The offset from the start of the header that the stylesStart field is at.
*/
private static final int STYLE_START_OFFSET = 24;
/** Flags. */
private final int flags;
/**
* Flags.
*/
private final int flags;
/** Index from header of the string data. */
private final int stringsStart;
/**
* Index from header of the string data.
*/
private final int stringsStart;
/** Index from header of the style data. */
private final int stylesStart;
/**
* Index from header of the style data.
*/
private final int stylesStart;
/**
* Number of strings in the original buffer. This is not necessarily the number of strings in
* {@code strings}.
*/
private final int stringCount;
/**
* Number of strings in the original buffer. This is not necessarily the number of strings in
* {@code strings}.
*/
private final int stringCount;
/**
* Number of styles in the original buffer. This is not necessarily the number of styles in
* {@code styles}.
*/
private final int styleCount;
/**
* Number of styles in the original buffer. This is not necessarily the number of styles in
* {@code styles}.
*/
private final int styleCount;
/**
* The strings ordered as they appear in the arsc file. e.g. strings.get(1234) gets the 1235th
* string in the arsc file.
*/
private final List<String> strings = new ArrayList<>();
/**
* The strings ordered as they appear in the arsc file. e.g. strings.get(1234) gets the 1235th
* string in the arsc file.
*/
private final List<String> strings = new ArrayList<>();
/**
* These styles have a 1:1 relationship with the strings. For example, styles.get(3) refers to
* the string at location strings.get(3). There are never more styles than strings (though there
* may be less). Inside of that are all of the styles referenced by that string.
*/
private final List<StringPoolStyle> styles = new ArrayList<>();
/**
* These styles have a 1:1 relationship with the strings. For example, styles.get(3) refers to
* the string at location strings.get(3). There are never more styles than strings (though there
* may be less). Inside of that are all of the styles referenced by that string.
*/
private final List<StringPoolStyle> styles = new ArrayList<>();
/**
* True if the original {@link StringPoolChunk} shows signs of being deduped. Specifically, this
* is set to true if there exists a string whose offset is <= the previous offset. This is used to
* preserve the deduping of strings for pools that have been deduped.
*/
private boolean isOriginalDeduped = false;
/**
* True if the original {@link StringPoolChunk} shows signs of being deduped. Specifically, this
* is set to true if there exists a string whose offset is <= the previous offset. This is used to
* preserve the deduping of strings for pools that have been deduped.
*/
private boolean isOriginalDeduped = false;
protected StringPoolChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
stringCount = buffer.getInt();
styleCount = buffer.getInt();
flags = buffer.getInt();
stringsStart = buffer.getInt();
stylesStart = buffer.getInt();
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
strings.addAll(readStrings(buffer, offset + stringsStart, stringCount));
styles.addAll(readStyles(buffer, offset + stylesStart, styleCount));
}
/**
* Returns the 0-based index of the first occurrence of the given string, or -1 if the string is
* not in the pool. This runs in O(n) time.
*
* @param string The string to check the pool for.
* @return Index of the string, or -1 if not found.
*/
public int indexOf(String string) {
return strings.indexOf(string);
}
/**
* Returns a string at the given (0-based) index.
*
* @param index The (0-based) index of the string to return.
* @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()).
*/
public String getString(int index) {
return strings.get(index);
}
public void updateString(int index, String value){
strings.set(index, value);
}
/** Returns the number of strings in this pool. */
public int getStringCount() {
return strings.size();
}
/**
* Returns a style at the given (0-based) index.
*
* @param index The (0-based) index of the style to return.
* @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()).
*/
public StringPoolStyle getStyle(int index) {
return styles.get(index);
}
/** Returns the number of styles in this pool. */
public int getStyleCount() {
return styles.size();
}
/** Returns the type of strings in this pool. */
public BinaryResourceString.Type getStringType() {
return isUTF8() ? BinaryResourceString.Type.UTF8 : BinaryResourceString.Type.UTF16;
}
@Override
protected Type getType() {
return Type.STRING_POOL;
}
/** Returns the number of bytes needed for offsets based on {@code strings} and {@code styles}. */
private int getOffsetSize() {
return (strings.size() + styles.size()) * 4;
}
/**
* True if this string pool contains strings in UTF-8 format. Otherwise, strings are in UTF-16.
*
* @return true if @{code strings} are in UTF-8; false if they're in UTF-16.
*/
public boolean isUTF8() {
return (flags & UTF8_FLAG) != 0;
}
/**
* True if this string pool contains already-sorted strings.
*
* @return true if @{code strings} are sorted.
*/
public boolean isSorted() {
return (flags & SORTED_FLAG) != 0;
}
private List<String> readStrings(ByteBuffer buffer, int offset, int count) {
List<String> result = new ArrayList<>();
int previousOffset = -1;
// After the header, we now have an array of offsets for the strings in this pool.
for (int i = 0; i < count; ++i) {
int stringOffset = offset + buffer.getInt();
result.add(BinaryResourceString.decodeString(buffer, stringOffset, getStringType()));
if (stringOffset <= previousOffset) {
isOriginalDeduped = true;
}
previousOffset = stringOffset;
}
return result;
}
private List<StringPoolStyle> readStyles(ByteBuffer buffer, int offset, int count) {
List<StringPoolStyle> result = new ArrayList<>();
// After the array of offsets for the strings in the pool, we have an offset for the styles
// in this pool.
for (int i = 0; i < count; ++i) {
int styleOffset = offset + buffer.getInt();
result.add(StringPoolStyle.create(buffer, styleOffset, this));
}
return result;
}
private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int stringOffset = 0;
Map<String, Integer> used = new HashMap<>(); // Keeps track of strings already written
for (String string : strings) {
// Dedupe everything except stylized strings, unless shrink is true (then dedupe everything)
if (used.containsKey(string) && (shrink || isOriginalDeduped)) {
Integer offset = used.get(string);
offsets.putInt(offset == null ? 0 : offset);
} else {
byte[] encodedString = BinaryResourceString.encodeString(string, getStringType());
payload.write(encodedString);
used.put(string, stringOffset);
offsets.putInt(stringOffset);
stringOffset += encodedString.length;
}
StringPoolChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
stringCount = buffer.getInt();
styleCount = buffer.getInt();
flags = buffer.getInt();
stringsStart = buffer.getInt();
stylesStart = buffer.getInt();
}
// ARSC files pad to a 4-byte boundary. We should do so too.
stringOffset = writePad(payload, stringOffset);
return stringOffset;
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
strings.addAll(readStrings(buffer, offset + stringsStart, stringCount));
styles.addAll(readStyles(buffer, offset + stylesStart, styleCount));
}
private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int styleOffset = 0;
/**
* Returns the 0-based index of the first occurrence of the given string, or -1 if the string is
* not in the pool. This runs in O(n) time.
*
* @param string The string to check the pool for.
* @return Index of the string, or -1 if not found.
*/
public int indexOf(String string) {
return strings.indexOf(string);
}
/**
* Returns a string at the given (0-based) index.
*
* @param index The (0-based) index of the string to return.
* @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()).
*/
public String getString(int index) {
return strings.get(index);
}
public void updateString(int index, String value) {
strings.set(index, value);
}
/**
* Returns the number of strings in this pool.
*/
public int getStringCount() {
return strings.size();
}
/**
* Returns a style at the given (0-based) index.
*
* @param index The (0-based) index of the style to return.
* @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()).
*/
public StringPoolStyle getStyle(int index) {
return styles.get(index);
}
/**
* Returns the number of styles in this pool.
*/
public int getStyleCount() {
return styles.size();
}
/**
* Returns the type of strings in this pool.
*/
public BinaryResourceString.Type getStringType() {
return isUTF8() ? BinaryResourceString.Type.UTF8 : BinaryResourceString.Type.UTF16;
}
@Override
protected Type getType() {
return Type.STRING_POOL;
}
/**
* Returns the number of bytes needed for offsets based on {@code strings} and {@code styles}.
*/
private int getOffsetSize() {
return (strings.size() + styles.size()) * 4;
}
/**
* True if this string pool contains strings in UTF-8 format. Otherwise, strings are in UTF-16.
*
* @return true if @{code strings} are in UTF-8; false if they're in UTF-16.
*/
public boolean isUTF8() {
return (flags & UTF8_FLAG) != 0;
}
/**
* True if this string pool contains already-sorted strings.
*
* @return true if @{code strings} are sorted.
*/
public boolean isSorted() {
return (flags & SORTED_FLAG) != 0;
}
private List<String> readStrings(ByteBuffer buffer, int offset, int count) {
List<String> result = new ArrayList<>();
int previousOffset = -1;
// After the header, we now have an array of offsets for the strings in this pool.
for (int i = 0; i < count; ++i) {
int stringOffset = offset + buffer.getInt();
result.add(BinaryResourceString.decodeString(buffer, stringOffset, getStringType()));
if (stringOffset <= previousOffset) {
isOriginalDeduped = true;
}
previousOffset = stringOffset;
}
return result;
}
private List<StringPoolStyle> readStyles(ByteBuffer buffer, int offset, int count) {
List<StringPoolStyle> result = new ArrayList<>();
// After the array of offsets for the strings in the pool, we have an offset for the styles
// in this pool.
for (int i = 0; i < count; ++i) {
int styleOffset = offset + buffer.getInt();
result.add(StringPoolStyle.create(buffer, styleOffset, this));
}
return result;
}
private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int stringOffset = 0;
Map<String, Integer> used = new HashMap<>(); // Keeps track of strings already written
for (String string : strings) {
// Dedupe everything except stylized strings, unless shrink is true (then dedupe everything)
if (used.containsKey(string) && (shrink || isOriginalDeduped)) {
Integer offset = used.get(string);
offsets.putInt(offset == null ? 0 : offset);
} else {
byte[] encodedString = BinaryResourceString.encodeString(string, getStringType());
payload.write(encodedString);
used.put(string, stringOffset);
offsets.putInt(stringOffset);
stringOffset += encodedString.length;
}
}
// ARSC files pad to a 4-byte boundary. We should do so too.
stringOffset = writePad(payload, stringOffset);
return stringOffset;
}
private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int styleOffset = 0;
if (!styles.isEmpty()) {
Map<StringPoolStyle, Integer> used = new HashMap<>(); // Keeps track of bytes already written
for (StringPoolStyle style : styles) {
if (!used.containsKey(style) || !shrink) {
byte[] encodedStyle = style.toByteArray(shrink);
payload.write(encodedStyle);
used.put(style, styleOffset);
offsets.putInt(styleOffset);
styleOffset += encodedStyle.length;
} else { // contains key and shrink is true
Integer offset = used.get(style);
offsets.putInt(offset == null ? 0 : offset);
Map<StringPoolStyle, Integer> used = new HashMap<>(); // Keeps track of bytes already written
for (StringPoolStyle style : styles) {
if (!used.containsKey(style) || !shrink) {
byte[] encodedStyle = style.toByteArray(shrink);
payload.write(encodedStyle);
used.put(style, styleOffset);
offsets.putInt(styleOffset);
styleOffset += encodedStyle.length;
} else { // contains key and shrink is true
Integer offset = used.get(style);
offsets.putInt(offset == null ? 0 : offset);
}
}
// The end of the spans are terminated with another sentinel value
payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END);
styleOffset += 4;
// TODO(acornwall): There appears to be an extra SPAN_END here... why?
payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END);
styleOffset += 4;
styleOffset = writePad(payload, styleOffset);
}
}
// The end of the spans are terminated with another sentinel value
payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END);
styleOffset += 4;
// TODO(acornwall): There appears to be an extra SPAN_END here... why?
payload.writeInt(StringPoolStyle.RES_STRING_POOL_SPAN_END);
styleOffset += 4;
styleOffset = writePad(payload, styleOffset);
}
return styleOffset;
}
@Override
protected void writeHeader(ByteBuffer output) {
int stringsStart = getHeaderSize() + getOffsetSize();
output.putInt(strings.size());
output.putInt(styles.size());
output.putInt(flags);
output.putInt(strings.isEmpty() ? 0 : stringsStart);
output.putInt(0); // Placeholder. The styles starting offset cannot be computed at this point.
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int stringOffset = 0;
ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize());
offsets.order(ByteOrder.LITTLE_ENDIAN);
// Write to a temporary payload so we can rearrange this and put the offsets first
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
stringOffset = writeStrings(payload, offsets, shrink);
writeStyles(payload, offsets, shrink);
}
output.write(offsets.array());
output.write(baos.toByteArray());
if (!styles.isEmpty()) {
header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset);
}
}
/**
* Represents all of the styles for a particular string. The string is determined by its index
* in {@link StringPoolChunk}.
*/
public static class StringPoolStyle implements SerializableResource {
// Styles are a list of integers with 0xFFFFFFFF serving as a sentinel value.
static final int RES_STRING_POOL_SPAN_END = 0xFFFFFFFF;
private final List<StringPoolSpan> spans;
static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) {
Builder<StringPoolSpan> spans = ImmutableList.builder();
int nameIndex = buffer.getInt(offset);
while (nameIndex != RES_STRING_POOL_SPAN_END) {
spans.add(StringPoolSpan.create(buffer, offset, parent));
offset += StringPoolSpan.SPAN_LENGTH;
nameIndex = buffer.getInt(offset);
}
return new StringPoolStyle(spans.build());
}
private StringPoolStyle(List<StringPoolSpan> spans) {
this.spans = spans;
return styleOffset;
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
protected void writeHeader(ByteBuffer output) {
int stringsStart = getHeaderSize() + getOffsetSize();
output.putInt(strings.size());
output.putInt(styles.size());
output.putInt(flags);
output.putInt(strings.isEmpty() ? 0 : stringsStart);
output.putInt(0); // Placeholder. The styles starting offset cannot be computed at this point.
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int stringOffset = 0;
ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize());
offsets.order(ByteOrder.LITTLE_ENDIAN);
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
for (StringPoolSpan span : spans) {
byte[] encodedSpan = span.toByteArray(shrink);
if (encodedSpan.length != StringPoolSpan.SPAN_LENGTH) {
throw new IllegalStateException("Encountered a span of invalid length.");
}
payload.write(encodedSpan);
// Write to a temporary payload so we can rearrange this and put the offsets first
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
stringOffset = writeStrings(payload, offsets, shrink);
writeStyles(payload, offsets, shrink);
}
payload.writeInt(RES_STRING_POOL_SPAN_END);
}
return baos.toByteArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringPoolStyle that = (StringPoolStyle)o;
return Objects.equals(spans, that.spans);
}
@Override
public int hashCode() {
return Objects.hash(spans);
output.write(offsets.array());
output.write(baos.toByteArray());
if (!styles.isEmpty()) {
header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset);
}
}
/**
* Returns a brief description of the contents of this style. The representation of this
* information is subject to change, but below is a typical example:
*
* <pre>"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"</pre>
* Represents all of the styles for a particular string. The string is determined by its index
* in {@link StringPoolChunk}.
*/
@Override
public String toString() {
return String.format("StringPoolStyle{spans=%s}", spans);
}
}
public static class StringPoolStyle implements SerializableResource {
/** Represents a styled span associated with a specific string. */
private static class StringPoolSpan implements SerializableResource {
static final int SPAN_LENGTH = 12;
// Styles are a list of integers with 0xFFFFFFFF serving as a sentinel value.
static final int RES_STRING_POOL_SPAN_END = 0xFFFFFFFF;
private final List<StringPoolSpan> spans;
private final int nameIndex;
private final int start;
private final int stop;
private final StringPoolChunk parent;
private StringPoolStyle(List<StringPoolSpan> spans) {
this.spans = spans;
}
static StringPoolSpan create(ByteBuffer buffer, int offset, StringPoolChunk parent) {
int nameIndex = buffer.getInt(offset);
int start = buffer.getInt(offset + 4);
int stop = buffer.getInt(offset + 8);
return new StringPoolSpan(nameIndex, start, stop, parent);
}
static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) {
Builder<StringPoolSpan> spans = ImmutableList.builder();
int nameIndex = buffer.getInt(offset);
while (nameIndex != RES_STRING_POOL_SPAN_END) {
spans.add(StringPoolSpan.create(buffer, offset, parent));
offset += StringPoolSpan.SPAN_LENGTH;
nameIndex = buffer.getInt(offset);
}
return new StringPoolStyle(spans.build());
}
private StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) {
this.nameIndex = nameIndex;
this.start = start;
this.stop = stop;
this.parent = parent;
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
}
@Override
public final byte[] toByteArray() {
return toByteArray(false);
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@Override
public final byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SPAN_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(nameIndex);
buffer.putInt(start);
buffer.putInt(stop);
return buffer.array();
}
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
for (StringPoolSpan span : spans) {
byte[] encodedSpan = span.toByteArray(shrink);
if (encodedSpan.length != StringPoolSpan.SPAN_LENGTH) {
throw new IllegalStateException("Encountered a span of invalid length.");
}
payload.write(encodedSpan);
}
payload.writeInt(RES_STRING_POOL_SPAN_END);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringPoolSpan that = (StringPoolSpan)o;
return nameIndex == that.nameIndex &&
start == that.start &&
stop == that.stop &&
Objects.equals(parent, that.parent);
}
return baos.toByteArray();
}
@Override
public int hashCode() {
return Objects.hash(nameIndex, start, stop, parent);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringPoolStyle that = (StringPoolStyle) o;
return Objects.equals(spans, that.spans);
}
@Override
public int hashCode() {
return Objects.hash(spans);
}
/**
* Returns a brief description of the contents of this style. The representation of this
* information is subject to change, but below is a typical example:
*
* <pre>"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"</pre>
*/
@Override
public String toString() {
return String.format("StringPoolStyle{spans=%s}", spans);
}
}
/**
* Returns a brief description of this span. The representation of this information is subject
* to change, but below is a typical example:
*
* <pre>"StringPoolSpan{foo, start=0, stop=5}"</pre>
* Represents a styled span associated with a specific string.
*/
@Override
public String toString() {
return String.format("StringPoolSpan{%s, start=%d, stop=%d}",
parent.getString(nameIndex), start, stop);
private static class StringPoolSpan implements SerializableResource {
static final int SPAN_LENGTH = 12;
private final int nameIndex;
private final int start;
private final int stop;
private final StringPoolChunk parent;
private StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) {
this.nameIndex = nameIndex;
this.start = start;
this.stop = stop;
this.parent = parent;
}
static StringPoolSpan create(ByteBuffer buffer, int offset, StringPoolChunk parent) {
int nameIndex = buffer.getInt(offset);
int start = buffer.getInt(offset + 4);
int stop = buffer.getInt(offset + 8);
return new StringPoolSpan(nameIndex, start, stop, parent);
}
@Override
public final byte[] toByteArray() {
return toByteArray(false);
}
@Override
public final byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SPAN_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(nameIndex);
buffer.putInt(start);
buffer.putInt(stop);
return buffer.array();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringPoolSpan that = (StringPoolSpan) o;
return nameIndex == that.nameIndex &&
start == that.start &&
stop == that.stop &&
Objects.equals(parent, that.parent);
}
@Override
public int hashCode() {
return Objects.hash(nameIndex, start, stop, parent);
}
/**
* Returns a brief description of this span. The representation of this information is subject
* to change, but below is a typical example:
*
* <pre>"StringPoolSpan{foo, start=0, stop=5}"</pre>
*/
@Override
public String toString() {
return String.format("StringPoolSpan{%s, start=%d, stop=%d}",
parent.getString(nameIndex), start, stop);
}
}
}
}
@@ -16,11 +16,11 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.io.LittleEndianDataOutputStream;
import com.google.common.primitives.UnsignedBytes;
import androidx.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.IOException;
@@ -38,412 +38,483 @@ import java.util.*;
*/
public final class TypeChunk extends Chunk {
/** The type identifier of the resource type this chunk is holding. */
private final int id;
/** The number of resources of this type at creation time. */
private final int entryCount;
/** The offset (from {@code offset}) in the original buffer where {@code entries} start. */
private final int entriesStart;
/** The resource configuration that these resource entries correspond to. */
private BinaryResourceConfiguration configuration;
/** A sparse list of resource entries defined by this chunk. */
private final Map<Integer, Entry> entries = new TreeMap<>();
protected TypeChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = UnsignedBytes.toInt(buffer.get());
buffer.position(buffer.position() + 3); // Skip 3 bytes for packing
entryCount = buffer.getInt();
entriesStart = buffer.getInt();
configuration = BinaryResourceConfiguration.create(buffer);
}
@Override
protected void init(ByteBuffer buffer) {
int offset = this.offset + entriesStart;
for (int i = 0; i < entryCount; ++i) {
Entry entry = Entry.create(buffer, offset, this);
if (entry != null) {
entries.put(i, entry);
}
}
}
/** Returns the (1-based) type id of the resource types that this {@link TypeChunk} is holding. */
public int getId() {
return id;
}
/** Returns the name of the type this chunk represents (e.g. string, attr, id). */
public String getTypeName() {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk typePool = packageChunk.getTypeStringPool();
Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass());
return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index
}
/** Returns the resource configuration that these resource entries correspond to. */
public BinaryResourceConfiguration getConfiguration() {
return configuration;
}
/**
* Sets the resource configuration that this chunk's entries correspond to.
*
* @param configuration The new configuration.
*/
public void setConfiguration(BinaryResourceConfiguration configuration) {
this.configuration = configuration;
}
/** Returns the total number of entries for this type + configuration, including null entries. */
public int getTotalEntryCount() {
return entryCount;
}
/** Returns a sparse list of 0-based indices to resource entries defined by this chunk. */
public Map<Integer, Entry> getEntries() {
return Collections.unmodifiableMap(entries);
}
/** Returns true if this chunk contains an entry for {@code resourceId}. */
public boolean containsResource(BinaryResourceIdentifier resourceId) {
PackageChunk packageChunk = Preconditions.checkNotNull(getPackageChunk());
int packageId = packageChunk.getId();
int typeId = getId();
return resourceId.packageId() == packageId
&& resourceId.typeId() == typeId
&& entries.containsKey(resourceId.entryId());
}
/**
* Overrides the entries in this chunk at the given index:entry pairs in {@code entries}.
* For example, if the current list of entries is {0: foo, 1: bar, 2: baz}, and {@code entries}
* is {1: qux, 3: quux}, then the entries will be changed to {0: foo, 1: qux, 2: baz}. If an entry
* has an index that does not exist in the dense entry list, then it is considered a no-op for
* that single entry.
*
* @param entries A sparse list containing index:entry pairs to override.
*/
public void overrideEntries(Map<Integer, Entry> entries) {
for (Map.Entry<Integer, Entry> entry : entries.entrySet()) {
int index = entry.getKey() != null ? entry.getKey() : -1;
overrideEntry(index, entry.getValue());
}
}
/**
* Overrides an entry at the given index. Passing null for the {@code entry} will remove that
* entry from {@code entries}. Indices < 0 or >= {@link #getTotalEntryCount()} are a no-op.
*
* @param index The 0-based index for the entry to override.
* @param entry The entry to override, or null if the entry should be removed at this location.
*/
public void overrideEntry(int index, @Nullable Entry entry) {
if (index >= 0 && index < entryCount) {
if (entry != null) {
entries.put(index, entry);
} else {
entries.remove(index);
}
}
}
protected String getString(int index) {
ResourceTableChunk resourceTable = getResourceTableChunk();
Preconditions.checkNotNull(resourceTable, "%s has no resource table.", getClass());
return resourceTable.getStringPool().getString(index);
}
protected String getKeyName(int index) {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk keyPool = packageChunk.getKeyStringPool();
Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass());
return keyPool.getString(index);
}
protected void updateKey(int index, String value) {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk keyPool = packageChunk.getKeyStringPool();
Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass());
keyPool.updateString(index, value);
}
@Nullable
private ResourceTableChunk getResourceTableChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof ResourceTableChunk)) {
chunk = chunk.getParent();
}
return chunk != null ? (ResourceTableChunk) chunk : null;
}
/** Returns the package enclosing this chunk, if any. Else, returns null. */
@Nullable
public PackageChunk getPackageChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof PackageChunk)) {
chunk = chunk.getParent();
}
return chunk != null ? (PackageChunk) chunk : null;
}
@Override
protected Type getType() {
return Type.TABLE_TYPE;
}
/** Returns the number of bytes needed for offsets based on {@code entries}. */
private int getOffsetSize() {
return entryCount * 4;
}
private int writeEntries(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int entryOffset = 0;
for (int i = 0; i < entryCount; ++i) {
Entry entry = entries.get(i);
if (entry == null) {
offsets.putInt(Entry.NO_ENTRY);
} else {
byte[] encodedEntry = entry.toByteArray(shrink);
payload.write(encodedEntry);
offsets.putInt(entryOffset);
entryOffset += encodedEntry.length;
}
}
entryOffset = writePad(payload, entryOffset);
return entryOffset;
}
@Override
protected void writeHeader(ByteBuffer output) {
int entriesStart = getHeaderSize() + getOffsetSize();
output.putInt(id); // Write an unsigned byte with 3 bytes padding
output.putInt(entryCount);
output.putInt(entriesStart);
output.put(configuration.toByteArray(false));
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()).order(ByteOrder.LITTLE_ENDIAN);
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
writeEntries(payload, offsets, shrink);
}
output.write(offsets.array());
output.write(baos.toByteArray());
}
/** An {@link Entry} in a {@link TypeChunk}. Contains one or more {@link BinaryResourceValue}. */
public static class Entry implements SerializableResource {
/** An entry offset that indicates that a given resource is not present. */
public static final int NO_ENTRY = 0xFFFFFFFF;
/** Set if this is a complex resource. Otherwise, it's a simple resource. */
private static final int FLAG_COMPLEX = 0x0001;
/** Size of a single resource id + value mapping entry. */
private static final int MAPPING_SIZE = 4 + BinaryResourceValue.SIZE;
private final int headerSize;
private final int flags;
private final int keyIndex;
private BinaryResourceValue value;
private Map<Integer, BinaryResourceValue> values;
private final int parentEntry;
private final TypeChunk parent;
private Entry(int headerSize,
int flags,
int keyIndex,
BinaryResourceValue value,
Map<Integer, BinaryResourceValue> values,
int parentEntry,
TypeChunk parent) {
this.headerSize = headerSize;
this.flags = flags;
this.keyIndex = keyIndex;
this.value = value;
this.values = values;
this.parentEntry = parentEntry;
this.parent = parent;
}
/** Number of bytes in the header of the {@link Entry}. */
public int headerSize() { return headerSize; }
/** Resource entry flags. */
public int flags() { return flags; }
/** Index into {@link PackageChunk#getKeyStringPool} identifying this entry. */
public int keyIndex() { return keyIndex; }
/** The value of this resource entry, if this is not a complex entry. Else, null. */
@Nullable
public BinaryResourceValue value() { return value; }
/**
* The type identifier of the resource type this chunk is holding.
*/
private final int id;
/**
* Update the value of this resource entry
* @param value
* The number of resources of this type at creation time.
*/
public void updateValue(@Nullable BinaryResourceValue value){
this.value = value;
private final int entryCount;
/**
* The offset (from {@code offset}) in the original buffer where {@code entries} start.
*/
private final int entriesStart;
/**
* A sparse list of resource entries defined by this chunk.
*/
private final Map<Integer, Entry> entries = new TreeMap<>();
/**
* The resource configuration that these resource entries correspond to.
*/
private BinaryResourceConfiguration configuration;
TypeChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = UnsignedBytes.toInt(buffer.get());
buffer.position(buffer.position() + 3); // Skip 3 bytes for packing
entryCount = buffer.getInt();
entriesStart = buffer.getInt();
configuration = BinaryResourceConfiguration.create(buffer);
}
/** The extra values in this resource entry if this {@link #isComplex}. */
public Map<Integer, BinaryResourceValue> values() { return values; }
/** Update the value in this resource entry if this {@link #isComplex}. */
public void updateValue(int index, BinaryResourceValue value){
if (index >= 0) {
if (value != null) {
values.put(index, value);
} else {
values.remove(index);
@Override
protected void init(ByteBuffer buffer) {
int offset = this.offset + entriesStart;
for (int i = 0; i < entryCount; ++i) {
Entry entry = Entry.create(buffer, offset, this);
if (entry != null) {
entries.put(i, entry);
}
}
}
}
/**
* Entry into {@link PackageChunk} that is the parent {@link Entry} to this entry.
* This value only makes sense when this is complex ({@link #isComplex} returns true).
* Returns the (1-based) type id of the resource types that this {@link TypeChunk} is holding.
*/
public int parentEntry() { return parentEntry; }
/** The {@link TypeChunk} that this resource entry belongs to. */
public TypeChunk parent() { return parent; }
/** Returns the name of the type this chunk represents (e.g. string, attr, id). */
public final String typeName() {
return parent().getTypeName();
}
/** The total number of bytes that this {@link Entry} takes up. */
public final int size() {
return headerSize() + (isComplex() ? values().size() * MAPPING_SIZE : BinaryResourceValue.SIZE);
}
/** Returns the key name identifying this resource entry. */
public final String key() {
return parent().getKeyName(keyIndex());
}
/** Update the key name in this resource entry. */
public final void updateKey(int keyIndex, String value) {
parent().updateKey(keyIndex, value);
}
/** Returns true if this is a complex resource. */
public final boolean isComplex() {
return (flags() & FLAG_COMPLEX) != 0;
public int getId() {
return id;
}
/**
* Creates a new {@link Entry} whose contents start at the 0-based position in
* {@code buffer} given by a 4-byte value read from {@code buffer} and then added to
* {@code baseOffset}. If the value read from {@code buffer} is equal to {@link #NO_ENTRY}, then
* null is returned as there is no resource at that position.
* Returns the name of the type this chunk represents (e.g. string, attr, id).
*/
public String getTypeName() {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk typePool = packageChunk.getTypeStringPool();
Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass());
return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index
}
/**
* Returns the resource configuration that these resource entries correspond to.
*/
public BinaryResourceConfiguration getConfiguration() {
return configuration;
}
/**
* Sets the resource configuration that this chunk's entries correspond to.
*
* <p>Otherwise, this position is parsed and returned as an {@link Entry}.
* @param configuration The new configuration.
*/
public void setConfiguration(BinaryResourceConfiguration configuration) {
this.configuration = configuration;
}
/**
* Returns the total number of entries for this type + configuration, including null entries.
*/
public int getTotalEntryCount() {
return entryCount;
}
/**
* Returns a sparse list of 0-based indices to resource entries defined by this chunk.
*/
public Map<Integer, Entry> getEntries() {
return Collections.unmodifiableMap(entries);
}
/**
* Returns true if this chunk contains an entry for {@code resourceId}.
*/
public boolean containsResource(BinaryResourceIdentifier resourceId) {
PackageChunk packageChunk = Preconditions.checkNotNull(getPackageChunk());
int packageId = packageChunk.getId();
int typeId = getId();
return resourceId.packageId() == packageId
&& resourceId.typeId() == typeId
&& entries.containsKey(resourceId.entryId());
}
/**
* Overrides the entries in this chunk at the given index:entry pairs in {@code entries}.
* For example, if the current list of entries is {0: foo, 1: bar, 2: baz}, and {@code entries}
* is {1: qux, 3: quux}, then the entries will be changed to {0: foo, 1: qux, 2: baz}. If an entry
* has an index that does not exist in the dense entry list, then it is considered a no-op for
* that single entry.
*
* @param buffer A buffer positioned at an offset to an {@link Entry}.
* @param baseOffset Offset that must be added to the value at {@code buffer}'s position.
* @param parent The {@link TypeChunk} that this resource entry belongs to.
* @return New {@link Entry} or null if there is no resource at this location.
* @param entries A sparse list containing index:entry pairs to override.
*/
public void overrideEntries(Map<Integer, Entry> entries) {
for (Map.Entry<Integer, Entry> entry : entries.entrySet()) {
int index = entry.getKey() != null ? entry.getKey() : -1;
overrideEntry(index, entry.getValue());
}
}
/**
* Overrides an entry at the given index. Passing null for the {@code entry} will remove that
* entry from {@code entries}. Indices < 0 or >= {@link #getTotalEntryCount()} are a no-op.
*
* @param index The 0-based index for the entry to override.
* @param entry The entry to override, or null if the entry should be removed at this location.
*/
public void overrideEntry(int index, @Nullable Entry entry) {
if (index >= 0 && index < entryCount) {
if (entry != null) {
entries.put(index, entry);
} else {
entries.remove(index);
}
}
}
private String getString(int index) {
ResourceTableChunk resourceTable = getResourceTableChunk();
Preconditions.checkNotNull(resourceTable, "%s has no resource table.", getClass());
return resourceTable.getStringPool().getString(index);
}
private String getKeyName(int index) {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk keyPool = packageChunk.getKeyStringPool();
Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass());
return keyPool.getString(index);
}
private void updateKey(int index, String value) {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk keyPool = packageChunk.getKeyStringPool();
Preconditions.checkNotNull(keyPool, "%s's parent package has no key pool.", getClass());
keyPool.updateString(index, value);
}
@Nullable
private ResourceTableChunk getResourceTableChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof ResourceTableChunk)) {
chunk = chunk.getParent();
}
return chunk != null ? (ResourceTableChunk) chunk : null;
}
/**
* Returns the package enclosing this chunk, if any. Else, returns null.
*/
@Nullable
public static Entry create(ByteBuffer buffer, int baseOffset, TypeChunk parent) {
int offset = buffer.getInt();
if (offset == NO_ENTRY) {
return null;
}
int position = buffer.position();
buffer.position(baseOffset + offset); // Set buffer position to resource entry start
Entry result = newInstance(buffer, parent);
buffer.position(position); // Restore buffer position
return result;
}
@Nullable
private static Entry newInstance(ByteBuffer buffer, TypeChunk parent) {
int headerSize = buffer.getShort() & 0xFFFF;
int flags = buffer.getShort() & 0xFFFF;
int keyIndex = buffer.getInt();
BinaryResourceValue value = null;
Map<Integer, BinaryResourceValue> values = new LinkedHashMap<>();
int parentEntry = 0;
if ((flags & FLAG_COMPLEX) != 0) {
parentEntry = buffer.getInt();
int valueCount = buffer.getInt();
for (int i = 0; i < valueCount; ++i) {
values.put(buffer.getInt(), BinaryResourceValue.create(buffer));
public PackageChunk getPackageChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof PackageChunk)) {
chunk = chunk.getParent();
}
} else {
value = BinaryResourceValue.create(buffer);
}
return new Entry(headerSize, flags, keyIndex, value, values, parentEntry, parent);
return chunk != null ? (PackageChunk) chunk : null;
}
@Override
public final byte[] toByteArray() {
return toByteArray(false);
protected Type getType() {
return Type.TABLE_TYPE;
}
@Override
public final byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) headerSize());
buffer.putShort((short) flags());
buffer.putInt(keyIndex());
if (isComplex()) {
buffer.putInt(parentEntry());
buffer.putInt(values().size());
for (Map.Entry<Integer, BinaryResourceValue> entry : values().entrySet()) {
buffer.putInt(entry.getKey());
buffer.put(entry.getValue().toByteArray(shrink));
/**
* Returns the number of bytes needed for offsets based on {@code entries}.
*/
private int getOffsetSize() {
return entryCount * 4;
}
private int writeEntries(DataOutput payload, ByteBuffer offsets, boolean shrink)
throws IOException {
int entryOffset = 0;
for (int i = 0; i < entryCount; ++i) {
Entry entry = entries.get(i);
if (entry == null) {
offsets.putInt(Entry.NO_ENTRY);
} else {
byte[] encodedEntry = entry.toByteArray(shrink);
payload.write(encodedEntry);
offsets.putInt(entryOffset);
entryOffset += encodedEntry.length;
}
}
} else {
BinaryResourceValue value = value();
Preconditions.checkNotNull(value, "A non-complex TypeChunk entry must have a value.");
buffer.put(value.toByteArray());
}
return buffer.array();
entryOffset = writePad(payload, entryOffset);
return entryOffset;
}
@Override
public final String toString() {
return String.format("Entry{key=%s}", key());
protected void writeHeader(ByteBuffer output) {
int entriesStart = getHeaderSize() + getOffsetSize();
output.putInt(id); // Write an unsigned byte with 3 bytes padding
output.putInt(entryCount);
output.putInt(entriesStart);
output.put(configuration.toByteArray(false));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry)o;
return headerSize == entry.headerSize &&
flags == entry.flags &&
keyIndex == entry.keyIndex &&
parentEntry == entry.parentEntry &&
Objects.equals(value, entry.value) &&
Objects.equals(values, entry.values) &&
Objects.equals(parent, entry.parent);
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()).order(ByteOrder.LITTLE_ENDIAN);
try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) {
writeEntries(payload, offsets, shrink);
}
output.write(offsets.array());
output.write(baos.toByteArray());
}
@Override
public int hashCode() {
return Objects.hash(headerSize, flags, keyIndex, value, values, parentEntry, parent);
/**
* An {@link Entry} in a {@link TypeChunk}. Contains one or more {@link BinaryResourceValue}.
*/
public static class Entry implements SerializableResource {
/**
* An entry offset that indicates that a given resource is not present.
*/
public static final int NO_ENTRY = 0xFFFFFFFF;
/**
* Set if this is a complex resource. Otherwise, it's a simple resource.
*/
private static final int FLAG_COMPLEX = 0x0001;
/**
* Size of a single resource id + value mapping entry.
*/
private static final int MAPPING_SIZE = 4 + BinaryResourceValue.SIZE;
private final int headerSize;
private final int flags;
private final int keyIndex;
private final int parentEntry;
private final TypeChunk parent;
private BinaryResourceValue value;
private final Map<Integer, BinaryResourceValue> values;
private Entry(int headerSize,
int flags,
int keyIndex,
BinaryResourceValue value,
Map<Integer, BinaryResourceValue> values,
int parentEntry,
TypeChunk parent) {
this.headerSize = headerSize;
this.flags = flags;
this.keyIndex = keyIndex;
this.value = value;
this.values = values;
this.parentEntry = parentEntry;
this.parent = parent;
}
/**
* Creates a new {@link Entry} whose contents start at the 0-based position in
* {@code buffer} given by a 4-byte value read from {@code buffer} and then added to
* {@code baseOffset}. If the value read from {@code buffer} is equal to {@link #NO_ENTRY}, then
* null is returned as there is no resource at that position.
*
* <p>Otherwise, this position is parsed and returned as an {@link Entry}.
*
* @param buffer A buffer positioned at an offset to an {@link Entry}.
* @param baseOffset Offset that must be added to the value at {@code buffer}'s position.
* @param parent The {@link TypeChunk} that this resource entry belongs to.
* @return New {@link Entry} or null if there is no resource at this location.
*/
@Nullable
public static Entry create(ByteBuffer buffer, int baseOffset, TypeChunk parent) {
int offset = buffer.getInt();
if (offset == NO_ENTRY) {
return null;
}
int position = buffer.position();
buffer.position(baseOffset + offset); // Set buffer position to resource entry start
Entry result = newInstance(buffer, parent);
buffer.position(position); // Restore buffer position
return result;
}
@Nullable
private static Entry newInstance(ByteBuffer buffer, TypeChunk parent) {
int headerSize = buffer.getShort() & 0xFFFF;
int flags = buffer.getShort() & 0xFFFF;
int keyIndex = buffer.getInt();
BinaryResourceValue value = null;
Map<Integer, BinaryResourceValue> values = new LinkedHashMap<>();
int parentEntry = 0;
if ((flags & FLAG_COMPLEX) != 0) {
parentEntry = buffer.getInt();
int valueCount = buffer.getInt();
for (int i = 0; i < valueCount; ++i) {
values.put(buffer.getInt(), BinaryResourceValue.create(buffer));
}
} else {
value = BinaryResourceValue.create(buffer);
}
return new Entry(headerSize, flags, keyIndex, value, values, parentEntry, parent);
}
/**
* Number of bytes in the header of the {@link Entry}.
*/
public int headerSize() {
return headerSize;
}
/**
* Resource entry flags.
*/
public int flags() {
return flags;
}
/**
* Index into {@link PackageChunk#getKeyStringPool} identifying this entry.
*/
public int keyIndex() {
return keyIndex;
}
/**
* The value of this resource entry, if this is not a complex entry. Else, null.
*/
@Nullable
public BinaryResourceValue value() {
return value;
}
/**
* Update the value of this resource entry
*
* @param value
*/
public void updateValue(@Nullable BinaryResourceValue value) {
this.value = value;
}
/**
* The extra values in this resource entry if this {@link #isComplex}.
*/
public Map<Integer, BinaryResourceValue> values() {
return values;
}
/**
* Update the value in this resource entry if this {@link #isComplex}.
*/
public void updateValue(int index, BinaryResourceValue value) {
if (index >= 0) {
if (value != null) {
values.put(index, value);
} else {
values.remove(index);
}
}
}
/**
* Entry into {@link PackageChunk} that is the parent {@link Entry} to this entry.
* This value only makes sense when this is complex ({@link #isComplex} returns true).
*/
public int parentEntry() {
return parentEntry;
}
/**
* The {@link TypeChunk} that this resource entry belongs to.
*/
public TypeChunk parent() {
return parent;
}
/**
* Returns the name of the type this chunk represents (e.g. string, attr, id).
*/
public final String typeName() {
return parent().getTypeName();
}
/**
* The total number of bytes that this {@link Entry} takes up.
*/
public final int size() {
return headerSize() + (isComplex() ? values().size() * MAPPING_SIZE : BinaryResourceValue.SIZE);
}
/**
* Returns the key name identifying this resource entry.
*/
public final String key() {
return parent().getKeyName(keyIndex());
}
/**
* Update the key name in this resource entry.
*/
public final void updateKey(int keyIndex, String value) {
parent().updateKey(keyIndex, value);
}
/**
* Returns true if this is a complex resource.
*/
public final boolean isComplex() {
return (flags() & FLAG_COMPLEX) != 0;
}
@Override
public final byte[] toByteArray() {
return toByteArray(false);
}
@Override
public final byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort((short) headerSize());
buffer.putShort((short) flags());
buffer.putInt(keyIndex());
if (isComplex()) {
buffer.putInt(parentEntry());
buffer.putInt(values().size());
for (Map.Entry<Integer, BinaryResourceValue> entry : values().entrySet()) {
buffer.putInt(entry.getKey());
buffer.put(entry.getValue().toByteArray(shrink));
}
} else {
BinaryResourceValue value = value();
Preconditions.checkNotNull(value, "A non-complex TypeChunk entry must have a value.");
buffer.put(value.toByteArray());
}
return buffer.array();
}
@Override
public final String toString() {
return String.format("Entry{key=%s}", key());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return headerSize == entry.headerSize &&
flags == entry.flags &&
keyIndex == entry.keyIndex &&
parentEntry == entry.parentEntry &&
Objects.equals(value, entry.value) &&
Objects.equals(values, entry.values) &&
Objects.equals(parent, entry.parent);
}
@Override
public int hashCode() {
return Objects.hash(headerSize, flags, keyIndex, value, values, parentEntry, parent);
}
}
}
}
@@ -16,86 +16,98 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedBytes;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
/** A chunk that contains a collection of resource entries for a particular resource data type. */
/**
* A chunk that contains a collection of resource entries for a particular resource data type.
*/
public final class TypeSpecChunk extends Chunk {
/** The id of the resource type that this type spec refers to. */
private final int id;
/**
* The id of the resource type that this type spec refers to.
*/
private final int id;
/** Resource configuration masks. */
private final int[] resources;
/**
* Resource configuration masks.
*/
private final int[] resources;
protected TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = UnsignedBytes.toInt(buffer.get());
buffer.position(buffer.position() + 3); // Skip 3 bytes for packing
int resourceCount = buffer.getInt();
resources = new int[resourceCount];
TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
id = UnsignedBytes.toInt(buffer.get());
buffer.position(buffer.position() + 3); // Skip 3 bytes for packing
int resourceCount = buffer.getInt();
resources = new int[resourceCount];
for (int i = 0; i < resourceCount; ++i) {
resources[i] = buffer.getInt();
for (int i = 0; i < resourceCount; ++i) {
resources[i] = buffer.getInt();
}
}
}
/**
* Returns the (1-based) type id of the resources that this {@link TypeSpecChunk} has
* configuration masks for.
*/
public int getId() {
return id;
}
/** Returns the number of resource entries that this chunk has configuration masks for. */
public int getResourceCount() {
return resources.length;
}
@Override
protected Type getType() {
return Type.TABLE_TYPE_SPEC;
}
/** Returns the name of the type this chunk represents (e.g. string, attr, id). */
public String getTypeName() {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk typePool = packageChunk.getTypeStringPool();
Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass());
return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index
}
/** Returns the package enclosing this chunk, if any. Else, returns null. */
@Nullable
private PackageChunk getPackageChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof PackageChunk)) {
chunk = chunk.getParent();
/**
* Returns the (1-based) type id of the resources that this {@link TypeSpecChunk} has
* configuration masks for.
*/
public int getId() {
return id;
}
return chunk != null ? (PackageChunk) chunk : null;
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
// id is an unsigned byte in the range [0-255]. It is guaranteed to be non-negative.
// Because our output is in little-endian, we are making use of the 4 byte packing here
output.putInt(id);
output.putInt(resources.length);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (int resource : resources) {
output.writeInt(resource);
/**
* Returns the number of resource entries that this chunk has configuration masks for.
*/
public int getResourceCount() {
return resources.length;
}
@Override
protected Type getType() {
return Type.TABLE_TYPE_SPEC;
}
/**
* Returns the name of the type this chunk represents (e.g. string, attr, id).
*/
public String getTypeName() {
PackageChunk packageChunk = getPackageChunk();
Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
StringPoolChunk typePool = packageChunk.getTypeStringPool();
Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass());
return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index
}
/**
* Returns the package enclosing this chunk, if any. Else, returns null.
*/
@Nullable
private PackageChunk getPackageChunk() {
Chunk chunk = getParent();
while (chunk != null && !(chunk instanceof PackageChunk)) {
chunk = chunk.getParent();
}
return chunk != null ? (PackageChunk) chunk : null;
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
// id is an unsigned byte in the range [0-255]. It is guaranteed to be non-negative.
// Because our output is in little-endian, we are making use of the 4 byte packing here
output.putInt(id);
output.putInt(resources.length);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
for (int resource : resources) {
output.writeInt(resource);
}
}
}
}
@@ -17,6 +17,7 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -27,35 +28,35 @@ import java.nio.ByteBuffer;
*/
public final class UnknownChunk extends Chunk {
private final Type type;
private final Type type;
private final byte[] header;
private final byte[] header;
private final byte[] payload;
private final byte[] payload;
protected UnknownChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
UnknownChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
type = Type.fromCode(buffer.getShort(offset));
header = new byte[headerSize - Chunk.METADATA_SIZE];
payload = new byte[chunkSize - headerSize];
buffer.get(header);
buffer.get(payload);
}
type = Type.fromCode(buffer.getShort(offset));
header = new byte[headerSize - Chunk.METADATA_SIZE];
payload = new byte[chunkSize - headerSize];
buffer.get(header);
buffer.get(payload);
}
@Override
protected void writeHeader(ByteBuffer output) {
output.put(header);
}
@Override
protected void writeHeader(ByteBuffer output) {
output.put(header);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
output.write(payload);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
output.write(payload);
}
@Override
protected Type getType() {
return type;
}
@Override
protected Type getType() {
return type;
}
}
@@ -20,129 +20,149 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
/** Represents an XML attribute and value. */
/**
* Represents an XML attribute and value.
*/
public class XmlAttribute implements SerializableResource {
/** The serialized size in bytes of an {@link XmlAttribute}. */
public static final int SIZE = 12 + BinaryResourceValue.SIZE;
/**
* The serialized size in bytes of an {@link XmlAttribute}.
*/
public static final int SIZE = 12 + BinaryResourceValue.SIZE;
private final int namespaceIndex;
private final int nameIndex;
private final int rawValueIndex;
private final BinaryResourceValue typedValue;
private final XmlNodeChunk parent;
private final int namespaceIndex;
private final int nameIndex;
private final int rawValueIndex;
private final BinaryResourceValue typedValue;
private final XmlNodeChunk parent;
/**
* Creates a new {@link XmlAttribute} based on the bytes at the current {@code buffer} position.
*
* @param buffer A buffer whose position is at the start of a {@link XmlAttribute}.
* @param parent The parent chunk that contains this attribute; used for string lookups.
*/
public static XmlAttribute create(ByteBuffer buffer, XmlNodeChunk parent) {
int namespace = buffer.getInt();
int name = buffer.getInt();
int rawValue = buffer.getInt();
BinaryResourceValue typedValue = BinaryResourceValue.create(buffer);
return new XmlAttribute(namespace, name, rawValue, typedValue, parent);
}
private XmlAttribute(int namespaceIndex,
int nameIndex,
int rawValueIndex,
BinaryResourceValue typedValue,
XmlNodeChunk parent) {
this.namespaceIndex = namespaceIndex;
this.nameIndex = nameIndex;
this.rawValueIndex = rawValueIndex;
this.typedValue = typedValue;
this.parent = parent;
}
private XmlAttribute(int namespaceIndex,
int nameIndex,
int rawValueIndex,
BinaryResourceValue typedValue,
XmlNodeChunk parent) {
this.namespaceIndex = namespaceIndex;
this.nameIndex = nameIndex;
this.rawValueIndex = rawValueIndex;
this.typedValue = typedValue;
this.parent = parent;
}
/**
* Creates a new {@link XmlAttribute} based on the bytes at the current {@code buffer} position.
*
* @param buffer A buffer whose position is at the start of a {@link XmlAttribute}.
* @param parent The parent chunk that contains this attribute; used for string lookups.
*/
public static XmlAttribute create(ByteBuffer buffer, XmlNodeChunk parent) {
int namespace = buffer.getInt();
int name = buffer.getInt();
int rawValue = buffer.getInt();
BinaryResourceValue typedValue = BinaryResourceValue.create(buffer);
return new XmlAttribute(namespace, name, rawValue, typedValue, parent);
}
/** A string reference to the namespace URI, or -1 if not present. */
public int namespaceIndex() {
return namespaceIndex;
}
/**
* A string reference to the namespace URI, or -1 if not present.
*/
public int namespaceIndex() {
return namespaceIndex;
}
/** A string reference to the attribute name. */
public int nameIndex() {
return nameIndex;
}
/**
* A string reference to the attribute name.
*/
public int nameIndex() {
return nameIndex;
}
/** A string reference to a string containing the character value. */
public int rawValueIndex() {
return rawValueIndex;
}
/**
* A string reference to a string containing the character value.
*/
public int rawValueIndex() {
return rawValueIndex;
}
/** A {@link BinaryResourceValue} instance containing the parsed value. */
public BinaryResourceValue typedValue() {
return typedValue;
}
/**
* A {@link BinaryResourceValue} instance containing the parsed value.
*/
public BinaryResourceValue typedValue() {
return typedValue;
}
/** The parent of this XML attribute; used for dereferencing the namespace and name. */
public XmlNodeChunk parent() {
return parent;
}
/**
* The parent of this XML attribute; used for dereferencing the namespace and name.
*/
public XmlNodeChunk parent() {
return parent;
}
/** The namespace URI, or the empty string if not present. */
public final String namespace() {
return getString(namespaceIndex());
}
/**
* The namespace URI, or the empty string if not present.
*/
public final String namespace() {
return getString(namespaceIndex());
}
/** The attribute name, or the empty string if not present. */
public final String name() {
return getString(nameIndex());
}
/**
* The attribute name, or the empty string if not present.
*/
public final String name() {
return getString(nameIndex());
}
/** The raw character value. */
public final String rawValue() {
return getString(rawValueIndex());
}
/**
* The raw character value.
*/
public final String rawValue() {
return getString(rawValueIndex());
}
private String getString(int index) {
return parent().getString(index);
}
private String getString(int index) {
return parent().getString(index);
}
@Override
public byte[] toByteArray() {
return toByteArray(false);
}
@Override
public byte[] toByteArray() {
return toByteArray(false);
}
@Override
public byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(namespaceIndex());
buffer.putInt(nameIndex());
buffer.putInt(rawValueIndex());
buffer.put(typedValue().toByteArray(shrink));
return buffer.array();
}
@Override
public byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(namespaceIndex());
buffer.putInt(nameIndex());
buffer.putInt(rawValueIndex());
buffer.put(typedValue().toByteArray(shrink));
return buffer.array();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
XmlAttribute that = (XmlAttribute)o;
return namespaceIndex == that.namespaceIndex &&
nameIndex == that.nameIndex &&
rawValueIndex == that.rawValueIndex &&
Objects.equals(typedValue, that.typedValue) &&
Objects.equals(parent, that.parent);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
XmlAttribute that = (XmlAttribute) o;
return namespaceIndex == that.namespaceIndex &&
nameIndex == that.nameIndex &&
rawValueIndex == that.rawValueIndex &&
Objects.equals(typedValue, that.typedValue) &&
Objects.equals(parent, that.parent);
}
@Override
public int hashCode() {
return Objects.hash(namespaceIndex, nameIndex, rawValueIndex, typedValue, parent);
}
@Override
public int hashCode() {
return Objects.hash(namespaceIndex, nameIndex, rawValueIndex, typedValue, parent);
}
/**
* Returns a brief description of this XML attribute. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlAttribute{namespace=foo, name=bar, value=1234}"</pre>
*/
@Override
public String toString() {
return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}",
namespace(), name(), rawValue());
}
/**
* Returns a brief description of this XML attribute. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlAttribute{namespace=foo, name=bar, value=1234}"</pre>
*/
@Override
public String toString() {
return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}",
namespace(), name(), rawValue());
}
}
@@ -17,57 +17,68 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
/** Represents an XML cdata node. */
/**
* Represents an XML cdata node.
*/
public final class XmlCdataChunk extends XmlNodeChunk {
/** A string reference to a string containing the raw character data. */
private final int rawValue;
/**
* A string reference to a string containing the raw character data.
*/
private final int rawValue;
/** A {@link BinaryResourceValue} instance containing the parsed value. */
private final BinaryResourceValue binaryResourceValue;
/**
* A {@link BinaryResourceValue} instance containing the parsed value.
*/
private final BinaryResourceValue binaryResourceValue;
protected XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
rawValue = buffer.getInt();
binaryResourceValue = BinaryResourceValue.create(buffer);
}
XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
rawValue = buffer.getInt();
binaryResourceValue = BinaryResourceValue.create(buffer);
}
/** Returns a string containing the raw character data of this chunk. */
public String getRawValue() {
return getString(rawValue);
}
/**
* Returns a string containing the raw character data of this chunk.
*/
public String getRawValue() {
return getString(rawValue);
}
/** Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. */
public BinaryResourceValue getResourceValue() {
return binaryResourceValue;
}
/**
* Returns a {@link BinaryResourceValue} instance containing the parsed cdata value.
*/
public BinaryResourceValue getResourceValue() {
return binaryResourceValue;
}
@Override
protected Type getType() {
return Type.XML_CDATA;
}
@Override
protected Type getType() {
return Type.XML_CDATA;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(rawValue);
output.write(binaryResourceValue.toByteArray());
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(rawValue);
output.write(binaryResourceValue.toByteArray());
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"</pre>
*/
@Override
public String toString() {
return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}",
getLineNumber(), getComment(), getRawValue());
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"</pre>
*/
@Override
public String toString() {
return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}",
getLineNumber(), getComment(), getRawValue());
}
}
@@ -17,6 +17,7 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/**
@@ -27,22 +28,24 @@ import java.nio.ByteBuffer;
*/
public final class XmlChunk extends ChunkWithChunks {
protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected Type getType() {
return Type.XML;
}
@Override
protected Type getType() {
return Type.XML;
}
/** Returns a string at the provided (0-based) index if the index exists in the string pool. */
public String getString(int index) {
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof StringPoolChunk) {
return ((StringPoolChunk) chunk).getString(index);
}
/**
* Returns a string at the provided (0-based) index if the index exists in the string pool.
*/
public String getString(int index) {
for (Chunk chunk : getChunks().values()) {
if (chunk instanceof StringPoolChunk) {
return ((StringPoolChunk) chunk).getString(index);
}
}
throw new IllegalStateException("XmlChunk did not contain a string pool.");
}
throw new IllegalStateException("XmlChunk did not contain a string pool.");
}
}
@@ -17,59 +17,70 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
/** Represents the end of an XML node. */
/**
* Represents the end of an XML node.
*/
public final class XmlEndElementChunk extends XmlNodeChunk {
/** A string reference to the namespace URI, or -1 if not present. */
private final int namespace;
/**
* A string reference to the namespace URI, or -1 if not present.
*/
private final int namespace;
/** A string reference to the attribute name. */
private final int name;
/**
* A string reference to the attribute name.
*/
private final int name;
protected XmlEndElementChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
namespace = buffer.getInt();
name = buffer.getInt();
}
XmlEndElementChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
namespace = buffer.getInt();
name = buffer.getInt();
}
/** Returns the namespace URI, or the empty string if no namespace is present. */
public String getNamespace() {
return getString(namespace);
}
/**
* Returns the namespace URI, or the empty string if no namespace is present.
*/
public String getNamespace() {
return getString(namespace);
}
/** Returns the attribute name. */
public String getName() {
return getString(name);
}
/**
* Returns the attribute name.
*/
public String getName() {
return getString(name);
}
@Override
protected Type getType() {
return Type.XML_END_ELEMENT;
}
@Override
protected Type getType() {
return Type.XML_END_ELEMENT;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(namespace);
output.writeInt(name);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(namespace);
output.writeInt(name);
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
* </pre>
*/
@Override
public String toString() {
return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}",
getLineNumber(), getComment(), getNamespace(), getName());
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
* </pre>
*/
@Override
public String toString() {
return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}",
getLineNumber(), getComment(), getNamespace(), getName());
}
}
@@ -17,54 +17,65 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
/** Represents the start/end of a namespace in an XML document. */
/**
* Represents the start/end of a namespace in an XML document.
*/
public abstract class XmlNamespaceChunk extends XmlNodeChunk {
/** A string reference to the namespace prefix. */
private final int prefix;
/**
* A string reference to the namespace prefix.
*/
private final int prefix;
/** A string reference to the namespace URI. */
private final int uri;
/**
* A string reference to the namespace URI.
*/
private final int uri;
protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
prefix = buffer.getInt();
uri = buffer.getInt();
}
protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
prefix = buffer.getInt();
uri = buffer.getInt();
}
/** Returns the namespace prefix. */
public String getPrefix() {
return getString(prefix);
}
/**
* Returns the namespace prefix.
*/
public String getPrefix() {
return getString(prefix);
}
/** Returns the namespace URI. */
public String getUri() {
return getString(uri);
}
/**
* Returns the namespace URI.
*/
public String getUri() {
return getString(uri);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(prefix);
output.writeInt(uri);
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(prefix);
output.writeInt(uri);
}
/**
* Returns a brief description of this namespace chunk. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
* </pre>
*/
@Override
public String toString() {
return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}",
getLineNumber(), getComment(), getPrefix(), getUri());
}
/**
* Returns a brief description of this namespace chunk. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
* </pre>
*/
@Override
public String toString() {
return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}",
getLineNumber(), getComment(), getPrefix(), getUri());
}
}
@@ -17,17 +17,20 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/** Represents the ending tag of a namespace in an XML document. */
/**
* Represents the ending tag of a namespace in an XML document.
*/
public final class XmlNamespaceEndChunk extends XmlNamespaceChunk {
protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected Type getType() {
return Type.XML_END_NAMESPACE;
}
@Override
protected Type getType() {
return Type.XML_END_NAMESPACE;
}
}
@@ -17,17 +17,20 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/** Represents the starting tag of a namespace in an XML document. */
/**
* Represents the starting tag of a namespace in an XML document.
*/
public final class XmlNamespaceStartChunk extends XmlNamespaceChunk {
protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected Type getType() {
return Type.XML_START_NAMESPACE;
}
@Override
protected Type getType() {
return Type.XML_START_NAMESPACE;
}
}
@@ -17,80 +17,93 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/** The common superclass for the various types of XML nodes. */
/**
* The common superclass for the various types of XML nodes.
*/
public abstract class XmlNodeChunk extends Chunk {
/** The line number in the original source at which this node appeared. */
private final int lineNumber;
/**
* The line number in the original source at which this node appeared.
*/
private final int lineNumber;
/** A string reference of this node's comment. If this is -1, then there is no comment. */
private final int comment;
/**
* A string reference of this node's comment. If this is -1, then there is no comment.
*/
private final int comment;
protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
lineNumber = buffer.getInt();
comment = buffer.getInt();
}
/** Returns true if this XML node contains a comment. Else, returns false. */
public boolean hasComment() {
return comment != -1;
}
/** Returns the line number in the original source at which this node appeared. */
public int getLineNumber() {
return lineNumber;
}
/** Returns the comment associated with this node, if any. Else, returns the empty string. */
public String getComment() {
return getString(comment);
}
/**
* An {@link XmlNodeChunk} does not know by itself what strings its indices reference. In order
* to get the actual string, the first {@link XmlChunk} ancestor is found. The
* {@link XmlChunk} ancestor should have a string pool which {@code index} references.
*
* @param index The index of the string.
* @return String that the given {@code index} references, or empty string if {@code index} is -1.
*/
protected String getString(int index) {
if (index == -1) { // Special case. Packed XML files use -1 for "no string entry"
return "";
protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
lineNumber = buffer.getInt();
comment = buffer.getInt();
}
Chunk parent = getParent();
while (parent != null) {
if (parent instanceof XmlChunk) {
return ((XmlChunk) parent).getString(index);
}
parent = parent.getParent();
/**
* Returns true if this XML node contains a comment. Else, returns false.
*/
public boolean hasComment() {
return comment != -1;
}
throw new IllegalStateException("XmlNodeChunk did not have an XmlChunk parent.");
}
/**
* An {@link XmlNodeChunk} and anything that is itself an {@link XmlNodeChunk} has a header size
* of 16. Anything else is, interestingly, considered to be a payload. For that reason, this
* method is final.
*/
@Override
protected final void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(lineNumber);
output.putInt(comment);
}
/**
* Returns the line number in the original source at which this node appeared.
*/
public int getLineNumber() {
return lineNumber;
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlNodeChunk{line=1234, comment=My awesome comment.}"</pre>
*/
@Override
public String toString() {
return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment());
}
/**
* Returns the comment associated with this node, if any. Else, returns the empty string.
*/
public String getComment() {
return getString(comment);
}
/**
* An {@link XmlNodeChunk} does not know by itself what strings its indices reference. In order
* to get the actual string, the first {@link XmlChunk} ancestor is found. The
* {@link XmlChunk} ancestor should have a string pool which {@code index} references.
*
* @param index The index of the string.
* @return String that the given {@code index} references, or empty string if {@code index} is -1.
*/
protected String getString(int index) {
if (index == -1) { // Special case. Packed XML files use -1 for "no string entry"
return "";
}
Chunk parent = getParent();
while (parent != null) {
if (parent instanceof XmlChunk) {
return ((XmlChunk) parent).getString(index);
}
parent = parent.getParent();
}
throw new IllegalStateException("XmlNodeChunk did not have an XmlChunk parent.");
}
/**
* An {@link XmlNodeChunk} and anything that is itself an {@link XmlNodeChunk} has a header size
* of 16. Anything else is, interestingly, considered to be a payload. For that reason, this
* method is final.
*/
@Override
protected final void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(lineNumber);
output.putInt(comment);
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>"XmlNodeChunk{line=1234, comment=My awesome comment.}"</pre>
*/
@Override
public String toString() {
return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment());
}
}
@@ -17,6 +17,7 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -31,56 +32,60 @@ import java.util.List;
*/
public class XmlResourceMapChunk extends Chunk {
/** The size of a resource reference for {@code resources} in bytes. */
private static final int RESOURCE_SIZE = 4;
/**
* The size of a resource reference for {@code resources} in bytes.
*/
private static final int RESOURCE_SIZE = 4;
/**
* Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the
* resourceID returned by {@code resources.get(2)}.
*/
private final List<Integer> resources = new ArrayList<>();
/**
* Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the
* resourceID returned by {@code resources.get(2)}.
*/
private final List<Integer> resources = new ArrayList<>();
protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
resources.addAll(enumerateResources(buffer));
}
private List<Integer> enumerateResources(ByteBuffer buffer) {
int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE;
List<Integer> result = new ArrayList<>(resourceCount);
int offset = this.offset + getHeaderSize();
buffer.mark();
buffer.position(offset);
for (int i = 0; i < resourceCount; ++i) {
result.add(buffer.getInt());
protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
buffer.reset();
return result;
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
resources.addAll(enumerateResources(buffer));
}
/** Returns the resource ID that this {@code attributeId} maps to. */
public BinaryResourceIdentifier getResourceId(int attributeId) {
return BinaryResourceIdentifier.create(resources.get(attributeId));
}
private List<Integer> enumerateResources(ByteBuffer buffer) {
int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE;
List<Integer> result = new ArrayList<>(resourceCount);
int offset = this.offset + getHeaderSize();
buffer.mark();
buffer.position(offset);
@Override
protected Type getType() {
return Type.XML_RESOURCE_MAP;
}
for (int i = 0; i < resourceCount; ++i) {
result.add(buffer.getInt());
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
for (Integer resource : resources) {
output.writeInt(resource);
buffer.reset();
return result;
}
/**
* Returns the resource ID that this {@code attributeId} maps to.
*/
public BinaryResourceIdentifier getResourceId(int attributeId) {
return BinaryResourceIdentifier.create(resources.get(attributeId));
}
@Override
protected Type getType() {
return Type.XML_RESOURCE_MAP;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
for (Integer resource : resources) {
output.writeInt(resource);
}
}
}
}
@@ -16,9 +16,9 @@
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -26,120 +26,144 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Represents the beginning of an XML node. */
/**
* Represents the beginning of an XML node.
*/
public final class XmlStartElementChunk extends XmlNodeChunk {
/** A string reference to the namespace URI, or -1 if not present. */
private final int namespace;
/**
* A string reference to the namespace URI, or -1 if not present.
*/
private final int namespace;
/** A string reference to the element name that this chunk represents. */
private final int name;
/**
* A string reference to the element name that this chunk represents.
*/
private final int name;
/** The offset to the start of the attributes payload. */
private final int attributeStart;
/**
* The offset to the start of the attributes payload.
*/
private final int attributeStart;
/** The number of attributes in the original buffer. */
private final int attributeCount;
/**
* The number of attributes in the original buffer.
*/
private final int attributeCount;
/** The (0-based) index of the id attribute, or -1 if not present. */
private final int idIndex;
/**
* The (0-based) index of the id attribute, or -1 if not present.
*/
private final int idIndex;
/** The (0-based) index of the class attribute, or -1 if not present. */
private final int classIndex;
/**
* The (0-based) index of the class attribute, or -1 if not present.
*/
private final int classIndex;
/** The (0-based) index of the style attribute, or -1 if not present. */
private final int styleIndex;
/**
* The (0-based) index of the style attribute, or -1 if not present.
*/
private final int styleIndex;
/** The XML attributes associated with this element. */
private final List<XmlAttribute> attributes = new ArrayList<>();
/**
* The XML attributes associated with this element.
*/
private final List<XmlAttribute> attributes = new ArrayList<>();
protected XmlStartElementChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
namespace = buffer.getInt();
name = buffer.getInt();
attributeStart = (buffer.getShort() & 0xFFFF);
int attributeSize = (buffer.getShort() & 0xFFFF);
Preconditions.checkState(attributeSize == XmlAttribute.SIZE,
"attributeSize is wrong size. Got %s, want %s", attributeSize, XmlAttribute.SIZE);
attributeCount = (buffer.getShort() & 0xFFFF);
XmlStartElementChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
namespace = buffer.getInt();
name = buffer.getInt();
attributeStart = (buffer.getShort() & 0xFFFF);
int attributeSize = (buffer.getShort() & 0xFFFF);
Preconditions.checkState(attributeSize == XmlAttribute.SIZE,
"attributeSize is wrong size. Got %s, want %s", attributeSize, XmlAttribute.SIZE);
attributeCount = (buffer.getShort() & 0xFFFF);
// The following indices are 1-based and need to be adjusted.
idIndex = (buffer.getShort() & 0xFFFF) - 1;
classIndex = (buffer.getShort() & 0xFFFF) - 1;
styleIndex = (buffer.getShort() & 0xFFFF) - 1;
}
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
attributes.addAll(enumerateAttributes(buffer));
}
private List<XmlAttribute> enumerateAttributes(ByteBuffer buffer) {
List<XmlAttribute> result = new ArrayList<>(attributeCount);
int offset = this.offset + getHeaderSize() + attributeStart;
int endOffset = offset + XmlAttribute.SIZE * attributeCount;
buffer.mark();
buffer.position(offset);
while (offset < endOffset) {
result.add(XmlAttribute.create(buffer, this));
offset += XmlAttribute.SIZE;
// The following indices are 1-based and need to be adjusted.
idIndex = (buffer.getShort() & 0xFFFF) - 1;
classIndex = (buffer.getShort() & 0xFFFF) - 1;
styleIndex = (buffer.getShort() & 0xFFFF) - 1;
}
buffer.reset();
return result;
}
/** Returns the namespace URI, or the empty string if not present. */
public String getNamespace() {
return getString(namespace);
}
/** Returns the element name that this chunk represents. */
public String getName() {
return getString(name);
}
/** Returns an unmodifiable list of this XML element's attributes. */
public List<XmlAttribute> getAttributes() {
return Collections.unmodifiableList(attributes);
}
@Override
protected Type getType() {
return Type.XML_START_ELEMENT;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(namespace);
output.writeInt(name);
output.writeShort((short) XmlAttribute.SIZE); // attribute start
output.writeShort((short) XmlAttribute.SIZE);
output.writeShort((short) attributes.size());
output.writeShort((short) (idIndex + 1));
output.writeShort((short) (classIndex + 1));
output.writeShort((short) (styleIndex + 1));
for (XmlAttribute attribute : attributes) {
output.write(attribute.toByteArray(shrink));
@Override
protected void init(ByteBuffer buffer) {
super.init(buffer);
attributes.addAll(enumerateAttributes(buffer));
}
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
* </pre>
*/
@Override
public String toString() {
return String.format(
"XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}",
getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString());
}
private List<XmlAttribute> enumerateAttributes(ByteBuffer buffer) {
List<XmlAttribute> result = new ArrayList<>(attributeCount);
int offset = this.offset + getHeaderSize() + attributeStart;
int endOffset = offset + XmlAttribute.SIZE * attributeCount;
buffer.mark();
buffer.position(offset);
while (offset < endOffset) {
result.add(XmlAttribute.create(buffer, this));
offset += XmlAttribute.SIZE;
}
buffer.reset();
return result;
}
/**
* Returns the namespace URI, or the empty string if not present.
*/
public String getNamespace() {
return getString(namespace);
}
/**
* Returns the element name that this chunk represents.
*/
public String getName() {
return getString(name);
}
/**
* Returns an unmodifiable list of this XML element's attributes.
*/
public List<XmlAttribute> getAttributes() {
return Collections.unmodifiableList(attributes);
}
@Override
protected Type getType() {
return Type.XML_START_ELEMENT;
}
@Override
protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
throws IOException {
super.writePayload(output, header, shrink);
output.writeInt(namespace);
output.writeInt(name);
output.writeShort((short) XmlAttribute.SIZE); // attribute start
output.writeShort((short) XmlAttribute.SIZE);
output.writeShort((short) attributes.size());
output.writeShort((short) (idIndex + 1));
output.writeShort((short) (classIndex + 1));
output.writeShort((short) (styleIndex + 1));
for (XmlAttribute attribute : attributes) {
output.write(attribute.toByteArray(shrink));
}
}
/**
* Returns a brief description of this XML node. The representation of this information is
* subject to change, but below is a typical example:
*
* <pre>
* "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
* </pre>
*/
@Override
public String toString() {
return String.format(
"XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}",
getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString());
}
}
@@ -1,20 +1,16 @@
package com.vk.api.audio;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.vk.api.base.ApiRequest;
import com.vk.dto.music.MusicTrack;
import com.vk.log.L;
import org.json.JSONObject;
import java.util.ArrayList;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.Genius;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
import java.util.ArrayList;
public class AudioGetLyrics extends ApiRequest<AudioGetLyrics.a> {
private final MusicTrack musicTrack;
@@ -29,7 +25,7 @@ public class AudioGetLyrics extends ApiRequest<AudioGetLyrics.a> {
public a a(@NonNull JSONObject jSONObject) {
var aVar = new a();
if (Preferences.getBoolValue("useGenius", false)) {
if (Preferences.getBoolValue("useGenius", false) && !Preferences.serverFeaturesDisable()) {
aVar.a = Genius.getTextMusic(musicTrack);
return aVar;
}
@@ -1,24 +1,12 @@
package com.vk.api.gifts;
import android.content.Context;
import com.vk.api.base.ApiRequest;
import com.vk.dto.gift.CatalogedGift;
import org.json.JSONException;
import org.json.JSONObject;
public class GiftGetByStickerId extends ApiRequest<GiftGetByStickerId.a> {
public static class a {
public final int a;
public final CatalogedGift b;
public a(int i, CatalogedGift catalogedGift) {
this.a = i;
this.b = catalogedGift;
}
}
public GiftGetByStickerId(Context context, int i) {
super("execute.getGiftByStickerId");
b("sticker_id", i);
@@ -29,7 +17,17 @@ public class GiftGetByStickerId extends ApiRequest<GiftGetByStickerId.a> {
@Override // com.vk.api.sdk.o.VKRequest
public GiftGetByStickerId.a a(JSONObject jSONObject) throws JSONException {
JSONObject jSONObject2 = jSONObject.getJSONObject("response");
return new a(jSONObject2.optInt("balance"),
return new a(jSONObject2.optInt("balance"),
new CatalogedGift(jSONObject2.getJSONObject("gift")));
}
public static class a {
public final int a;
public final CatalogedGift b;
public a(int i, CatalogedGift catalogedGift) {
this.a = i;
this.b = catalogedGift;
}
}
}
@@ -27,6 +27,15 @@ public class RecoloredDrawable extends Drawable implements Drawable.Callback {
onStateChange(new int[0]);
}
public RecoloredDrawable(Drawable drawable, int i) {
this(drawable, new ColorStateList(new int[][]{new int[0]}, new int[]{i}));
}
public RecoloredDrawable() {
this.c = 255;
this.d = 255;
}
public void a(int i) {
a(new ColorStateList(new int[][]{new int[0]}, new int[]{i}));
}
@@ -120,13 +129,4 @@ public class RecoloredDrawable extends Drawable implements Drawable.Callback {
public Drawable a() {
return this.drawable;
}
public RecoloredDrawable(Drawable drawable, int i) {
this(drawable, new ColorStateList(new int[][]{new int[0]}, new int[]{i}));
}
public RecoloredDrawable() {
this.c = 255;
this.d = 255;
}
}
@@ -14,7 +14,7 @@ import b.h.v.InitialValueObservable;
import b.h.v.TextViewTextChangeEvent;
import b.h.v.TextViewTextChangeEventObservable;
import com.vk.core.util.ContextExtKt;
import ru.vtosters.lite.utils.ThemesUtils;
import ru.vtosters.hooks.other.ThemesUtils;
public final class TextViewExt {
public static void a(TextView textView, Drawable drawable) {
@@ -11,13 +11,15 @@ import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import b.h.g.m.CameraUtils;
import b.h.g.m.FileUtils;
import com.facebook.common.i.MediaUtils;
import com.facebook.x.g.EncodedImage;
import com.vk.imageloader.VKImageLoader;
import com.vk.log.L;
import com.vk.metrics.eventtracking.VkTracker;
import com.vtosters.lite.R;
import ru.vtosters.lite.concurrent.VTExecutors;
import java.io.File;
import java.io.FileOutputStream;
@@ -26,10 +28,6 @@ import java.io.InputStream;
import java.net.URI;
import java.util.HashSet;
import b.h.g.m.CameraUtils;
import b.h.g.m.FileUtils;
import ru.vtosters.lite.concurrent.VTExecutors;
public class DownloadUtils {
public static final HashSet<Long> a = new HashSet<>();
@@ -49,7 +47,7 @@ public class DownloadUtils {
ToastUtils.a(context.getString(R.string.error) + " [" + e2.getMessage() + "]");
}
}
private static void b(final Context context, String str, String uri, boolean z) {
if (TextUtils.isEmpty(uri)) {
return;
@@ -14,13 +14,20 @@ import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.dto.common.VerifyInfo;
import com.vk.extensions.ViewExtKt;
import com.vtosters.lite.R;
import ru.vtosters.lite.utils.ThemesUtils;
import ru.vtosters.hooks.other.ThemesUtils;
public final class VerifyInfoHelper {
public static final VerifyInfoHelper h;
public static int devIcon = R.drawable.ic_favorite_16;
static {
h = new VerifyInfoHelper();
}
private VerifyInfoHelper() {
}
public static Integer enumSwitchMapping(VerifyInfoHelper.ColorTheme color) {
return switch (color) {
case normal -> 1;
@@ -30,18 +37,47 @@ public final class VerifyInfoHelper {
};
}
public enum ColorTheme {
white,
normal,
light,
ultraLight
public static Drawable b(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.b(z, z2, dev, context, colorTheme);
}
static {
h = new VerifyInfoHelper();
public static Drawable b(VerifyInfoHelper verifyInfoHelper, VerifyInfo verifyInfo, Context context, ColorTheme colorTheme, int i, Object obj) {
return b(verifyInfoHelper, verifyInfo.u1(), verifyInfo.t1(), verifyInfo.dev(), context, colorTheme, i, obj);
}
private VerifyInfoHelper() {
public static Drawable b(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, Context context, ColorTheme colorTheme, int i, Object obj) {
return b(verifyInfoHelper, z, z2, false, context, colorTheme, i, obj);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, VerifyInfo verifyInfo, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 4) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.b(verifyInfo, context, colorTheme);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.a(z, z2, dev, context, colorTheme);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, Context context, ColorTheme colorTheme, int i, Object obj) {
return a(verifyInfoHelper, z, z2, false, context, colorTheme, i, obj);
}
public static void a(VerifyInfoHelper verifyInfoHelper, TextView textView, VerifyInfo verifyInfo, boolean z, ColorTheme colorTheme, int i, Object obj) {
if ((i & 4) != 0) {
z = false;
}
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
verifyInfoHelper.a(textView, verifyInfo, z, colorTheme);
}
private int a() {
@@ -89,7 +125,6 @@ public final class VerifyInfoHelper {
return b(this, z, z2, false, context, null, 8, null);
}
public int b(ColorTheme colorTheme) {
return a(colorTheme);
}
@@ -104,7 +139,6 @@ public final class VerifyInfoHelper {
};
}
public Drawable b(Context context, VerifyInfo verifyInfo) {
if (verifyInfo.dev()) {
return new RecoloredDrawable(ContextCompat.getDrawable(context, devIcon), c(ColorTheme.normal));
@@ -123,21 +157,6 @@ public final class VerifyInfoHelper {
return b(verifyInfo.u1(), verifyInfo.t1(), verifyInfo.dev(), context, colorTheme);
}
public static Drawable b(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.b(z, z2, dev, context, colorTheme);
}
public static Drawable b(VerifyInfoHelper verifyInfoHelper, VerifyInfo verifyInfo, Context context, ColorTheme colorTheme, int i, Object obj) {
return b(verifyInfoHelper, verifyInfo.u1(), verifyInfo.t1(), verifyInfo.dev(), context, colorTheme, i, obj);
}
public static Drawable b(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, Context context, ColorTheme colorTheme, int i, Object obj) {
return b(verifyInfoHelper, z, z2, false, context, colorTheme, i, obj);
}
public Drawable a(Context context, VerifyInfo verifyInfo) {
if (verifyInfo.dev()) {
return new RecoloredDrawable(ContextCompat.getDrawable(context, devIcon), c(ColorTheme.normal));
@@ -181,28 +200,10 @@ public final class VerifyInfoHelper {
return new RecoloredDrawable(ContextCompat.getDrawable(context, i), c2);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, VerifyInfo verifyInfo, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 4) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.b(verifyInfo, context, colorTheme);
}
public Drawable a(VerifyInfo verifyInfo, Context context, ColorTheme colorTheme) {
return a(verifyInfo.u1(), verifyInfo.t1(), verifyInfo.dev(), context, colorTheme);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme, int i, Object obj) {
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
return verifyInfoHelper.a(z, z2, dev, context, colorTheme);
}
public static Drawable a(VerifyInfoHelper verifyInfoHelper, boolean z, boolean z2, Context context, ColorTheme colorTheme, int i, Object obj) {
return a(verifyInfoHelper, z, z2, false, context, colorTheme, i, obj);
}
public Drawable a(boolean z, boolean z2, Context context, ColorTheme colorTheme) {
return a(z, z2, false, context, colorTheme);
}
@@ -230,16 +231,6 @@ public final class VerifyInfoHelper {
return new RecoloredDrawable(ContextCompat.getDrawable(context, i), c2);
}
public static void a(VerifyInfoHelper verifyInfoHelper, TextView textView, VerifyInfo verifyInfo, boolean z, ColorTheme colorTheme, int i, Object obj) {
if ((i & 4) != 0) {
z = false;
}
if ((i & 8) != 0) {
colorTheme = ColorTheme.normal;
}
verifyInfoHelper.a(textView, verifyInfo, z, colorTheme);
}
public void a(TextView textView, VerifyInfo verifyInfo, boolean z, ColorTheme colorTheme) {
Drawable drawable;
Context context = textView.getContext();
@@ -267,4 +258,11 @@ public final class VerifyInfoHelper {
}
ViewExtKt.p(imageView);
}
public enum ColorTheme {
white,
normal,
light,
ultraLight
}
}
@@ -31,30 +31,6 @@ public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd<Suggest
private final boolean e;
private final Object f;
public static final class a<T> implements Comparator<Member> {
List a;
a(List list) {
this.a = list;
}
@Override // java.util.Comparator
/* renamed from: a */
public int compare(Member member, Member member2) {
int indexOf = this.a.indexOf(member);
int indexOf2 = this.a.indexOf(member2);
int i = indexOf;
if (indexOf < 0) {
i = Integer.MAX_VALUE;
}
int i2 = indexOf2;
if (indexOf2 < 0) {
i2 = Integer.MAX_VALUE;
}
return Intrinsics.a(i, i2);
}
}
public DialogGetMentionSuggestionCmd(int i, String str, Source source, boolean z, Object obj) {
this.f2365b = i;
this.c = str;
@@ -150,7 +126,7 @@ public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd<Suggest
Collection<Member> a2;
Source source;
if (ImDialogsUtils.d(this.f2365b) != PeerType.CHAT) {
return new Suggestion((EntityValue) null, (ProfilesInfo) null);
return new Suggestion(null, null);
}
Member Z = imEnvironment.Z();
int d = imEnvironment.a0().n().d();
@@ -186,8 +162,7 @@ public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd<Suggest
}
}), a(b2)));
EntityValue entityValue = new EntityValue(z2 ? null : l, z3);
switch (this.d)
{
switch (this.d) {
case CACHE:
source = Source.CACHE;
break;
@@ -236,4 +211,28 @@ public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd<Suggest
public String toString() {
return "DialogGetMentionSuggestionCmd(dialogId=" + this.f2365b + ", query=" + this.c + ", source=" + this.d + ", isAwaitNetwork=" + this.e + ", changerTag=" + this.f + ")";
}
public static final class a<T> implements Comparator<Member> {
List a;
a(List list) {
this.a = list;
}
@Override // java.util.Comparator
/* renamed from: a */
public int compare(Member member, Member member2) {
int indexOf = this.a.indexOf(member);
int indexOf2 = this.a.indexOf(member2);
int i = indexOf;
if (indexOf < 0) {
i = Integer.MAX_VALUE;
}
int i2 = indexOf2;
if (indexOf2 < 0) {
i2 = Integer.MAX_VALUE;
}
return Intrinsics.a(i, i2);
}
}
}
@@ -16,12 +16,18 @@ public enum MemberType {
public static MemberType a(int i) {
switch (i) {
case 0: return UNKNOWN;
case 1: return USER;
case 2: return GROUP;
case 3: return EMAIL;
case 4: return CONTACT;
case 5: return CUSTOM_BOT;
case 0:
return UNKNOWN;
case 1:
return USER;
case 2:
return GROUP;
case 3:
return EMAIL;
case 4:
return CONTACT;
case 5:
return CUSTOM_BOT;
}
throw new IllegalArgumentException("Unknown member type value: " + i);
}
@@ -11,16 +11,15 @@ import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.groups.Group;
import com.vk.im.engine.utils.DialogPermissionHelper;
import com.vk.im.ui.p.ImBridge7;
import ru.vtosters.hooks.DialogMenuInjectors;
import java.util.ArrayList;
import java.util.List;
import ru.vtosters.lite.dnr.DNRInjector;
public final class DialogActionsHelper {
public static final DialogActionsHelper a = new DialogActionsHelper();
public final List<DialogAction> a(ImConfig imConfig, Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) {
public List<DialogAction> a(ImConfig imConfig, Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) {
if (dialog != null && profilesSimpleInfo != null) {
long b2 = TimeProvider.f.b();
ChatSettings z1 = dialog.z1();
@@ -28,7 +27,7 @@ public final class DialogActionsHelper {
boolean I1 = z1 != null && z1.I1();
ArrayList<DialogAction> arrayList = new ArrayList<>();
DNRInjector.inject(dialog, arrayList);
DialogMenuInjectors.inject(dialog, arrayList);
arrayList.add(DialogAction.SEARCH);
@@ -85,7 +84,7 @@ public final class DialogActionsHelper {
return new ArrayList<>();
}
public final List<DialogAction> a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) {
public List<DialogAction> a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) {
long b2 = TimeProvider.f.b();
Profile profile = profilesSimpleInfo.get(dialog.getId());
@@ -97,7 +96,7 @@ public final class DialogActionsHelper {
ChatSettings z1 = dialog.z1();
ArrayList<DialogAction> arrayList = new ArrayList<>();
DNRInjector.inject(dialog, arrayList);
DialogMenuInjectors.inject(dialog, arrayList);
// if (dialog.hasUnread()) {
arrayList.add(DialogAction.MARK_AS_READ);
@@ -139,7 +138,7 @@ public final class DialogActionsHelper {
return arrayList;
}
public final boolean a(Dialog dialog) {
public boolean a(Dialog dialog) {
if (dialog.V1() || !dialog.y1()) {
return false;
}
@@ -11,5 +11,6 @@ public enum MsgAction {
PIN,
UNPIN,
TRANSLATE,
READTO
READTO,
KICK
}
@@ -3,9 +3,11 @@ package com.vk.im.ui.components.common;
import com.vk.core.extensions.CollectionExt;
import com.vk.im.engine.ImConfig;
import com.vk.im.engine.ImEngine;
import com.vk.im.engine.models.Member;
import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.engine.utils.MsgPermissionHelper;
import ru.vtosters.lite.utils.AccountManagerUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -14,11 +16,11 @@ import java.util.List;
public final class MsgActionHelper {
public static final MsgActionHelper a = new MsgActionHelper();
public final List<MsgAction> a(ImEngine imEngine, Dialog dialog, Collection<? extends Msg> collection) {
public List<MsgAction> a(ImEngine imEngine, Dialog dialog, Collection<? extends Msg> collection) {
return a(imEngine != null ? imEngine.g() : null, dialog, collection);
}
public final List<MsgAction> a(ImConfig imConfig, Dialog dialog, Collection<? extends Msg> collection) {
public List<MsgAction> a(ImConfig imConfig, Dialog dialog, Collection<? extends Msg> collection) {
if (imConfig != null && dialog != null && collection != null) {
MsgPermissionHelper.a aVar = new MsgPermissionHelper.a(imConfig, dialog, collection);
ArrayList<MsgAction> arrayList = new ArrayList<>();
@@ -35,6 +37,24 @@ public final class MsgActionHelper {
CollectionExt.a(arrayList, MsgAction.TRANSLATE, true);
CollectionExt.a(arrayList, MsgAction.READTO, true);
boolean isAdmin = false;
try {
if (aVar.a().z1().L0().t1() == AccountManagerUtils.getUserId()) { // is owner
isAdmin = true;
} else if (!aVar.a().z1().K1()) { // is you not kicked
for (Member admins : aVar.a().z1().t1()) { // admin list
if (admins.t1() == AccountManagerUtils.getUserId()) {
isAdmin = true;
break;
}
}
}
} catch (Exception ignored) {
}
CollectionExt.a(arrayList, MsgAction.KICK, isAdmin);
return arrayList;
}
@@ -21,6 +21,7 @@ import com.vk.im.ui.q.h.VideoAutoPlayer;
import com.vk.im.ui.views.span.OnSpanClickListener;
import com.vk.im.ui.views.span.OnSpanLongPressListener;
import com.vk.im.ui.views.sticker.StickerAnimationState;
import java.util.List;
@MainThread
@@ -17,11 +17,7 @@ import com.vk.im.engine.models.ProfilesSimpleInfo;
import com.vk.im.engine.models.attaches.AttachAudioMsg;
import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.dialogs.DialogTheme;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.engine.models.messages.MsgFromUser;
import com.vk.im.engine.models.messages.MsgIdType;
import com.vk.im.engine.models.messages.MsgSyncState;
import com.vk.im.engine.models.messages.WithUserContent;
import com.vk.im.engine.models.messages.*;
import com.vk.im.engine.utils.collection.IntSet;
import com.vk.im.ui.components.viewcontrollers.msg_list.entry.AdapterEntry;
import com.vk.im.ui.media.audio.AudioTrack;
@@ -78,11 +74,7 @@ public class VhBindArgs {
private boolean L() {
boolean var1;
if (this.K() <= this.m && !TimeProvider.f.c()) {
var1 = false;
} else {
var1 = true;
}
var1 = this.K() > this.m || TimeProvider.f.c();
return var1;
}
@@ -113,11 +105,7 @@ public class VhBindArgs {
public boolean B() {
AdapterEntry var1 = this.a;
boolean var2;
if (var1 != null && var1.c()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.c();
return var2;
}
@@ -125,11 +113,7 @@ public class VhBindArgs {
public boolean C() {
AdapterEntry var1 = this.a;
boolean var2;
if (var1 != null && var1.c() && this.a.c.getLocalId() == this.b.c.getLocalId()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.c() && this.a.c.getLocalId() == this.b.c.getLocalId();
return var2;
}
@@ -137,11 +121,7 @@ public class VhBindArgs {
public boolean D() {
AdapterEntry var1 = this.a;
boolean var2;
if (var1 != null && var1.a == 83) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a == 83;
return var2;
}
@@ -149,11 +129,7 @@ public class VhBindArgs {
public boolean E() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).i2()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).i2();
return var2;
}
@@ -161,11 +137,7 @@ public class VhBindArgs {
public boolean F() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).F0()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).F0();
return var2;
}
@@ -173,11 +145,7 @@ public class VhBindArgs {
public boolean G() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null && var1.a == 51) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a == 51;
return var2;
}
@@ -190,11 +158,7 @@ public class VhBindArgs {
long var1 = this.K();
long var3 = this.m;
boolean var5;
if (var1 > var3 && var1 < var3 * 2L) {
var5 = true;
} else {
var5 = false;
}
var5 = var1 > var3 && var1 < var3 * 2L;
return var5;
}
@@ -207,21 +171,13 @@ public class VhBindArgs {
} else if (!(var1 instanceof MsgFromUser)) {
return false;
} else {
MsgFromUser var7 = (MsgFromUser)var1;
MsgFromUser var7 = (MsgFromUser) var1;
Msg var3 = this.a.c;
boolean var4;
if (var3 != null && var3.d() == MsgSyncState.DONE) {
var4 = true;
} else {
var4 = false;
}
var4 = var3 != null && var3.d() == MsgSyncState.DONE;
boolean var5;
if (this.o == ImBgSyncState.CONNECTED) {
var5 = true;
} else {
var5 = false;
}
var5 = this.o == ImBgSyncState.CONNECTED;
boolean var6 = var2;
if (!var7.H0()) {
@@ -246,7 +202,7 @@ public class VhBindArgs {
byte var2 = 0;
int var3 = var2;
if (var1) {
AttachAudioMsg var4 = (AttachAudioMsg)((MsgFromUser)this.b.c).c(AttachAudioMsg.class, false);
AttachAudioMsg var4 = ((MsgFromUser) this.b.c).c(AttachAudioMsg.class, false);
var3 = var2;
if (var4 != null) {
var3 = var4.f();
@@ -271,11 +227,7 @@ public class VhBindArgs {
public boolean c() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).I0()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).I0();
return var2;
}
@@ -283,22 +235,14 @@ public class VhBindArgs {
public boolean d() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof WithUserContent && ((WithUserContent)var1).D0() != null) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof WithUserContent && ((WithUserContent) var1).D0() != null;
return var2;
}
public boolean e() {
boolean var1;
if (this.b.i > 0) {
var1 = true;
} else {
var1 = false;
}
var1 = this.b.i > 0;
return var1;
}
@@ -321,11 +265,7 @@ public class VhBindArgs {
public boolean g() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).G0()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).G0();
return var2;
}
@@ -333,33 +273,21 @@ public class VhBindArgs {
public boolean h() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).Z1()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).Z1();
return var2;
}
public boolean i() {
boolean var1;
if (this.j && !this.u() && !this.l()) {
var1 = true;
} else {
var1 = false;
}
var1 = this.j && !this.u() && !this.l();
return var1;
}
public boolean j() {
boolean var1;
if (!this.j || this.u() || this.B() && this.A() && !this.a.c.H1()) {
var1 = false;
} else {
var1 = true;
}
var1 = this.j && !this.u() && (!this.B() || !this.A() || this.a.c.H1());
return var1;
}
@@ -367,11 +295,7 @@ public class VhBindArgs {
public boolean k() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.U1()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.U1();
return var2;
}
@@ -379,11 +303,7 @@ public class VhBindArgs {
public boolean l() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.V1()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.V1();
return var2;
}
@@ -391,11 +311,7 @@ public class VhBindArgs {
public boolean m() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.W1()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.W1();
return var2;
}
@@ -408,11 +324,7 @@ public class VhBindArgs {
public boolean o() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null && var1.a == 101) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a == 101;
return var2;
}
@@ -420,11 +332,7 @@ public class VhBindArgs {
public boolean p() {
Msg var1 = this.b.c;
boolean var2;
if (var1 != null && var1.G1()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.G1();
return var2;
}
@@ -447,17 +355,13 @@ public class VhBindArgs {
public boolean r() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).e2()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 instanceof MsgFromUser && ((MsgFromUser) var1).e2();
return var2;
}
public boolean s() {
if(this.C != null)
if (this.C != null)
return switch (this.C) {
case LOCAL_ID -> this.D == this.b.c.getLocalId();
case VK_ID -> this.D == this.b.c.C1();
@@ -492,27 +396,19 @@ public class VhBindArgs {
public boolean v() {
IntSet var1 = this.r;
boolean var2;
if (var1 != null && var1.a(this.b.c.getLocalId())) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a(this.b.c.getLocalId());
return var2;
}
public boolean w() {
return this.z() ^ true;
return !this.z();
}
public boolean x() {
AdapterEntry var1 = this.c;
boolean var2;
if (var1 != null && var1.a == 100) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a == 100;
return var2;
}
@@ -520,11 +416,7 @@ public class VhBindArgs {
public boolean y() {
AdapterEntry var1 = this.c;
boolean var2;
if (var1 != null && var1.a == 96) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.a == 96;
return var2;
}
@@ -532,11 +424,7 @@ public class VhBindArgs {
public boolean z() {
AdapterEntry var1 = this.c;
boolean var2;
if (var1 != null && var1.c() && this.c.c.getLocalId() == this.b.c.getLocalId()) {
var2 = true;
} else {
var2 = false;
}
var2 = var1 != null && var1.c() && this.c.c.getLocalId() == this.b.c.getLocalId();
return var2;
}
@@ -21,7 +21,7 @@ import com.vk.im.ui.views.msg.MsgPartIconTwoRowView;
import com.vtosters.lite.R;
import kotlin.TypeCastException;
import kotlin.jvm.internal.Intrinsics;
import ru.vtosters.lite.hooks.CallsHook;
import ru.vtosters.hooks.CallsHook;
public final class MsgPartCallHolder extends MsgPartHolderBase<AttachCall> {
public static final a I = new a();
@@ -46,7 +46,7 @@ public final class MsgPartCallHolder extends MsgPartHolderBase<AttachCall> {
this.G = new StringBuilder();
}
private final void a(Msg var1) {
private void a(Msg var1) {
this.G.setLength(0);
MsgTimeFormatter var2 = this.F;
Context var3 = this.C;
@@ -55,7 +55,7 @@ public final class MsgPartCallHolder extends MsgPartHolderBase<AttachCall> {
this.H.setTimeText(this.G);
}
private final void a(boolean var1) {
private void a(boolean var1) {
MsgPartIconTwoRowView var2 = this.H;
int var3;
if (var1) {
@@ -67,7 +67,7 @@ public final class MsgPartCallHolder extends MsgPartHolderBase<AttachCall> {
var2.setIcon(var3);
}
private final void a(boolean var1, AttachCall var2) {
private void a(boolean var1, AttachCall var2) {
boolean var3 = !var1;
if (var2.h() == CallState.ERROR) {
this.H.setSubtitleText(m.vkim_msg_list_call_error);
@@ -23,7 +23,7 @@ public class MsgPartTextHolder extends MsgPartHolderBase<Attach> {
MsgPartTextHolder.this.f.a(MsgPartTextHolder.this.g.getLocalId());
});
mTextPart.setOnLongClickListener(view -> {
if(!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId());
if (!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId());
return false;
});
@@ -28,7 +28,6 @@ import com.vk.im.engine.models.Profile;
import com.vk.im.engine.models.dialogs.BubbleColors;
import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.engine.models.messages.MsgSyncState;
import com.vk.im.engine.utils.MsgPermissionHelper;
import com.vk.im.ui.components.viewcontrollers.msg_list.BombView;
import com.vk.im.ui.components.viewcontrollers.msg_list.SwipeToReplyItemTouchCallback;
@@ -50,6 +49,7 @@ import com.vk.im.ui.views.msg.bubble.MsgBubbleView;
import com.vk.im.ui.views.sticker.StickerAnimationState;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import ru.vtosters.lite.themes.ThemesHacks;
public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public static final int h0 = Screen.a(124);
@@ -57,46 +57,6 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
private static final int[][] j0;
private static final int[][] k0;
private static final int[][] l0;
private final MsgPartHolderBase B;
private final MsgPartHolderBindArgs C = new MsgPartHolderBindArgs();
private final Rect D = new Rect();
private final Rect E = new Rect();
private final Rect F = new Rect();
private final Rect G = new Rect();
private ColorDrawable H;
private final int I;
private final int J;
private final int K;
private final int L;
private final int M;
private Drawable N;
private Drawable O;
private BombView P;
private BombView.d Q;
private int R;
private int S;
private final DisplayNameFormatter T = new DisplayNameFormatter();
private final StringBuilder U = new StringBuilder();
private final StringBuilder V = new StringBuilder();
private final String W;
private final String X;
private final String Y;
private boolean Z;
private BubbleColors a0;
private final FluidHorizontalLayout b;
@ColorInt
private int b0;
private final AvatarView c;
private MsgListAdapterCallback c0;
private final Space d;
private Msg d0;
private final MsgBubbleView e;
private Dialog e0;
private final MsgStatusView f;
private int f0;
private final ImageView g;
private h g0 = new h();
private final Space h;
static {
int[] var0 = new int[]{0, 0, 0, 0};
@@ -120,20 +80,61 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
l0 = new int[][]{var0, {4, 4, 0, 0, 0, 2, 8, 0}, var1, var2, {4, 0, 0, 0, 0, 2, 8, 0}, {0, 0, 0, 0, 0, 2, 8, 0}, {4, 4, 0, 0, 0, 2, 8, 0}, {0, 0, 0, 0, 0, 0, 0, 0}};
}
private final MsgPartHolderBase B;
private final MsgPartHolderBindArgs C = new MsgPartHolderBindArgs();
private final Rect D = new Rect();
private final Rect E = new Rect();
private final Rect F = new Rect();
private final Rect G = new Rect();
private final int I;
private final int J;
private final int K;
private final int L;
private final int M;
private final DisplayNameFormatter T = new DisplayNameFormatter();
private final StringBuilder U = new StringBuilder();
private final StringBuilder V = new StringBuilder();
private final String W;
private final String X;
private final String Y;
private final FluidHorizontalLayout b;
private final AvatarView c;
private final Space d;
private final MsgBubbleView e;
private final MsgStatusView f;
private final ImageView g;
private final Space h;
private final ColorDrawable H;
private Drawable N;
private Drawable O;
private final BombView P;
private final BombView.d Q;
private final int R;
private final int S;
private boolean Z;
private BubbleColors a0;
@ColorInt
private int b0;
private MsgListAdapterCallback c0;
private Msg d0;
private Dialog e0;
private int f0;
private final h g0 = new h();
public VhMsg(View itemView, MsgPartHolderBase vhBase) {
super(itemView);
Context context = itemView.getContext();
LayoutInflater var4 = LayoutInflater.from(context);
this.b = (FluidHorizontalLayout) itemView;
this.c = (AvatarView) itemView.findViewById(com.vk.im.ui.h.avatar);
this.d = (Space) itemView.findViewById(com.vk.im.ui.h.avatar_space);
this.e = (MsgBubbleView) itemView.findViewById(com.vk.im.ui.h.bubble);
this.f = (MsgStatusView) itemView.findViewById(com.vk.im.ui.h.status);
this.g = (ImageView) itemView.findViewById(com.vk.im.ui.h.vkim_channel_share);
this.h = (Space) itemView.findViewById(com.vk.im.ui.h.status_space);
this.P = (BombView) itemView.findViewById(com.vk.im.ui.h.bomb);
this.c = itemView.findViewById(com.vk.im.ui.h.avatar);
this.d = itemView.findViewById(com.vk.im.ui.h.avatar_space);
this.e = itemView.findViewById(com.vk.im.ui.h.bubble);
this.f = itemView.findViewById(com.vk.im.ui.h.status);
this.g = itemView.findViewById(com.vk.im.ui.h.vkim_channel_share);
this.h = itemView.findViewById(com.vk.im.ui.h.status_space);
this.P = itemView.findViewById(com.vk.im.ui.h.bomb);
this.B = vhBase;
this.H = new ColorDrawable(context.getResources().getColor(com.vk.im.ui.d.msg_search_selection));
this.H = new ColorDrawable(ThemesHacks.getColors(context, com.vk.im.ui.d.msg_search_selection));
this.I = ContextExtKt.b(context, com.vk.im.ui.e.msg_bubble_max_width);
this.J = ContextExtKt.b(context, com.vk.im.ui.e.msg_reply_max_width);
this.K = ContextExtKt.b(context, com.vk.im.ui.e.msg_sticker_max_width);
@@ -144,55 +145,45 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
this.X = context.getString(m.vkim_accessibility_msg_unread);
MsgBubbleView var5 = this.e;
var5.setContentView(this.B.a(var4, var5));
ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams)this.e.getLayoutParams();
ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams) this.e.getLayoutParams();
Rect var6 = this.G;
var6.left = var7.leftMargin;
var6.right = var7.rightMargin;
var6.top = var7.topMargin;
var6.bottom = var7.bottomMargin;
ViewExtKt.b(this.c, new View.OnClickListener() {
public void onClick(View var1) {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getFrom());
}
ViewExtKt.b(this.c, var1 -> {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getFrom());
}
});
this.c.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View var1) {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getFrom());
return true;
} else {
return false;
}
}
});
ViewExtKt.b(this.g, new View.OnClickListener() {
public void onClick(View var1) {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0);
}
}
});
ViewExtKt.b(this.itemView, new View.OnClickListener() {
public void onClick(View var1) {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getLocalId());
}
}
});
this.itemView.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View var1) {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.b(VhMsg.this.d0.getLocalId());
}
this.c.setOnLongClickListener(var1 -> {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getFrom());
return true;
} else {
return false;
}
});
ViewExtKt.b(this.g, var1 -> {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0);
}
});
ViewExtKt.b(this.itemView, (View.OnClickListener) var1 -> {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.a(VhMsg.this.d0.getLocalId());
}
});
this.itemView.setOnLongClickListener((View.OnLongClickListener) var1 -> {
if (VhMsg.this.c0 != null && VhMsg.this.d0 != null) {
VhMsg.this.c0.b(VhMsg.this.d0.getLocalId());
}
return true;
});
this.R = ContextExtKt.i(context, com.vk.im.ui.c.im_msg_part_corner_radius_small);
this.S = ContextExtKt.i(context, com.vk.im.ui.c.im_msg_part_corner_radius_big);
this.c0 = null;
@@ -204,6 +195,10 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
};
}
public static VhMsg a(LayoutInflater var0, ViewGroup var1, MsgPartHolderBase var2) {
return new VhMsg(var0.inflate(j.vkim_msg_list_item_msg_from_user, var1, false), var2);
}
private int a(Style var1, Style var2, boolean var3) {
int var4;
if (var3) {
@@ -284,20 +279,15 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
case 103:
return VhMsg.Style.EMPTY;
default:
StringBuilder var3 = new StringBuilder();
var3.append("Unknown viewType: ");
var3.append(var1);
throw new IllegalArgumentException(var3.toString());
String var3 = "Unknown viewType: " +
var1;
throw new IllegalArgumentException(var3);
}
}
return VhMsg.Style.TWO_LINE;
}
public static VhMsg a(LayoutInflater var0, ViewGroup var1, MsgPartHolderBase var2) {
return new VhMsg(var0.inflate(j.vkim_msg_list_item_msg_from_user, var1, false), var2);
}
private void a(VhBindArgs args, Rect rect) {
Style var3 = this.a(args.b);
boolean var4 = args.b.f();
@@ -337,7 +327,8 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
if (args.w()) {
var12 = var11;
if (args.k()) {
label30: {
label30:
{
if (args.G()) {
var12 = Screen.a(4);
} else {
@@ -411,11 +402,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
Msg var10 = var5.c;
var9 = var6;
if (var10 != null) {
if (var10.v1() == args.g.t1()) {
var9 = true;
} else {
var9 = false;
}
var9 = var10.v1() == args.g.t1();
if (var10.O1()) {
int var12 = var10.d().ordinal();
@@ -433,11 +420,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
b = args.I();
}
} else {
if (var10.C1() <= args.i) {
var6 = true;
} else {
var6 = false;
}
var6 = var10.C1() <= args.i;
if (!var6 && !var9) {
var11 = MsgStatus.UNREAD;
@@ -575,18 +558,14 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
partArgs.o = args.n;
partArgs.s = args.v();
partArgs.t = args.q;
partArgs.u = var4.b() ^ true;
partArgs.u = !var4.b();
boolean var5;
if (var3.i > 0) {
var5 = true;
} else {
var5 = false;
}
var5 = var3.i > 0;
partArgs.v = var5;
partArgs.w = args.k;
partArgs.x = args.l;
partArgs.y = this.Z ^ true;
partArgs.y = !this.Z;
partArgs.z = args.s;
partArgs.A = args.t;
partArgs.l = this.b0;
@@ -671,7 +650,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
private void f(VhBindArgs args) {
Msg var2 = args.b.c;
if (var2 != null) {
this.P.setStateListener((BombView.d)null);
this.P.setStateListener(null);
if (var2.M1() && !var2.E1()) {
this.P.setVisibility(View.GONE);
} else {
@@ -810,29 +789,18 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
this.e.setFwdPadding(this.D);
this.e.setContentPadding(this.E);
boolean var7;
if (args.b.a == 83) {
var7 = true;
} else {
var7 = false;
}
var7 = args.b.a == 83;
boolean var5;
if (args.a.a == 83) {
var5 = true;
} else {
var5 = false;
}
var5 = args.a.a == 83;
if (!args.F() || !var7 && !var5) {
var5 = true;
} else {
var5 = false;
}
var5 = !args.F() || !var7 && !var5;
MsgBubbleView var8 = this.e;
boolean var6 = var4;
if (var5) {
label57: {
label57:
{
if (!args.C()) {
var6 = var4;
if (!args.z()) {
@@ -877,7 +845,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
if (args.s()) {
this.itemView.setBackground(this.H);
} else {
this.itemView.setBackground((Drawable)null);
this.itemView.setBackground((Drawable) null);
}
}
@@ -903,18 +871,10 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
return false;
} else {
boolean var5;
if (var2.a == 50) {
var5 = true;
} else {
var5 = false;
}
var5 = var2.a == 50;
boolean var6;
if (!args.z() && var2.i == 0) {
var6 = true;
} else {
var6 = false;
}
var6 = !args.z() && var2.i == 0;
if (var5 || var6 || args.y() || args.x()) {
var4 = true;
@@ -933,11 +893,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
private boolean u(int var1) {
boolean var2;
if (var1 != 2 && var1 != 3) {
var2 = false;
} else {
var2 = true;
}
var2 = var1 == 2 || var1 == 3;
return var2;
}
@@ -959,9 +915,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
Msg var1 = this.d0;
if (var1 != null) {
Dialog var2 = this.e0;
if (var2 != null && MsgPermissionHelper.b.a(var2, var1)) {
return true;
}
return var2 != null && MsgPermissionHelper.b.a(var2, var1);
}
}
return false;
@@ -985,7 +939,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public void a(@NonNull Msg var1, int var2) {
if (this.f0()) {
((MsgPartCarouselHolder)this.B).a(var1, var2);
((MsgPartCarouselHolder) this.B).a(var1, var2);
}
}
@@ -996,11 +950,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public void a(VhBindArgs args) {
boolean var2;
if (VKThemeHelper.l() == VKTheme.VKAPP_LIGHT) {
var2 = true;
} else {
var2 = false;
}
var2 = VKThemeHelper.l() == VKTheme.VKAPP_LIGHT;
this.Z = var2;
AdapterEntry var3 = args.b;
@@ -1061,11 +1011,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public boolean f0() {
boolean var1;
if (this.f0 == 100) {
var1 = true;
} else {
var1 = false;
}
var1 = this.f0 == 100;
return var1;
}
@@ -1082,7 +1028,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
this.B.c(var1);
}
private static enum Style {
private enum Style {
BUTTON(6),
EMPTY(7),
FWD_SENDER(4),
@@ -1094,14 +1040,14 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public final int index;
private Style(int var3) {
Style(int var3) {
this.index = var3;
}
}
private class h implements Functions<Unit> {
private int a;
private Runnable b;
private final int a;
private final Runnable b;
private h() {
this.a = Screen.a(12);
@@ -1110,7 +1056,7 @@ public class VhMsg extends VhBase implements SwipeToReplyItemTouchCallback.b {
public Unit invoke() {
if (VhMsg.this.e.getMeasuredWidth() < VhMsg.h0) {
((ViewGroup.MarginLayoutParams)VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a;
((ViewGroup.MarginLayoutParams) VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a;
VhMsg.this.e.invalidate();
VhMsg.this.e.requestLayout();
} else {
@@ -6,7 +6,6 @@ import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.TextView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.vk.core.ui.VkBottomSheetBehavior;
@@ -27,35 +26,28 @@ import com.vtosters.lite.R;
public final class MentionsController {
private final RichEditText a;
private final ImEngine mImEngine;
private final View j;
private final b k;
private boolean state;
private CoordinatorLayout mCoordinatorLayout;
private VkBottomSheetBehavior<RecyclerView> mBottomSheetBehavior;
private boolean e;
private DialogMentionComponent mDialogMentionComponent;
private MentionsManager mMentionsManager;
private final ImEngine mImEngine;
private int i;
private final View j;
private final b k;
public interface b {
boolean c();
boolean d();
}
private final DialogMentionComponent mDialogMentionComponent;
private final MentionsManager mMentionsManager;
private final int i;
public MentionsController(ImEngine imEngine, int i, View view, b bVar) {
this.mImEngine = imEngine;
this.i = i;
this.j = view;
this.k = bVar;
this.a = (RichEditText) this.j.findViewById(R.id.writebar_edit);
this.a = this.j.findViewById(R.id.writebar_edit);
this.mMentionsManager = new MentionsManager(this.a, new MentionSuggestionsSupplier() {
@Override
public boolean a() {
if(MentionsController.this.state)
if (MentionsController.this.state)
return ViewExtKt.i(MentionsController.this.mCoordinatorLayout);
return false;
}
@@ -72,7 +64,7 @@ public final class MentionsController {
@Override
public void b() {
MentionsController.this.mDialogMentionComponent.a(0, (String) null);
MentionsController.this.mDialogMentionComponent.a(0, null);
MentionsController.this.g();
}
});
@@ -93,19 +85,19 @@ public final class MentionsController {
});
}
public final void g() {
public void g() {
this.e = false;
j();
}
private final void h() {
private void h() {
if (this.state) {
return;
}
View inflate = ((ViewStub) this.j.findViewById(R.id.mentions_container_stub)).inflate();
ViewGroup viewGroup = (ViewGroup) inflate.findViewById(R.id.mentions_container);
viewGroup.addView(this.mDialogMentionComponent.a(viewGroup, (Bundle) null));
this.mCoordinatorLayout = inflate.findViewById(R.id.mentions_cl);;
ViewGroup viewGroup = inflate.findViewById(R.id.mentions_container);
viewGroup.addView(this.mDialogMentionComponent.a(viewGroup, null));
this.mCoordinatorLayout = inflate.findViewById(R.id.mentions_cl);
this.mBottomSheetBehavior = (VkBottomSheetBehavior<RecyclerView>) ((CoordinatorLayout.LayoutParams) viewGroup.getLayoutParams()).getBehavior();
this.mBottomSheetBehavior.a(new VkBottomSheetBehavior.b() {
@Override
@@ -121,12 +113,12 @@ public final class MentionsController {
this.state = true;
}
public final void i() {
public void i() {
this.e = true;
j();
}
public final void j() {
public void j() {
if (this.k.d()) {
Context context = this.j.getContext();
Activity f = ContextExtKt.f(context);
@@ -140,27 +132,33 @@ public final class MentionsController {
}
}
public final void a() {
public void a() {
this.mDialogMentionComponent.g();
}
public final void b() {
public void b() {
this.mDialogMentionComponent.h();
}
public final b c() {
public b c() {
return this.k;
}
public final int d() {
public int d() {
return this.i;
}
public final void e() {
public void e() {
this.mDialogMentionComponent.q();
}
public final void f() {
public void f() {
this.mDialogMentionComponent.r();
}
public interface b {
boolean c();
boolean d();
}
}
@@ -2,13 +2,13 @@ package com.vk.im.ui.views;
import androidx.annotation.NonNull;
public class Corners{
public class Corners {
private int topLeft;
private int topRight;
private int bottomLeft;
private int bottomRight;
public Corners(int i, int i2, int i3, int i4){
public Corners(int i, int i2, int i3, int i4) {
this.topLeft = i;
this.topRight = i2;
this.bottomLeft = i3;
@@ -23,7 +23,7 @@ public class Corners{
this(0, 0, 0, 0, 15);
}
public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj){
public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj) {
if ((i5 & 1) != 0) {
i = corners.topLeft;
}
@@ -40,30 +40,30 @@ public class Corners{
return corners;
}
public int a(){
public int a() {
return this.bottomLeft;
}
public int b(){
public int b() {
return this.bottomRight;
}
public int c(){
public int c() {
return this.topLeft;
}
public int d(){
public int d() {
return this.topRight;
}
public boolean e(){
public boolean e() {
int i;
int i2 = this.topLeft;
int i3 = this.topRight;
return i2 == i3 && i3 == (i = this.bottomRight) && this.bottomLeft == i;
}
public boolean equals(Object obj){
public boolean equals(Object obj) {
if (this != obj) {
if (!(obj instanceof Corners)) {
return false;
@@ -74,24 +74,24 @@ public class Corners{
return true;
}
public boolean f(){
public boolean f() {
return this.topLeft == 0 && e();
}
public void g(){
public void g() {
a(0, 0);
}
public int hashCode(){
public int hashCode() {
return (((((this.topLeft * 31) + this.topRight) * 31) + this.bottomLeft) * 31) + this.bottomRight;
}
@NonNull
public String toString(){
public String toString() {
return "Corners(topLeft=" + this.topLeft + ", topRight=" + this.topRight + ", bottomLeft=" + this.bottomLeft + ", bottomRight=" + this.bottomRight + ")";
}
public Corners a(int i, int i2){
public Corners a(int i, int i2) {
if ((i2 & 1) > 0) {
this.topLeft = i;
}
@@ -113,7 +113,7 @@ public class Corners{
return this;
}
public Corners a(int i, int i2, int i3, int i4){
public Corners a(int i, int i2, int i3, int i4) {
this.topLeft = i;
this.topRight = i2;
this.bottomLeft = i3;
@@ -121,7 +121,7 @@ public class Corners{
return this;
}
public Corners a(Corners corners){
public Corners a(Corners corners) {
this.topLeft = corners.topLeft;
this.topRight = corners.topRight;
this.bottomLeft = corners.bottomLeft;
@@ -9,19 +9,18 @@ import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.vk.im.ui.o;
public class StoryBorderView extends View {
private boolean B;
private final Paint a;
private final int g;
private final int h;
private boolean B;
private LinearGradient f15747b;
private int f15748c;
private int f15749d;
private int f15750e;
private int f15751f;
private final int g;
private final int h;
public StoryBorderView(Context context) {
this(context, null, 0, 6);
@@ -35,10 +34,32 @@ public class StoryBorderView extends View {
this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i);
}
public StoryBorderView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
this.a = paint;
TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, o.StoryBorderView);
setBorderColor(obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderColor, 0));
this.g = obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderGradientStartColor, 0);
this.h = obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderGradientEndColor, 0);
setBorderWidth(obtainStyledAttributes.getDimensionPixelSize(o.StoryBorderView_vkim_borderWidth, 0));
obtainStyledAttributes.recycle();
}
public int getBorderColor() {
return this.f15749d;
}
public void setBorderColor(int i) {
if (this.f15749d != i) {
this.f15749d = i;
this.a.setColor(i);
invalidate();
}
}
public int getBorderGradientEndColor() {
return this.h;
}
@@ -51,14 +72,33 @@ public class StoryBorderView extends View {
return this.f15750e;
}
public void setBorderWidth(int i) {
if (this.f15750e != i) {
this.f15750e = i;
this.a.setStrokeWidth(i);
invalidate();
}
}
public int getPadding() {
return this.f15751f;
}
public void setPadding(int i) {
if (this.f15751f != i) {
this.f15751f = i;
setPadding(i, i, i, i);
}
}
public int getViewSize() {
return this.f15748c;
}
public void setViewSize(int i) {
this.f15748c = i;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawOval(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(), this.a);
@@ -70,22 +110,6 @@ public class StoryBorderView extends View {
setMeasuredDimension(i3, i3);
}
public void setBorderColor(int i) {
if (this.f15749d != i) {
this.f15749d = i;
this.a.setColor(i);
invalidate();
}
}
public void setBorderWidth(int i) {
if (this.f15750e != i) {
this.f15750e = i;
this.a.setStrokeWidth(i);
invalidate();
}
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams layoutParams) {
super.setLayoutParams(layoutParams);
@@ -109,29 +133,4 @@ public class StoryBorderView extends View {
invalidate();
}
}
public void setPadding(int i) {
if (this.f15751f != i) {
this.f15751f = i;
setPadding(i, i, i, i);
}
}
public void setViewSize(int i) {
this.f15748c = i;
}
public StoryBorderView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
this.a = paint;
TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, o.StoryBorderView);
setBorderColor(obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderColor, 0));
this.g = obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderGradientStartColor, 0);
this.h = obtainStyledAttributes.getInt(o.StoryBorderView_vkim_borderGradientEndColor, 0);
setBorderWidth(obtainStyledAttributes.getDimensionPixelSize(o.StoryBorderView_vkim_borderWidth, 0));
obtainStyledAttributes.recycle();
}
}
@@ -1,8 +1,5 @@
package com.vk.im.ui.views.dialogs;
import static com.vk.im.ui.d.vkim_administration_title;
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -17,10 +14,9 @@ import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.appcompat.widget.AppCompatImageView;
import b.h.g.r.MathExt;
import com.vk.core.extensions.AnimationExtKt;
import com.vk.core.util.ContextExtKt;
import com.vk.core.util.Screen;
@@ -42,9 +38,10 @@ import com.vk.im.ui.views.FixTextView;
import com.vk.im.ui.views.avatars.AvatarView;
import com.vk.im.ui.views.avatars.StoryBorderView;
import b.h.g.r.MathExt;
import static com.vk.im.ui.d.vkim_administration_title;
import static ru.vtosters.hooks.other.ThemesUtils.getAccentColor;
public class DialogItemView extends ViewGroup{
public class DialogItemView extends ViewGroup {
private final int B;
private final int C;
private final int D;
@@ -80,15 +77,15 @@ public class DialogItemView extends ViewGroup{
private boolean g0;
private boolean h0;
public DialogItemView(Context context){
public DialogItemView(Context context) {
this(context, null, 0, 6);
}
public DialogItemView(Context context, AttributeSet attributeSet){
public DialogItemView(Context context, AttributeSet attributeSet) {
this(context, attributeSet, 0, 4);
}
public DialogItemView(Context context, AttributeSet attributeSet, int i){
public DialogItemView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
Resources resources = getResources();
this.a = resources.getDisplayMetrics();
@@ -255,11 +252,11 @@ public class DialogItemView extends ViewGroup{
addView(this.f0);
}
public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2){
public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2) {
this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i);
}
public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){
public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) {
if ((i & 1) != 0) {
f = 0.0f;
}
@@ -279,7 +276,7 @@ public class DialogItemView extends ViewGroup{
return AnimationExtKt.b(view, f, j, j2, runnable);
}
public static void a(View view, float f, float f2, int i, Object obj){
public static void a(View view, float f, float f2, int i, Object obj) {
if ((i & 1) != 0) {
f = 1.0f;
}
@@ -291,7 +288,7 @@ public class DialogItemView extends ViewGroup{
AnimationExtKt.a(view, f, f2);
}
public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){
public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) {
if ((i & 1) != 0) {
f = 1.0f;
}
@@ -311,14 +308,14 @@ public class DialogItemView extends ViewGroup{
return AnimationExtKt.a(view, f, j, j2, runnable);
}
static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj){
static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj) {
if ((i4 & 4) != 0) {
i3 = 0;
}
dialogItemView.a(i, i2, i3);
}
static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj){
static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj) {
if ((i7 & 1) != 0) {
i = -2;
}
@@ -340,19 +337,19 @@ public class DialogItemView extends ViewGroup{
return dialogItemView.a(i, i2, i3, i4, i5, i6);
}
private Drawable getDrOnlineMobile(){
private Drawable getDrOnlineMobile() {
return ContextExtKt.c(getContext(), f.ic_online_mobile_vkapp_composite_16);
}
private Drawable getDrOnlineVkMe(){
private Drawable getDrOnlineVkMe() {
return ContextExtKt.c(getContext(), f.ic_online_mobile_vkme_composite_16);
}
private Drawable getDrOnlineWeb(){
private Drawable getDrOnlineWeb() {
return ContextExtKt.c(getContext(), f.ic_online_web_composite_16);
}
private int getTimeMargin(){
private int getTimeMargin() {
Layout layout = this.T.getLayout();
if (layout == null) {
@@ -374,11 +371,11 @@ public class DialogItemView extends ViewGroup{
return 0;
}
private int getTitleColorHighlight(){
private int getTitleColorHighlight() {
return ContextExtKt.a(getContext(), vkim_administration_title);
}
private void h(int i, int i2, int i3, int i4){
private void h(int i, int i2, int i3, int i4) {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int measuredWidth = getMeasuredWidth() - getPaddingRight();
@@ -412,51 +409,51 @@ public class DialogItemView extends ViewGroup{
j(measuredWidth - r, max6, measuredWidth, q + max6);
}
private int i(){
private int i() {
return g(this.K);
}
private int j(){
private int j() {
return f(this.T) + Math.max(f(this.V), f(this.U));
}
private int k(){
private int k() {
return Math.max(g(this.T), g(this.V) + g(this.U));
}
private int l(){
private int l() {
return Math.max(Math.max(f(this.N), f(this.R)), Math.max(f(this.O), f(this.P)));
}
private int m(){
private int m() {
return View.MeasureSpec.makeMeasureSpec(0, 0);
}
private boolean n(){
private boolean n() {
return e(this.S);
}
private int o(){
private int o() {
return f(this.S);
}
private int p(){
private int p() {
return g(this.S);
}
private int q(){
private int q() {
return MathExt.a(f(this.b0), f(this.c0), f(this.d0), f(this.e0), f(this.f0));
}
private int r(){
private int r() {
return g(this.b0) + MathExt.a(g(this.c0), g(this.d0), g(this.e0), g(this.f0));
}
private int s(){
private int s() {
return Math.max(f(this.a0), f(this.W));
}
private void setAttach(CharSequence charSequence){
private void setAttach(CharSequence charSequence) {
int i = VISIBLE;
if (charSequence == null || charSequence.length() == 0) {
@@ -467,57 +464,57 @@ public class DialogItemView extends ViewGroup{
this.U.setText(charSequence);
}
private void setBodyLinesCount(int i){
private void setBodyLinesCount(int i) {
boolean z = i == 1;
this.T.setSingleLine(z);
this.T.setMaxLines(i);
}
private int t(){
private int t() {
return g(this.a0) + g(this.W);
}
private void u(){
private void u() {
ViewExtKt.b(this.b0, this.g0 | this.h0);
}
public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo){
public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) {
this.K.a(dialog, profilesSimpleInfo);
}
public void b(){
public void b() {
a(null, (ComposingType) null);
}
public void c(){
public void c() {
a(getDrOnlineMobile());
}
public void d(){
public void d() {
a(getDrOnlineVkMe());
}
public void e(){
public void e() {
a(getDrOnlineWeb());
}
public void f(){
public void f() {
this.S.setVisibility(GONE);
this.S.e();
}
public void g(){
public void g() {
b(this.L, 0.0f, 0L, 0L, null, 15, null);
}
@Override // android.view.ViewGroup, android.view.View
protected void onDetachedFromWindow(){
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
a(this.L, 0.0f, 0.0f, 3, null);
}
@Override // android.view.ViewGroup, android.view.View
protected void onLayout(boolean z, int i, int i2, int i3, int i4){
protected void onLayout(boolean z, int i, int i2, int i3, int i4) {
if (this.E) {
g(i, i2, i3, i4);
} else {
@@ -526,7 +523,7 @@ public class DialogItemView extends ViewGroup{
}
@Override // android.view.View
protected void onMeasure(int i, int i2){
protected void onMeasure(int i, int i2) {
if (this.E) {
c(i);
} else {
@@ -534,11 +531,11 @@ public class DialogItemView extends ViewGroup{
}
}
public void setAvatar(Profile profile){
public void setAvatar(Profile profile) {
this.K.a(profile);
}
public void setBombVisible(boolean z){
public void setBombVisible(boolean z) {
if (z) {
this.b0.setImageResource(f.ic_bomb_composite_24);
}
@@ -548,23 +545,23 @@ public class DialogItemView extends ViewGroup{
u();
}
public void setCasperIconColor(@ColorInt int i){
public void setCasperIconColor(@ColorInt int i) {
ViewExtKt.a(this.Q, i);
}
public void setCasperIconVisible(boolean z){
public void setCasperIconVisible(boolean z) {
this.Q.setVisibility(z ? VISIBLE : GONE);
}
public void setErrorVisible(boolean z){
public void setErrorVisible(boolean z) {
this.f0.setVisibility(z ? VISIBLE : GONE);
}
public void setGiftVisible(boolean z){
public void setGiftVisible(boolean z) {
this.V.setVisibility(z ? VISIBLE : GONE);
}
public void setHasStories(boolean z){
public void setHasStories(boolean z) {
if (this.I == z) {
return;
}
@@ -590,7 +587,7 @@ public class DialogItemView extends ViewGroup{
setClipToPadding(!z);
}
public void setMentionVisible(boolean z){
public void setMentionVisible(boolean z) {
if (z) {
this.b0.setImageResource(f.ic_mention_composite_24);
}
@@ -600,15 +597,15 @@ public class DialogItemView extends ViewGroup{
u();
}
public void setMutedVisible(boolean z){
public void setMutedVisible(boolean z) {
this.P.setVisibility(z ? VISIBLE : GONE);
}
public void setOnStoryClickListener(View.OnClickListener onClickListener){
public void setOnStoryClickListener(View.OnClickListener onClickListener) {
ViewExtKt.b(this.M, onClickListener);
}
public void setSender(Profile profile){
public void setSender(Profile profile) {
if (profile == null) {
f();
return;
@@ -617,29 +614,29 @@ public class DialogItemView extends ViewGroup{
this.S.a(profile);
}
public void setSendingVisible(boolean z){
public void setSendingVisible(boolean z) {
this.e0.setVisibility(z ? VISIBLE : GONE);
}
public void setTime(CharSequence charSequence){
public void setTime(CharSequence charSequence) {
this.R.setText(charSequence);
}
public void setUnreadOutVisible(boolean z){
public void setUnreadOutVisible(boolean z) {
this.d0.setVisibility(z ? VISIBLE : GONE);
}
public void setVerifiedVisible(boolean z){
public void setVerifiedVisible(boolean z) {
this.O.setVisibility(z ? VISIBLE : GONE);
}
private void a(Drawable drawable){
private void a(Drawable drawable) {
ViewExtKt.c(this.L, a(16), a(20));
this.L.setImageDrawable(drawable);
a(this.L, 0.0f, 0L, 0L, null, 15, null);
}
private void c(int i){
private void c(int i) {
int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight();
d(b(size - this.K.getViewSize()), m());
int r = r();
@@ -655,7 +652,7 @@ public class DialogItemView extends ViewGroup{
setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q()))));
}
private void d(int i){
private void d(int i) {
int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight();
d(b(size - this.K.getViewSize()), m());
int r = r();
@@ -672,7 +669,7 @@ public class DialogItemView extends ViewGroup{
setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q()))));
}
private void e(int i, int i2, int i3, int i4){
private void e(int i, int i2, int i3, int i4) {
if (e(this.T) && e(this.U)) {
c(i, i2, i3, i4);
} else if (e(this.T)) {
@@ -683,7 +680,7 @@ public class DialogItemView extends ViewGroup{
}
}
private void g(int i, int i2, int i3, int i4){
private void g(int i, int i2, int i3, int i4) {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int measuredWidth = getMeasuredWidth() - getPaddingRight();
@@ -706,12 +703,12 @@ public class DialogItemView extends ViewGroup{
j(measuredWidth - r, i8, measuredWidth, q + i8);
}
private void i(int i, int i2, int i3, int i4){
private void i(int i, int i2, int i3, int i4) {
AvatarView avatarView = this.S;
b(avatarView, i + b(avatarView), ((i2 + ((i4 - i2) / 2)) - (this.S.getMeasuredHeight() / 2)) + d(this.S));
}
private void j(int i, int i2, int i3, int i4){
private void j(int i, int i2, int i3, int i4) {
int i5 = i2 + ((i4 - i2) / 2);
AppCompatImageView appCompatImageView = this.b0;
b(appCompatImageView, b(appCompatImageView) + i, (i5 - (this.b0.getMeasuredHeight() / 2)) + d(this.b0));
@@ -728,7 +725,7 @@ public class DialogItemView extends ViewGroup{
b(appCompatImageView4, i + b(appCompatImageView4), (i5 - (this.f0.getMeasuredHeight() / 2)) + d(this.f0));
}
private void k(int i, int i2, int i3, int i4){
private void k(int i, int i2, int i3, int i4) {
int i5 = i2 + ((i4 - i2) / 2);
TextView textView = this.W;
b(textView, b(textView) + i, (i5 - (this.W.getMeasuredHeight() / 2)) + d(this.W));
@@ -741,7 +738,7 @@ public class DialogItemView extends ViewGroup{
b(appCompatImageView, i + b(appCompatImageView), (i5 - (this.a0.getMeasuredHeight() / 2)) + d(this.a0));
}
public void b(CharSequence charSequence, boolean z){
public void b(CharSequence charSequence, boolean z) {
int i = VISIBLE;
if (charSequence == null || charSequence.length() == 0) {
@@ -755,7 +752,7 @@ public class DialogItemView extends ViewGroup{
this.g.a(z ? this.B : this.h);
}
private void f(int i, int i2, int i3, int i4){
private void f(int i, int i2, int i3, int i4) {
TextView textView = this.N;
b(textView, i + b(textView), d(this.N) + i2);
int right = this.N.getRight() + c(this.N);
@@ -783,7 +780,7 @@ public class DialogItemView extends ViewGroup{
}
}
private void l(int i, int i2, int i3, int i4){
private void l(int i, int i2, int i3, int i4) {
TextView textView = this.W;
b(textView, i + b(textView), i2);
int right = this.W.getRight() + c(this.W);
@@ -791,33 +788,33 @@ public class DialogItemView extends ViewGroup{
b(appCompatImageView, right + b(appCompatImageView), (((this.W.getBottom() + this.W.getTop()) / 2) - (this.a0.getMeasuredHeight() / 2)) + d(this.a0));
}
private void e(int i, int i2){
private void e(int i, int i2) {
b(this.a0, i, 0, i2, 0);
b(this.W, i, g(this.a0), i2, 0);
}
public void a(CharSequence charSequence, boolean z){
public void a(CharSequence charSequence, boolean z) {
this.N.setText(charSequence);
this.N.setTextColor(z ? getTitleColorHighlight() : this.f15761e);
}
private void b(int i, int i2){
private void b(int i, int i2) {
b(this.O, i, 0, i2, 0);
b(this.Q, i, 0, i2, 0);
b(this.P, i, 0, i2, 0);
b(this.R, i, 0, i2, 0);
}
public void a(){
public void a() {
a(null, 1);
setAttach(null);
}
private boolean e(View view){
private boolean e(View view) {
return view.getVisibility() == VISIBLE;
}
public void a(CharSequence charSequence, int i, CharSequence charSequence2){
public void a(CharSequence charSequence, int i, CharSequence charSequence2) {
boolean z = false;
if (!(charSequence == null || charSequence.length() == 0)) {
@@ -840,11 +837,11 @@ public class DialogItemView extends ViewGroup{
}
}
private void b(int i, int i2, int i3){
private void b(int i, int i2, int i3) {
a(this.N, i, i3, i2, 0);
}
private void b(int i, int i2, int i3, int i4){
private void b(int i, int i2, int i3, int i4) {
int g = e(this.V) ? g(this.V) + i : i;
TextView textView = this.U;
a(textView, g + b(textView), i2 + d(this.U));
@@ -852,7 +849,7 @@ public class DialogItemView extends ViewGroup{
b(appCompatImageView, i + b(appCompatImageView), (this.U.getTop() + (this.U.getMeasuredHeight() / 2)) - (this.V.getMeasuredHeight() / 2));
}
private void a(CharSequence charSequence, int i){
private void a(CharSequence charSequence, int i) {
int i2 = VISIBLE;
if (charSequence == null || charSequence.length() == 0) {
@@ -864,13 +861,13 @@ public class DialogItemView extends ViewGroup{
setBodyLinesCount(i);
}
private void b(View view, int i, int i2, int i3, int i4){
private void b(View view, int i, int i2, int i3, int i4) {
if (view.getVisibility() != GONE) {
a(view, i, i2, i3, i4);
}
}
public void a(CharSequence charSequence, ComposingType composingType){
public void a(CharSequence charSequence, ComposingType composingType) {
Drawable drawable = composingType == ComposingType.AUDIO ? this.H : this.G;
boolean z = false;
drawable.setVisible(!(charSequence == null || charSequence.length() == 0), false);
@@ -885,13 +882,13 @@ public class DialogItemView extends ViewGroup{
this.W.setText(charSequence);
}
private void b(View view, int i, int i2){
private void b(View view, int i, int i2) {
if (view.getVisibility() == VISIBLE) {
a(view, i, i2);
}
}
private int b(View view){
private int b(View view) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (marginLayoutParams != null) {
@@ -901,11 +898,11 @@ public class DialogItemView extends ViewGroup{
return 0;
}
private int b(int i){
private int b(int i) {
return View.MeasureSpec.makeMeasureSpec(i, MeasureSpec.AT_MOST);
}
private int g(View view){
private int g(View view) {
if (view.getVisibility() == GONE) {
return 0;
}
@@ -915,13 +912,13 @@ public class DialogItemView extends ViewGroup{
return marginLayoutParams != null ? measuredWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin : measuredWidth;
}
private void a(int i, int i2){
private void a(int i, int i2) {
a(this.K, i, 0, i2, 0);
b(this.M, i, 0, i2, 0);
b(this.L, i, 0, i2, 0);
}
private int f(View view){
private int f(View view) {
if (view.getVisibility() == GONE) {
return 0;
}
@@ -934,32 +931,32 @@ public class DialogItemView extends ViewGroup{
return measuredHeight;
}
private void a(int i, int i2, int i3, int i4){
private void a(int i, int i2, int i3, int i4) {
AvatarView avatarView = this.K;
a(avatarView, i + b(avatarView), i2 + d(this.K));
a(this.M, this.K.getLeft() - this.K.getPaddingLeft(), this.K.getTop() - this.K.getPaddingTop());
b(this.L, this.K.getRight() - this.L.getMeasuredWidth(), this.K.getBottom() - this.L.getMeasuredHeight());
}
private void c(int i, int i2){
private void c(int i, int i2) {
b(this.S, i, 0, i2, 0);
}
private void c(int i, int i2, int i3, int i4){
private void c(int i, int i2, int i3, int i4) {
d(i, i2, i3, f(this.T) + i2);
b(i, i2 + f(this.T), i3, i4);
}
private int h(){
private int h() {
return f(this.K);
}
private void d(int i, int i2, int i3, int i4){
private void d(int i, int i2, int i3, int i4) {
TextView textView = this.T;
a(textView, i + b(textView), i2 + d(this.T));
}
private int c(View view){
private int c(View view) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (marginLayoutParams != null) {
@@ -969,7 +966,7 @@ public class DialogItemView extends ViewGroup{
return 0;
}
private void d(int i, int i2){
private void d(int i, int i2) {
b(this.b0, i, 0, i2, 0);
b(this.c0, i, 0, i2, 0);
b(this.d0, i, 0, i2, 0);
@@ -977,13 +974,13 @@ public class DialogItemView extends ViewGroup{
b(this.f0, i, 0, i2, 0);
}
private void a(int i, int i2, int i3){
private void a(int i, int i2, int i3) {
b(this.T, i, i3, i2, 0);
b(this.V, i, i3, i2, 0);
b(this.U, i, i3 + g(this.V), i2, 0);
}
private int d(View view){
private int d(View view) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (marginLayoutParams != null) {
@@ -993,7 +990,7 @@ public class DialogItemView extends ViewGroup{
return 0;
}
private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6){
private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6) {
ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams(i, i2);
marginLayoutParams.leftMargin = i3;
marginLayoutParams.topMargin = i4;
@@ -1002,23 +999,23 @@ public class DialogItemView extends ViewGroup{
return marginLayoutParams;
}
public int a(int i){
public int a(int i) {
return (int) (i * (this.a.densityDpi / 160.0f));
}
public int a(float f2){
public int a(float f2) {
return (int) (f2 * (this.a.densityDpi / 160.0f));
}
private void a(View view, int i, int i2, int i3, int i4){
private void a(View view, int i, int i2, int i3, int i4) {
view.measure(ViewGroup.getChildMeasureSpec(i, i2 + b(view) + c(view), view.getLayoutParams().width), ViewGroup.getChildMeasureSpec(i3, i4 + d(view) + a(view), view.getLayoutParams().height));
}
private void a(View view, int i, int i2){
private void a(View view, int i, int i2) {
view.layout(i, i2, view.getMeasuredWidth() + i, view.getMeasuredHeight() + i2);
}
private int a(View view){
private int a(View view) {
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (marginLayoutParams != null) {
@@ -0,0 +1,71 @@
package com.vk.medianative;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.os.Build;
import android.util.Log;
import ru.vtosters.hooks.other.Preferences;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class MediaImageEncoder {
private static boolean compressBitmap(Bitmap bitmap, File file, CompressFormat format, int quality) {
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
return bitmap.compress(format, quality, fileOutputStream);
} catch (IOException e) {
Log.e(MediaImageEncoder.class.getSimpleName(), "Bitmap compression error: " + e);
return false;
}
}
private static boolean compressBitmapJpeg(Bitmap bitmap, File file, int quality) {
return compressBitmap(bitmap, file, CompressFormat.JPEG, quality);
}
public static boolean needToCompress() {
return Preferences.getPreferences().getBoolean("compressPhotos", true);
}
private static boolean compressBitmapNative(Bitmap bitmap, File file, int quality) {
if (MediaNative.isX86() || MediaNative.isAsus() || Build.VERSION.SDK_INT < 24) {
Log.e(MediaImageEncoder.class.getSimpleName(), "JPEG turbo not supported on this device!");
return compressBitmapJpeg(bitmap, file, quality);
}
byte[] jpegData = MediaNative.compressBitmapJpeg(bitmap, quality);
if (jpegData == null) {
return compressBitmapJpeg(bitmap, file, quality);
}
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
fileOutputStream.write(jpegData);
return true;
} catch (IOException e) {
Log.e(MediaImageEncoder.class.getSimpleName(), "JPEG compression error: " + e);
return false;
}
}
public static boolean encodePicture(Bitmap bitmap, File file, int quality) {
if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) {
return compressBitmapNative(bitmap, file, quality);
} else {
return false;
}
}
public static boolean encodeJpeg(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) {
return encodePicture(bitmap, file, 100);
}
public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) {
return encodePicture(bitmap, file, Preferences.compress(85));
}
}
@@ -0,0 +1,501 @@
package com.vk.medianative;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Process;
import android.os.*;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.facebook.soloader.SoLoader;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Scanner;
public final class MediaNative {
private static final String a = NativeMediaEncoder.class.getSimpleName();
private static final Object f17443d = new Object();
@SuppressLint("StaticFieldLeak")
@Keep
public static volatile Context context;
private static boolean isMediaLoaded = false;
private static boolean isMediaMasksLoaded = false;
public static int HSLToIntRGB(float f2, float f3, float f4) {
int[] iArr = new int[3];
waitForContext();
nativeHSLToRGB(f2, f3, f4, iArr);
return Color.argb(255, iArr[0], iArr[1], iArr[2]);
}
public static void HSLToRGB(float f2, float f3, float f4, int[] iArr) {
waitForContext();
nativeHSLToRGB(f2, f3, f4, iArr);
}
public static int HSVToIntRGB(float f2, float f3, float f4) {
int[] iArr = new int[3];
waitForContext();
nativeHSVToRGB(f2, f3, f4, iArr);
return Color.argb(255, iArr[0], iArr[1], iArr[2]);
}
public static void HSVToRGB(float f2, float f3, float f4, int[] iArr) {
waitForContext();
nativeHSVToRGB(f2, f3, f4, iArr);
}
public static int LabToIntRGB(float f2, float f3, float f4) {
int[] iArr = new int[3];
waitForContext();
nativeLabToRGB(f2, f3, f4, iArr);
return Color.argb(255, iArr[0], iArr[1], iArr[2]);
}
public static void LabToRGB(float f2, float f3, float f4, int[] iArr) {
waitForContext();
nativeLabToRGB(f2, f3, f4, iArr);
}
public static void RGBToHSL(int i, int i2, int i3, float[] fArr) {
waitForContext();
nativeRGBToHSL(i, i2, i3, fArr);
}
public static void RGBToHSV(int i, int i2, int i3, float[] fArr) {
waitForContext();
nativeRGBToHSV(i, i2, i3, fArr);
}
public static void RGBToLab(int i, int i2, int i3, float[] fArr) {
waitForContext();
nativeRGBToLab(i, i2, i3, fArr);
}
public static long animationPlayerCreate(String str, int i, int i2, boolean z) {
waitForContext();
return nativeAnimationPlayerCreate(str, i, i2, z);
}
public static int animationPlayerDecode(long j, Bitmap bitmap) {
waitForContext();
return nativeAnimationPlayerDecode(j, bitmap);
}
public static int animationPlayerGetSize(long j) {
waitForContext();
return nativeAnimationPlayerGetSize(j);
}
public static void animationPlayerRelease(long j) {
waitForContext();
nativeAnimationPlayerRelease(j);
}
public static boolean animationPlayerSeek(long j, int i) {
waitForContext();
return nativeAnimationPlayerSeek(j, i);
}
public static void applyCurveAndSaturationBitmap(Bitmap bitmap, int[] iArr, float f2) {
waitForContext();
nativeCurveSaturation(bitmap, iArr, f2, Runtime.getRuntime().availableProcessors());
}
public static long audioGetTotalPcmDuration() {
waitForContext();
return nativeAudioGetTotalPcmDuration();
}
public static byte[] audioGetWaveform(short[] sArr, int i) {
waitForContext();
return nativeAudioGetWaveform(sArr, i);
}
public static int audioIsOpusFile(String str) {
waitForContext();
return nativeAudioIsOpusFile(str);
}
public static void audioReadOpusFile(ByteBuffer byteBuffer, int i, int[] iArr) {
waitForContext();
nativeAudioReadOpusFile(byteBuffer, i, iArr);
}
public static int audioSeekOpusFile(float f2) {
waitForContext();
return nativeAudioSeekOpusFile(f2);
}
public static int audioStartRecord(String str) { // need to hack
waitForContext();
return nativeAudioStartRecord(str);
}
public static void audioStopRecord() { // need to hack
waitForContext();
nativeAudioStopRecord();
}
public static int audioWriteFrame(ByteBuffer byteBuffer, int i) { // need to hack
waitForContext();
return nativeAudioWriteFrame(byteBuffer, i);
}
private static void waitForContext() {
synchronized (f17443d) {
if (context == null) {
try {
f17443d.wait();
} catch (InterruptedException e2) {
Log.e(a, "Interrupted while waiting for context", e2);
}
}
}
}
public static void blurBitmap(Bitmap bitmap, int i) {
waitForContext();
nativeBlur(bitmap, i);
}
private static String c() {
return getProcMaps(String.valueOf(Process.myPid()));
}
public static void cameraProcessorCreate() {
waitForContext();
nativeProcessorCreate();
}
public static boolean cameraProcessorDo(byte[] bArr, long[] jArr) {
waitForContext();
return nativeProcessorDo(bArr, jArr);
}
public static int cameraProcessorGetVersion() {
waitForContext();
return nativeProcessorGetVersion();
}
public static boolean cameraProcessorInit(String str, int i, int i2) {
waitForContext();
return nativeProcessorInit(str, i, i2);
}
public static boolean cameraProcessorLoad(String str, boolean z) {
waitForContext();
return nativeProcessorLoad(str, z);
}
public static void cameraProcessorMouseTap(int i, float f2, float f3) {
waitForContext();
nativeProcessorMouseTap(i, f2, f3);
}
public static void cameraProcessorRelease() {
waitForContext();
nativeProcessorRelease();
}
public static void colorCorrectionBitmap(Bitmap bitmap, Bitmap bitmap2, float[] fArr) {
waitForContext();
nativeColorCorrection(bitmap, bitmap2, fArr, fArr.length, Runtime.getRuntime().availableProcessors());
}
public static byte[] compressBitmapJpeg(Bitmap bitmap, int i) {
waitForContext();
return nativeJpegTurboCompress(bitmap, i);
}
public static String createAllInOneShader() {
waitForContext();
return nativeAllInOneShader();
}
public static void createCurve(int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4, int[] iArr5, float f2, float f3, float f4, float f5, float f6) {
waitForContext();
nativeCreateCurve(iArr, iArr2, iArr3, iArr4, iArr5, f2, f3, f4, f5, f6);
}
public static String createOneInAllShader() {
waitForContext();
return nativeOneInAllShader();
}
private static String d() {
return getProcMaps("self");
}
public static String dump() {
return "LD_LIBRARY_PATH:\n" + SoLoader.makeLdLibraryPath() + "\nSELF_MAPS:\n" + d() + "\nPID_MAPS:\n" + c() + "\n";
}
private static void e() {
isMediaMasksLoaded = NativeLibLoader.loadLibrary("vkmediamasks");
}
public static void enhanceBitmap(Bitmap bitmap, float f2) {
waitForContext();
nativeEnhance(bitmap, f2, Runtime.getRuntime().availableProcessors());
}
private static void loadVKMediaLibs() {
if (isMediaLoaded) {
return;
}
isMediaLoaded = NativeLibLoader.loadLibrary("vkmedia");
isMediaLoaded = NativeLibLoader.loadLibrary("vkmediaencoder");
}
public static void flipHorizontallyBitmap(Bitmap bitmap) {
waitForContext();
nativeFlipHorizontally(bitmap);
}
public static void flipVerticallyBitmap(Bitmap bitmap) {
waitForContext();
nativeFlipVertically(bitmap);
}
public static void generateHistogram(Bitmap bitmap, int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4) {
waitForContext();
nativeHistogram(bitmap, iArr, iArr2, iArr3, iArr4);
}
public static void init(Context context2) {
synchronized (f17443d) {
context = context2;
SoLoader.init(context2, false);
NativeLibLoader.loadLibrary("gnustl_shared");
NativeLibLoader.loadLibrary("vkchronicle");
loadVKMediaLibs();
f17443d.notifyAll();
}
}
public static void intRGBToHSL(int i, float[] fArr) {
waitForContext();
nativeRGBToHSL(Color.red(i), Color.green(i), Color.blue(i), fArr);
}
public static void intRGBToHSV(int i, float[] fArr) {
waitForContext();
nativeRGBToHSV(Color.red(i), Color.green(i), Color.blue(i), fArr);
}
public static void intRGBToLab(int i, float[] fArr) {
waitForContext();
nativeRGBToLab(Color.red(i), Color.green(i), Color.blue(i), fArr);
}
public static boolean isAsus() {
return "asus".equalsIgnoreCase(Build.MANUFACTURER);
}
public static boolean isMediaSupported() {
if (!isMediaMasksLoaded) {
e();
}
return isMediaMasksLoaded;
}
public static boolean isX86() {
try {
String str = Build.SUPPORTED_ABIS[0];
if (str != null) {
return str.startsWith("x86");
}
return false;
} catch (Throwable unused) {
return false;
}
}
public static void lookupBitmap(Bitmap bitmap, Bitmap bitmap2) {
waitForContext();
nativeLookup(bitmap, bitmap2, Runtime.getRuntime().availableProcessors());
}
public static long mediaEncoderCreate(MediaEncoderSettings mediaEncoderSettings) {
waitForContext();
return nativeCreateEncoder(mediaEncoderSettings);
}
public static int mediaEncoderDoEncode(long j) {
waitForContext();
return nativeDoEncode(j);
}
public static void mediaEncoderDoRelease(long j) {
waitForContext();
nativeReleaseEncoder(j);
}
private static native String nativeAllInOneShader();
private static native long nativeAnimationPlayerCreate(String str, int i, int i2, boolean z);
private static native int nativeAnimationPlayerDecode(long j, Bitmap bitmap);
private static native int nativeAnimationPlayerGetSize(long j);
private static native void nativeAnimationPlayerRelease(long j);
private static native boolean nativeAnimationPlayerSeek(long j, int i);
private static native long nativeAudioGetTotalPcmDuration();
private static native byte[] nativeAudioGetWaveform(short[] sArr, int i);
private static native int nativeAudioIsOpusFile(String str);
private static native int nativeAudioOpenOpusFile(String str);
private static native void nativeAudioReadOpusFile(ByteBuffer byteBuffer, int i, int[] iArr);
private static native int nativeAudioSeekOpusFile(float f2);
private static native int nativeAudioStartRecord(String str);
private static native void nativeAudioStopRecord();
private static native int nativeAudioWriteFrame(ByteBuffer byteBuffer, int i);
private static native void nativeBlur(Bitmap bitmap, int i);
private static native void nativeColorCorrection(Bitmap bitmap, Bitmap bitmap2, float[] fArr, int i, int i2);
private static native String nativeCreateColorCorrectionShader(float[] fArr, int i, int i2);
private static native void nativeCreateCurve(int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4, int[] iArr5, float f2, float f3, float f4, float f5, float f6);
private static native long nativeCreateEncoder(MediaEncoderSettings mediaEncoderSettings);
private static native void nativeCurveSaturation(Bitmap bitmap, int[] iArr, double d2, int i);
private static native int nativeDoEncode(long j);
private static native void nativeEnhance(Bitmap bitmap, double d2, int i);
private static native void nativeFlipHorizontally(Bitmap bitmap);
private static native void nativeFlipVertically(Bitmap bitmap);
private static native void nativeHSLToRGB(float f2, float f3, float f4, int[] iArr);
private static native void nativeHSVToRGB(float f2, float f3, float f4, int[] iArr);
private static native void nativeHistogram(Bitmap bitmap, int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4);
private static native byte[] nativeJpegTurboCompress(Bitmap bitmap, int i);
private static native void nativeLabToRGB(float f2, float f3, float f4, int[] iArr);
private static native void nativeLookup(Bitmap bitmap, Bitmap bitmap2, int i);
private static native String nativeOneInAllShader();
private static native void nativePinBitmap(Bitmap bitmap);
private static native void nativeProcessorCreate();
private static native boolean nativeProcessorDo(byte[] bArr, long[] jArr);
private static native int nativeProcessorGetVersion();
private static native boolean nativeProcessorInit(String str, int i, int i2);
private static native boolean nativeProcessorLoad(String str, boolean z);
private static native void nativeProcessorMouseTap(int i, float f2, float f3);
private static native void nativeProcessorRelease();
private static native void nativeRGBToHSL(int i, int i2, int i3, float[] fArr);
private static native void nativeRGBToHSV(int i, int i2, int i3, float[] fArr);
private static native void nativeRGBToLab(int i, int i2, int i3, float[] fArr);
private static native void nativeReleaseEncoder(long j);
private static native void nativeResize(Bitmap bitmap, Bitmap bitmap2, int i);
public static int openOpusFile(String str) {
waitForContext();
return nativeAudioOpenOpusFile(str);
}
public static void pinBitmap(Bitmap bitmap) {
waitForContext();
nativePinBitmap(bitmap);
}
public static void resizeBitmap(Bitmap bitmap, Bitmap bitmap2) {
waitForContext();
nativeResize(bitmap, bitmap2, Runtime.getRuntime().availableProcessors());
}
private static String getProcMaps(String str) {
try (InputStream inputStream = Runtime.getRuntime().exec("cat /proc/" + str + "/maps").getInputStream()) {
return new Scanner(inputStream).useDelimiter("\\A").next();
} catch (Exception e) {
return "empty";
}
}
public static class EncoderHandler extends Handler {
private Callback a;
public EncoderHandler(Looper looper) {
super(looper);
}
@Keep
public static Object postEventFromNative(Object obj, int i, int i2, int i3) {
EncoderHandler encoderHandler;
if (obj != null && (encoderHandler = (EncoderHandler) ((WeakReference<?>) obj).get()) != null) {
if (i == 2) {
return encoderHandler.a.getLayerBitmap(i2, i3);
}
encoderHandler.sendMessage(encoderHandler.obtainMessage(i, i2, i3, null));
}
return null;
}
@Override // android.os.Handler
public void handleMessage(Message message) {
Callback callback = this.a;
if (callback == null) {
return;
}
int i = message.what;
if (i == 0) {
callback.onBytes(message.arg1);
} else if (i != 1) {
String str = MediaNative.a;
Log.e(str, "Unknown message type " + message.what);
} else {
callback.onProgress(message.arg1);
}
}
public void setCallback(Callback callback) {
this.a = callback;
}
public interface Callback {
@Nullable
Bitmap getLayerBitmap(int i, int i2);
void onBytes(int i);
void onProgress(int i);
}
}
}
@@ -11,7 +11,6 @@ import com.vk.api.sdk.exceptions.VKApiExecutionException;
import com.vk.common.k.PodcastTimeCodeClickListener;
import com.vk.common.links.LinkParser;
import com.vk.core.util.ContextExtKt;
import com.vk.dto.music.Artist;
import com.vk.dto.music.MusicTrack;
import com.vk.music.artists.chooser.MusicArtistSelector;
import com.vk.music.common.MusicPlaybackLaunchContext;
@@ -24,6 +23,8 @@ import com.vtosters.lite.R;
import com.vtosters.lite.ViewUtils;
import com.vtosters.lite.ui.MusicErrorViewHelper;
import com.vtosters.lite.ui.holder.RecyclerHolder;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.Genius;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -155,6 +156,14 @@ public class LyricsHolder extends RecyclerHolder<PlayerInfo> implements View.OnC
@Override // com.vk.api.base.ApiCallback
public void a(VKApiExecutionException vKApiExecutionException) {
if (Preferences.getBoolValue("useGenius", false) && !Preferences.serverFeaturesDisable()) {
var text = Genius.getTextMusic(a);
var lyrics = new AudioGetLyrics.a();
lyrics.a = text;
a(lyrics);
return;
}
LyricsHolder.this.B.set(false);
LyricsHolder.this.f18328f.a(vKApiExecutionException);
LyricsHolder.this.f18328f.b(0);
@@ -0,0 +1,106 @@
package com.vk.newsfeed.holders;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.dto.newsfeed.entries.NewsEntry;
import com.vk.dto.newsfeed.entries.Post;
import com.vk.dto.newsfeed.entries.PromoPost;
import com.vk.extensions.ViewExtKt;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.newsfeed.NewsFeedFiltersUtils;
import ru.vtosters.sponsorpost.internal.Native;
import ru.vtosters.sponsorpost.internal.VotesPreferences;
import ru.vtosters.sponsorpost.internal.VotesService;
import ru.vtosters.sponsorpost.utils.PostsPreferences;
public final class AdMarkHolder extends BaseNewsEntryHolder<NewsEntry> {
private final TextView F;
@SuppressLint("ResourceType")
public AdMarkHolder(ViewGroup viewGroup) {
super(R.layout.marked_as_ads, viewGroup);
View view = ((RecyclerView.ViewHolder) this).itemView;
this.F = ViewExtKt.a(view, R.id.text, (View.OnClickListener) null);
}
@SuppressLint("ResourceType")
public void b(NewsEntry newsEntry) { // onBind
String disclamer = null;
Drawable drawable = null;
int height = 0;
if (newsEntry instanceof Post) {
Post post = ((Post) newsEntry);
if (post.L1()) {
drawable = VKThemeHelper.a(R.drawable.marked_as_ads, R.attr.icon_tertiary);
int ownerId = post.b();
int postId = post.P1();
int date = post.K();
String text = post.getText();
if (PostsPreferences.isPostAd(ownerId, postId)) {
disclamer = "SponsorPost: Реклама";
if (Preferences.isValidSignature() && Native.canVote()) {
this.F.setOnClickListener(listener -> voteDialog(ownerId, postId, date, this.F.getContext()));
}
} else if (VotesPreferences.isPostAd(ownerId, postId)) {
disclamer = "SponsorPost: Возможно реклама";
if (Preferences.isValidSignature() && Native.canVote()) {
this.F.setOnClickListener(listener -> voteDialog(ownerId, postId, date, this.F.getContext()));
}
} else if (NewsFeedFiltersUtils.sponsorFilters(text)) {
disclamer = "SponsorPost: Заблокировано фильтрами";
this.F.setOnClickListener(listener -> AndroidUtils.sendToast("Заблокировано по выражению: " + NewsFeedFiltersUtils.sponsorFiltersBanWord(text)));
} else {
disclamer = m(R.string.sponsored_post_in_group);
}
height = e0().getDimensionPixelSize(R.dimen.newsfeed_ad_mark_post_height);
}
} else if (newsEntry instanceof PromoPost) {
PromoPost promoPost = (PromoPost) newsEntry;
if (!promoPost.E1().isEmpty()) {
height = e0().getDimensionPixelSize(R.dimen.newsfeed_ad_mark_promo_post_height);
disclamer = promoPost.E1();
}
}
this.F.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, null, null, null);
this.F.setMinHeight(height);
this.F.setText(disclamer);
}
public static void voteDialog(int ownerId, int postId, int date, Context ctx) {
new VkAlertDialog.Builder(ctx)
.setTitle("SponsorPost")
.setMessage("Этот пост является рекламным?")
.setPositiveButton("Да", ((dialog, which) -> {
int resp = VotesService.ratePost(ownerId, postId, date, true).optInt("code");
if (resp == 201 || resp == 100) {
AndroidUtils.sendToast("Спасибо за голос!");
} else {
AndroidUtils.sendToast("Ваш голос уже учтен");
}
}))
.setNeutralButton("Нет", ((dialog, which) -> {
int resp = VotesService.ratePost(ownerId, postId, date, false).optInt("code");
if (resp == 201 || resp == 100) {
AndroidUtils.sendToast("Спасибо за голос!");
} else {
AndroidUtils.sendToast("Ваш голос уже учтен");
}
}))
.show();
}
}
@@ -6,7 +6,6 @@ import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.util.AttributeSet;
import com.vk.core.ui.themes.Themable;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.ContextExtKt;
@@ -14,7 +13,7 @@ import com.vk.dto.stories.model.StoriesContainer;
import com.vk.stories.view.BorderedCircleImageView;
import com.vtosters.lite.R;
public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable{
public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable {
private boolean f0;
private int g0;
private int h0;
@@ -23,64 +22,64 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im
private int unread;
private int read;
public CommunityBorderedImageView(Context context){
public CommunityBorderedImageView(Context context) {
this(context, null, 0, 6);
}
public CommunityBorderedImageView(Context context, AttributeSet attributeSet){
public CommunityBorderedImageView(Context context, AttributeSet attributeSet) {
this(context, attributeSet, 0, 4);
}
public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2){
public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2) {
this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i);
}
public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i){
public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
this.g0 = VKThemeHelper.d((int) R.attr.accent);
this.h0 = ContextExtKt.a(context, (int) R.color.gray_200);
this.g0 = VKThemeHelper.d(R.attr.accent);
this.h0 = ContextExtKt.a(context, R.color.gray_200);
this.i0 = new PorterDuffColorFilter(this.g0, PorterDuff.Mode.SRC_IN);
this.j0 = new PorterDuffColorFilter(this.h0, PorterDuff.Mode.SRC_IN);
}
public final void c(int unread, int read){ // get unread and read resid
public void c(int unread, int read) { // get unread and read resid
this.unread = unread;
this.read = read;
this.T = BitmapFactory.decodeResource(getResources(), this.unread);
}
public final int getPrimaryColor(){
public int getPrimaryColor() {
return this.g0;
}
public final void setPrimaryColor(int i){
public void setPrimaryColor(int i) {
this.g0 = i;
this.i0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN);
}
public final int getWasViewedColor(){
public int getWasViewedColor() {
return this.h0;
}
public final void setWasViewedColor(int i){
public void setWasViewedColor(int i) {
this.h0 = i;
this.j0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN);
}
public final void o(){
public void o() {
this.f0 = false;
l();
}
@Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View
public void onDraw(Canvas canvas){
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (this.f0) {
a(canvas);
}
}
public final void setStoryContainer(StoriesContainer storiesContainer){
public void setStoryContainer(StoriesContainer storiesContainer) {
if (storiesContainer.L1()) {
this.f0 = true;
m();
@@ -101,6 +100,6 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im
}
@Override // com.vk.core.ui.themes.Themable
public void v(){
public void v() {
}
}
@@ -1,26 +1,13 @@
package com.vk.sharing.view;
import static b.h.z.f.roboto_medium;
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import androidx.annotation.UiThread;
import androidx.core.content.ContextCompat;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.view.DraweeHolder;
@@ -31,23 +18,26 @@ import com.vk.imageloader.view.VKMultiImageView;
import com.vk.navigation.NavigatorKeys;
import com.vtosters.lite.R;
import com.vtosters.lite.a0;
import ru.vtosters.lite.utils.Preferences;
import ru.vtosters.hooks.other.Preferences;
import java.util.Arrays;
import static b.h.z.f.roboto_medium;
import static ru.vtosters.hooks.other.ThemesUtils.getAccentColor;
@UiThread
public class TargetImageView extends VKMultiImageView {
private static final int I = Screen.a(2);
private final int B;
private final int C;
private final RectF f21629d;
private final Paint f21630e;
private final RectF g;
private final Paint h;
private Drawable D;
private String[] F;
private int G;
private final RectF f21629d;
private final Paint f21630e;
private Canvas f21631f;
private final RectF g;
private final Paint h;
public TargetImageView(Context context, Drawable d) {
this(context, null, 0, 6);
@@ -61,6 +51,43 @@ public class TargetImageView extends VKMultiImageView {
this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i);
}
public TargetImageView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
this.f21629d = new RectF();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
this.f21630e = paint;
this.g = new RectF();
Paint paint2 = new Paint();
paint2.setAntiAlias(true);
paint2.setStrokeWidth(I);
paint2.setStyle(Paint.Style.STROKE);
this.h = paint2;
this.B = getAccentColor();
this.C = this.B;
Drawable c2 = ContextExtKt.c(context, R.drawable.ic_check_circle_composite_24_full);
if (c2 != null) {
this.D = c2;
this.F = new String[4];
int i2 = I * 2;
setPadding(i2, i2, i2, i2);
if (attributeSet != null) {
TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, a0.TargetImageView);
setSelected(obtainStyledAttributes.getBoolean(0, false));
obtainStyledAttributes.recycle();
}
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.user_placeholder);
for (int i3 = 0; i3 <= 3; i3++) {
a();
DraweeHolder<GenericDraweeHierarchy> a3 = this.a.a(i3);
a3.b().e(drawable);
}
setScaleType(ScalingUtils.b.o);
}
}
private void e() {
int i = this.G;
for (int i2 = 0; i2 < i; i2++) {
@@ -261,41 +288,4 @@ public class TargetImageView extends VKMultiImageView {
drawable.setBounds(i - drawable.getIntrinsicWidth(), i2 - this.D.getIntrinsicHeight(), i, i2);
f();
}
public TargetImageView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
this.f21629d = new RectF();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
this.f21630e = paint;
this.g = new RectF();
Paint paint2 = new Paint();
paint2.setAntiAlias(true);
paint2.setStrokeWidth(I);
paint2.setStyle(Paint.Style.STROKE);
this.h = paint2;
this.B = getAccentColor();
this.C = this.B;
Drawable c2 = ContextExtKt.c(context, R.drawable.ic_check_circle_composite_24_full);
if (c2 != null) {
this.D = c2;
this.F = new String[4];
int i2 = I * 2;
setPadding(i2, i2, i2, i2);
if (attributeSet != null) {
TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, a0.TargetImageView);
setSelected(obtainStyledAttributes.getBoolean(0, false));
obtainStyledAttributes.recycle();
}
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.user_placeholder);
for (int i3 = 0; i3 <= 3; i3++) {
a();
DraweeHolder<GenericDraweeHierarchy> a3 = this.a.a(i3);
a3.b().e(drawable);
}
setScaleType(ScalingUtils.b.o);
}
}
}
@@ -104,6 +104,25 @@ public final class StickerDetailsAdapter extends RecyclerView.Adapter<RecyclerVi
}
}
private static final class d extends RecyclerView.ViewHolder {
private final VKStickerImageView mImage;
public d(ViewGroup viewGroup) {
super(new VKStickerImageView(viewGroup.getContext()));
this.mImage = (VKStickerImageView) itemView;
this.mImage.setAspectRatio(1.0f);
this.mImage.setClickable(true);
this.mImage.getHierarchy().a().setVisible(true, true);
this.mImage.getHierarchy().a(0);
this.mImage.setContentDescription(this.mImage.getContext().getString(R.string.stickers_accessibility_sticker));
}
public void a(StickerItem stickerItem) {
this.mImage.a(stickerItem.h(Screen.a(80)));
this.mImage.setTag(R.id.id, stickerItem.getId());
}
}
private final class c extends RecyclerView.ViewHolder {
private final VKImageView mImageBg;
private final VKImageView mPackImage;
@@ -129,43 +148,24 @@ public final class StickerDetailsAdapter extends RecyclerView.Adapter<RecyclerVi
this.mImageBg.getHierarchy().a().setVisible(true, true);
this.mImageBg.getHierarchy().a(0);
this.mImageBg.getHierarchy().e(new ColorDrawable(VKThemeHelper.d(R.attr.placeholder_icon_background)));
this.mPackImage.a(stickerStockItem.h(Screen.a(68)));
this.mPackImage.getHierarchy().a().setVisible(true, true);
this.mPackImage.getHierarchy().a(0);
if (stickerStockItem.E1()) {
ViewExtKt.r(this.mAnimPack);
} else {
ViewExtKt.p(this.mAnimPack);
}
this.mPackTitle.setText(stickerStockItem.getTitle());
this.mPackAuthor.setText(stickerStockItem.v1());
ViewExtKt.p(this.mPackSubtitle);
this.mDescription.setText(stickerStockItem.C1());
}
}
private static final class d extends RecyclerView.ViewHolder {
private final VKStickerImageView mImage;
public d(ViewGroup viewGroup) {
super(new VKStickerImageView(viewGroup.getContext()));
this.mImage = (VKStickerImageView) itemView;
this.mImage.setAspectRatio(1.0f);
this.mImage.setClickable(true);
this.mImage.getHierarchy().a().setVisible(true, true);
this.mImage.getHierarchy().a(0);
this.mImage.setContentDescription(this.mImage.getContext().getString(R.string.stickers_accessibility_sticker));
}
public void a(StickerItem stickerItem) {
this.mImage.a(stickerItem.h(Screen.a(80)));
this.mImage.setTag(R.id.id, stickerItem.getId());
}
}
}
@@ -11,11 +11,9 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.airbnb.lottie.LottieComposition;
import com.airbnb.lottie.LottieCompositionFactory;
import com.airbnb.lottie.LottieDrawable;
@@ -47,7 +45,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
public class StoryCircleImageView extends BorderedCircleImageView implements Themable{
public class StoryCircleImageView extends BorderedCircleImageView implements Themable {
private static final HashSet<Integer> B0 = new HashSet<>();
private final PipelineDraweeControllerBuilder f0;
private int A0;
@@ -72,35 +70,35 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
private String y0;
private LottieDrawable z0;
public StoryCircleImageView(Context context){
public StoryCircleImageView(Context context) {
this(context, null);
}
public StoryCircleImageView(Context context, AttributeSet attributeSet){
public StoryCircleImageView(Context context, AttributeSet attributeSet) {
this(context, attributeSet, 0);
}
public StoryCircleImageView(Context context, AttributeSet attributeSet, int i){
public StoryCircleImageView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
this.f0 = FrescoWrapper.c.d();
a(context, attributeSet, i);
}
private void b(Canvas canvas){
private void b(Canvas canvas) {
this.z0.draw(canvas);
}
private boolean o(){
private boolean o() {
return !this.u0 && !this.t0 && this.z0 == null && !this.w0 && !this.v0;
}
private void p(){
private void p() {
this.z0.stop();
this.z0 = null;
invalidate();
}
private void q(){
private void q() {
int i = this.j0;
if (i != 0 && VKThemeHelper.k.a(i)) {
this.k0 = VKThemeHelper.d(this.j0);
@@ -132,19 +130,19 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
this.s0 = DrawableUtils.a(a4, i8, i8);
}
private void r(){
private void r() {
this.c0 = Screen.a(o() ? 0 : 2);
m();
q();
}
private void setUploadFailed(boolean z){
private void setUploadFailed(boolean z) {
this.u0 = z;
invalidate();
}
@Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View
public void onDraw(Canvas canvas){
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
setBorderAlpha(255);
if (this.u0) {
@@ -170,13 +168,13 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
@Override // com.vk.stories.view.BorderedCircleImageView, android.view.View
public void onSizeChanged(int i, int i2, int i3, int i4){
public void onSizeChanged(int i, int i2, int i3, int i4) {
super.onSizeChanged(i, i2, i3, i4);
this.i0.setBounds(0, 0, i, i2);
b(i);
}
public void setStoryContainer(@Nullable StoriesContainer storiesContainer){
public void setStoryContainer(@Nullable StoriesContainer storiesContainer) {
boolean z = true;
boolean z2 = storiesContainer != null && storiesContainer.Q1();
this.v0 = storiesContainer != null && storiesContainer.S1() && !storiesContainer.L1();
@@ -246,12 +244,12 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
@Override // com.vk.core.ui.themes.Themable
public void v(){
public void v() {
r();
invalidate();
}
private void b(int i){
private void b(int i) {
LottieDrawable lottieDrawable = this.z0;
if (lottieDrawable == null || lottieDrawable.e() == null) {
return;
@@ -259,7 +257,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
this.z0.d(i / this.z0.e().a().width());
}
protected void a(Context context, AttributeSet attributeSet, int i){
protected void a(Context context, AttributeSet attributeSet, int i) {
TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, com.vtosters.lite.a0.StoryCircleImageView, i, 0);
a(context, attributeSet, obtainStyledAttributes);
obtainStyledAttributes.recycle();
@@ -267,7 +265,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
this.T = this.p0;
}
public void b(@Nullable List<String> list){
public void b(@Nullable List<String> list) {
PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder = this.f0;
pipelineDraweeControllerBuilder.j();
pipelineDraweeControllerBuilder.a(getController());
@@ -277,7 +275,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
@SuppressLint("ResourceType")
private void a(Context context, AttributeSet attributeSet, TypedArray typedArray){ // bullshit vk code is not working with custom bordered circle image
private void a(Context context, AttributeSet attributeSet, TypedArray typedArray) { // bullshit vk code is not working with custom bordered circle image
Drawable drawable;
this.A0 = typedArray.getDimensionPixelSize(0, Screen.a(64.0f));
if (typedArray.hasValue(2)) {
@@ -344,13 +342,13 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
this.s0 = DrawableUtils.a(drawable4, i4, i4);
}
public void a(StoriesContainer storiesContainer){
public void a(StoriesContainer storiesContainer) {
if (this.z0 == null) {
LottieCompositionFactory.a(getContext(), this.y0).b(new a(storiesContainer));
}
}
public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition){
public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition) {
this.z0 = new LottieDrawable();
this.z0.a(lottieComposition);
this.z0.d(-1);
@@ -363,7 +361,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
this.z0.stop();
}
private void a(boolean z, boolean z2){
private void a(boolean z, boolean z2) {
k();
this.t0 = z;
if (!z2) {
@@ -373,7 +371,7 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
}
private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List<String> list){
private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List<String> list) {
pipelineDraweeControllerBuilder.b(null);
pipelineDraweeControllerBuilder.a((Supplier<DataSource<CloseableReference<com.facebook.x.g.CloseableImage>>>) null);
if (list != null && !list.isEmpty()) {
@@ -384,13 +382,13 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
return;
}
pipelineDraweeControllerBuilder.a((Supplier<DataSource<CloseableReference<CloseableImage>>>) () -> StoryCircleImageView.this.a(list));
pipelineDraweeControllerBuilder.a(() -> StoryCircleImageView.this.a(list));
return;
}
pipelineDraweeControllerBuilder.b(null);
}
public DataSource<CloseableReference<CloseableImage>> a(@Nullable List<String> list){
public DataSource<CloseableReference<CloseableImage>> a(@Nullable List<String> list) {
ArrayList<Image> arrayList = new ArrayList<>();
for (String o : Objects.requireNonNull(list)) {
arrayList.add(new Image(-1, -1, o));
@@ -398,15 +396,15 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
return AvatarDataSource.a(arrayList, this.A0, 0);
}
public class a implements LottieListener<LottieComposition>{
public class a implements LottieListener<LottieComposition> {
final StoriesContainer a;
a(StoriesContainer storiesContainer){
a(StoriesContainer storiesContainer) {
this.a = storiesContainer;
}
@Override // com.airbnb.lottie.LottieListener
public void a(LottieComposition lottieComposition){
public void a(LottieComposition lottieComposition) {
if (StoryCircleImageView.this.getWidth() > 0) {
StoryCircleImageView.this.a(this.a, lottieComposition);
} else {
@@ -414,15 +412,15 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
}
public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener{
public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener {
final LottieComposition a;
ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition){
ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition) {
this.a = lottieComposition;
}
@Override // android.view.ViewTreeObserver.OnGlobalLayoutListener
public void onGlobalLayout(){
public void onGlobalLayout() {
StoryCircleImageView.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
a aVar = a.this;
StoryCircleImageView.this.a(aVar.a, this.a);
@@ -430,12 +428,12 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The
}
}
public class b implements ValueAnimator.AnimatorUpdateListener{
b(){
public class b implements ValueAnimator.AnimatorUpdateListener {
b() {
}
@Override // android.animation.ValueAnimator.AnimatorUpdateListener
public void onAnimationUpdate(ValueAnimator valueAnimator){
public void onAnimationUpdate(ValueAnimator valueAnimator) {
StoryCircleImageView.this.invalidate();
}
}
+169 -64
View File
@@ -222,8 +222,10 @@ public final class R {
public static final int undel_value = 0x7f030075; // 2130903157
public static final int wallpaper_change_dialog = 0x7f030076; // 2130903158
public static final int accent_select_type = 0x7f030077; // 2130903159
public static final int theme_type_name = 0x7f030078; // 2130903160
public static final int theme_type_name_checkbox = 0x7f030079; // 2130903161
public static final int compression = 0x7f030078; // 2130903160
public static final int compression_value = 0x7f030079; // 2130903161
public static final int theme_type_name = 0x7f03007a; // 2130903162
public static final int theme_type_name_checkbox = 0x7f03007b; // 2130903163
}
public static final class attr {
@@ -4974,28 +4976,29 @@ public final class R {
public static final int ic_deepl_logo_icon = 0x7f080bcb; // 2131233739
public static final int ic_google_translate_logo = 0x7f080bcc; // 2131233740
public static final int ic_yandex_translate_icon = 0x7f080bd1; // 2131233745
public static final int microsoft = 0x7f080bd2; // 2131233746
public static final int $splash__0 = 0x7f080bd2; // 2131233746
public static final int roundmsg = 0x7f080bd3; // 2131233747
public static final int tgs_entry_background = 0x7f080bd4; // 2131233748
public static final int yandex = 0x7f080bd7; // 2131233751
public static final int $splash__0 = 0x7f080bd8; // 2131233752
public static final int $splash__1 = 0x7f080bd9; // 2131233753
public static final int $splash__10 = 0x7f080bda; // 2131233754
public static final int $splash__11 = 0x7f080bdb; // 2131233755
public static final int $splash__12 = 0x7f080bdc; // 2131233756
public static final int $splash__13 = 0x7f080bdd; // 2131233757
public static final int $splash__14 = 0x7f080bde; // 2131233758
public static final int $splash__15 = 0x7f080bdf; // 2131233759
public static final int $splash__2 = 0x7f080be0; // 2131233760
public static final int $splash__3 = 0x7f080be1; // 2131233761
public static final int $splash__4 = 0x7f080be2; // 2131233762
public static final int $splash__5 = 0x7f080be3; // 2131233763
public static final int $splash__6 = 0x7f080be4; // 2131233764
public static final int $splash__7 = 0x7f080be5; // 2131233765
public static final int $splash__8 = 0x7f080be6; // 2131233766
public static final int $splash__9 = 0x7f080be7; // 2131233767
public static final int $splash__1 = 0x7f080bd8; // 2131233752
public static final int $splash__10 = 0x7f080bd9; // 2131233753
public static final int $splash__11 = 0x7f080bda; // 2131233754
public static final int $splash__12 = 0x7f080bdb; // 2131233755
public static final int $splash__13 = 0x7f080bdc; // 2131233756
public static final int $splash__14 = 0x7f080bdd; // 2131233757
public static final int $splash__15 = 0x7f080bde; // 2131233758
public static final int $splash__2 = 0x7f080bdf; // 2131233759
public static final int $splash__3 = 0x7f080be0; // 2131233760
public static final int $splash__4 = 0x7f080be1; // 2131233761
public static final int $splash__5 = 0x7f080be2; // 2131233762
public static final int $splash__6 = 0x7f080be3; // 2131233763
public static final int $splash__7 = 0x7f080be4; // 2131233764
public static final int $splash__8 = 0x7f080be5; // 2131233765
public static final int $splash__9 = 0x7f080be6; // 2131233766
public static final int checker_background = 0x7f080be8; // 2131233768
public static final int splash = 0x7f080be9; // 2131233769
public static final int custom_thumb_selector = 0x7f080be9; // 2131233769
public static final int custom_track_selector = 0x7f080bea; // 2131233770
public static final int splash = 0x7f080bec; // 2131233772
public static final int abc_control_background_material = 0x7f080032; // 2131230770
public static final int ic_action_close = 0x7f080322; // 2131231522
public static final int ic_camera_outline_28_new = 0x7f0803a0; // 2131231648
@@ -6793,6 +6796,9 @@ public final class R {
public static final int ic_telegram_24 = 0x7f080bcf; // 2131233743
public static final int ic_telegram_outline_28 = 0x7f080bd0; // 2131233744
public static final int vtlogo = 0x7f080bd6; // 2131233750
public static final int bing = 0x7f080be7; // 2131233767
public static final int sauceNAO = 0x7f080beb; // 2131233771
public static final int tracemoe = 0x7f080bed; // 2131233773
public static final int bg_music_play_button_white = 0x7f08015e; // 2131231070
public static final int bg_poll_bg_thumb = 0x7f080181; // 2131231105
public static final int bg_stickers_suggestions_left = 0x7f0801ad; // 2131231149
@@ -11054,6 +11060,8 @@ public final class R {
public static final int color_name = 0x7f0a1022; // 2131365922
public static final int color_value = 0x7f0a1023; // 2131365923
public static final int color_preview_border = 0x7f0a1024; // 2131365924
public static final int account_backup_view = 0x7f0a1025; // 2131365925
public static final int copy_image = 0x7f0a1026; // 2131365926
}
public static final class integer {
@@ -12993,7 +13001,31 @@ public final class R {
public static final int vt_changeable_foreground = 0x7f0f0044; // 2131689540
public static final int vt_launcher = 0x7f0f0045; // 2131689541
public static final int vt_party_foreground = 0x7f0f0047; // 2131689543
public static final int vt_adaptive_launcher_foneground = 0x7f0f0043; // 2131689539
public static final int ic_launcher_balloon_pink = 0x7f0f0048; // 2131689544
public static final int ic_launcher_balloon_pink_background = 0x7f0f0049; // 2131689545
public static final int ic_launcher_balloon_pink_foreground = 0x7f0f004a; // 2131689546
public static final int ic_launcher_bubble_gum = 0x7f0f004b; // 2131689547
public static final int ic_launcher_bubble_gum_background = 0x7f0f004c; // 2131689548
public static final int ic_launcher_bubble_gum_foreground = 0x7f0f004d; // 2131689549
public static final int ic_launcher_comics = 0x7f0f004e; // 2131689550
public static final int ic_launcher_comics_background = 0x7f0f004f; // 2131689551
public static final int ic_launcher_comics_foreground = 0x7f0f0050; // 2131689552
public static final int ic_launcher_flowers = 0x7f0f0051; // 2131689553
public static final int ic_launcher_flowers_background = 0x7f0f0052; // 2131689554
public static final int ic_launcher_flowers_foreground = 0x7f0f0053; // 2131689555
public static final int ic_launcher_metal_dark = 0x7f0f0054; // 2131689556
public static final int ic_launcher_metal_dark_background = 0x7f0f0055; // 2131689557
public static final int ic_launcher_metal_dark_foreground = 0x7f0f0056; // 2131689558
public static final int ic_launcher_pin_blue = 0x7f0f0057; // 2131689559
public static final int ic_launcher_pin_blue_background = 0x7f0f0058; // 2131689560
public static final int ic_launcher_pin_blue_foreground = 0x7f0f0059; // 2131689561
public static final int ic_launcher_rabbit = 0x7f0f005a; // 2131689562
public static final int ic_launcher_rabbit_background = 0x7f0f005b; // 2131689563
public static final int ic_launcher_rabbit_foreground = 0x7f0f005c; // 2131689564
public static final int ic_launcher_shine = 0x7f0f005d; // 2131689565
public static final int ic_launcher_shine_background = 0x7f0f005e; // 2131689566
public static final int ic_launcher_shine_foreground = 0x7f0f005f; // 2131689567
public static final int vt_adaptive_launcher_foreground = 0x7f0f0043; // 2131689539
public static final int vt_launcher_round = 0x7f0f0046; // 2131689542
public static final int ic_logo_facebook_28 = 0x7f0f0003; // 2131689475
public static final int ic_logo_google_28 = 0x7f0f0004; // 2131689476
@@ -19547,49 +19579,121 @@ public final class R {
public static final int accent_color_summ = 0x7f12183b; // 2131892283
public static final int accent_color_title = 0x7f12183c; // 2131892284
public static final int accents = 0x7f12183d; // 2131892285
public static final int add_to_ads_stories_whitelist = 0x7f12183e; // 2131892286
public static final int amoledtheme_summ = 0x7f12183f; // 2131892287
public static final int amoledtheme_title = 0x7f121840; // 2131892288
public static final int app_open_by_default_settings = 0x7f121841; // 2131892289
public static final int audio_deleted_from_cache = 0x7f121842; // 2131892290
public static final int blockminiapps_summ = 0x7f121843; // 2131892291
public static final int blockminiapps_title = 0x7f121844; // 2131892292
public static final int businessDisabler_title = 0x7f121845; // 2131892293
public static final int by_choice = 0x7f121846; // 2131892294
public static final int calls_not_available = 0x7f121847; // 2131892295
public static final int cannot_write = 0x7f121848; // 2131892296
public static final int change_accent_color = 0x7f121849; // 2131892297
public static final int copydebuginfo_summ = 0x7f12184a; // 2131892298
public static final int copydebuginfo_title = 0x7f12184b; // 2131892299
public static final int copyownlink_summ = 0x7f12184c; // 2131892300
public static final int copyownlink_title = 0x7f12184d; // 2131892301
public static final int crash_service_name = 0x7f12184e; // 2131892302
public static final int current_theme = 0x7f12184f; // 2131892303
public static final int custom_links_warning = 0x7f121850; // 2131892304
public static final int data_updated = 0x7f121851; // 2131892305
public static final int device_info = 0x7f121852; // 2131892306
public static final int device_info_copied = 0x7f121853; // 2131892307
public static final int dialogstats = 0x7f121854; // 2131892308
public static final int downloading_update = 0x7f121855; // 2131892309
public static final int invalid_pack_link = 0x7f121856; // 2131892310
public static final int maxquality_summ = 0x7f121857; // 2131892311
public static final int maxquality_title = 0x7f121858; // 2131892312
public static final int monettheme_summ = 0x7f121859; // 2131892313
public static final int monettheme_title = 0x7f12185a; // 2131892314
public static final int newsfeed_notif_summ = 0x7f12185b; // 2131892315
public static final int newsfeed_notif_title = 0x7f12185c; // 2131892316
public static final int open_bot = 0x7f12185d; // 2131892317
public static final int promotedstickers_title = 0x7f12185e; // 2131892318
public static final int remove_from_ads_stories_whitelist = 0x7f12185f; // 2131892319
public static final int select_color = 0x7f121860; // 2131892320
public static final int select_palette = 0x7f121861; // 2131892321
public static final int sources = 0x7f121862; // 2131892322
public static final int updateverifdata_summ = 0x7f121863; // 2131892323
public static final int updateverifdata_title = 0x7f121864; // 2131892324
public static final int useOldAppVer_summ = 0x7f121865; // 2131892325
public static final int useOldAppVer_title = 0x7f121866; // 2131892326
public static final int vkx_integration_enabled_info = 0x7f121867; // 2131892327
public static final int writebar_ios_title = 0x7f121868; // 2131892328
public static final int accounts_saved_by_path = 0x7f12183e; // 2131892286
public static final int add_to_ads_stories_whitelist = 0x7f12183f; // 2131892287
public static final int amoledtheme_summ = 0x7f121840; // 2131892288
public static final int amoledtheme_title = 0x7f121841; // 2131892289
public static final int appLanguage = 0x7f121842; // 2131892290
public static final int app_open_by_default_settings = 0x7f121843; // 2131892291
public static final int applying_accent = 0x7f121844; // 2131892292
public static final int audio_deleted_from_cache = 0x7f121845; // 2131892293
public static final int blockminiapps_summ = 0x7f121846; // 2131892294
public static final int blockminiapps_title = 0x7f121847; // 2131892295
public static final int businessDisabler_title = 0x7f121848; // 2131892296
public static final int by_choice = 0x7f121849; // 2131892297
public static final int calls_not_available = 0x7f12184a; // 2131892298
public static final int cannot_write = 0x7f12184b; // 2131892299
public static final int change_accent_color = 0x7f12184c; // 2131892300
public static final int compression_types = 0x7f12184d; // 2131892301
public static final int compression_types_selection = 0x7f12184e; // 2131892302
public static final int copydebuginfo_summ = 0x7f12184f; // 2131892303
public static final int copydebuginfo_title = 0x7f121850; // 2131892304
public static final int copyownlink_summ = 0x7f121851; // 2131892305
public static final int copyownlink_title = 0x7f121852; // 2131892306
public static final int crash_service_name = 0x7f121853; // 2131892307
public static final int currentLanguage = 0x7f121854; // 2131892308
public static final int current_theme = 0x7f121855; // 2131892309
public static final int custom_links_warning = 0x7f121856; // 2131892310
public static final int data = 0x7f121857; // 2131892311
public static final int data_updated = 0x7f121858; // 2131892312
public static final int datasettings_title = 0x7f121859; // 2131892313
public static final int device_info = 0x7f12185a; // 2131892314
public static final int device_info_copied = 0x7f12185b; // 2131892315
public static final int dialogstats = 0x7f12185c; // 2131892316
public static final int disable_analytics = 0x7f12185d; // 2131892317
public static final int disable_analytics_summ = 0x7f12185e; // 2131892318
public static final int downloading_update = 0x7f12185f; // 2131892319
public static final int error_applying_accent = 0x7f121860; // 2131892320
public static final int error_no_text = 0x7f121861; // 2131892321
public static final int error_saving_file = 0x7f121862; // 2131892322
public static final int failed_to_get_text = 0x7f121863; // 2131892323
public static final int invalid_pack_link = 0x7f121864; // 2131892324
public static final int invalidate_theme_cache_title = 0x7f121865; // 2131892325
public static final int logColors_title = 0x7f121866; // 2131892326
public static final int maxquality_summ = 0x7f121867; // 2131892327
public static final int maxquality_title = 0x7f121868; // 2131892328
public static final int microgsett = 0x7f121869; // 2131892329
public static final int monettheme_summ = 0x7f12186a; // 2131892330
public static final int monettheme_title = 0x7f12186b; // 2131892331
public static final int newsfeed_notif_summ = 0x7f12186c; // 2131892332
public static final int newsfeed_notif_title = 0x7f12186d; // 2131892333
public static final int open_bot = 0x7f12186e; // 2131892334
public static final int promotedstickers_title = 0x7f12186f; // 2131892335
public static final int remove_encryption_key = 0x7f121870; // 2131892336
public static final int remove_from_ads_stories_whitelist = 0x7f121871; // 2131892337
public static final int reset_accounts = 0x7f121872; // 2131892338
public static final int reset_all_settings = 0x7f121873; // 2131892339
public static final int restore_accounts = 0x7f121874; // 2131892340
public static final int restore_settings = 0x7f121875; // 2131892341
public static final int save_accounts = 0x7f121876; // 2131892342
public static final int save_online_data = 0x7f121877; // 2131892343
public static final int select_color = 0x7f121878; // 2131892344
public static final int select_palette = 0x7f121879; // 2131892345
public static final int sources = 0x7f12187a; // 2131892346
public static final int storiesGroupsAdBlock = 0x7f12187b; // 2131892347
public static final int storiesGroupsAdBlocksummary = 0x7f12187c; // 2131892348
public static final int systemtheme = 0x7f12187d; // 2131892349
public static final int systemtheme_enabled = 0x7f12187e; // 2131892350
public static final int systemtheme_summ = 0x7f12187f; // 2131892351
public static final int updateverifdata_summ = 0x7f121880; // 2131892352
public static final int updateverifdata_title = 0x7f121881; // 2131892353
public static final int useCustomPrefsStyle_summ = 0x7f121882; // 2131892354
public static final int useCustomPrefsStyle_title = 0x7f121883; // 2131892355
public static final int useGenius_summ = 0x7f121884; // 2131892356
public static final int useGenius_title = 0x7f121885; // 2131892357
public static final int useNewColorEngine_summ = 0x7f121886; // 2131892358
public static final int useNewColorEngine_title = 0x7f121887; // 2131892359
public static final int useOldAppVer_summ = 0x7f121888; // 2131892360
public static final int useOldAppVer_title = 0x7f121889; // 2131892361
public static final int vkx_integration_enabled_info = 0x7f12188a; // 2131892362
public static final int whitelisted_ad_groups_stories_title = 0x7f12188b; // 2131892363
public static final int writebar_ios_title = 0x7f12188c; // 2131892364
public static final int cache_size = 0x7f12188d; // 2131892365
public static final int cache_size_summ = 0x7f12188e; // 2131892366
public static final int clear_all_cache = 0x7f12188f; // 2131892367
public static final int disable_roaming_state = 0x7f121890; // 2131892368
public static final int disable_roaming_state_submit = 0x7f121891; // 2131892369
public static final int invalidate_theme_cache_each_update_title = 0x7f121892; // 2131892370
public static final int invertFilters = 0x7f121893; // 2131892371
public static final int invertFilters_summ = 0x7f121894; // 2131892372
public static final int invertcached_summ = 0x7f121895; // 2131892373
public static final int invertcached_title = 0x7f121896; // 2131892374
public static final int microg_dialog1 = 0x7f121897; // 2131892375
public static final int microg_dialog2 = 0x7f121898; // 2131892376
public static final int microg_dialog3 = 0x7f121899; // 2131892377
public static final int serverFeaturesDisable = 0x7f12189a; // 2131892378
public static final int serverFeaturesDisable_summ = 0x7f12189b; // 2131892379
public static final int sett_autoupdates = 0x7f12189c; // 2131892380
public static final int sett_autoupdates_summ = 0x7f12189d; // 2131892381
public static final int sett_battery = 0x7f12189e; // 2131892382
public static final int sett_battery_summ1 = 0x7f12189f; // 2131892383
public static final int sett_battery_summ2 = 0x7f1218a0; // 2131892384
public static final int sett_chats = 0x7f1218a1; // 2131892385
public static final int sett_chats_summ = 0x7f1218a2; // 2131892386
public static final int sett_community = 0x7f1218a3; // 2131892387
public static final int sett_community_summ = 0x7f1218a4; // 2131892388
public static final int sett_missing_links = 0x7f1218a5; // 2131892389
public static final int sett_missing_links_summ = 0x7f1218a6; // 2131892390
public static final int sett_nogms_summ = 0x7f1218a7; // 2131892391
public static final int sett_notifs = 0x7f1218a8; // 2131892392
public static final int sett_notifs_summ = 0x7f1218a9; // 2131892393
public static final int sett_recommendations = 0x7f1218aa; // 2131892394
public static final int tgs_token_missing = 0x7f1218ab; // 2131892395
public static final int tgs_token_reset = 0x7f1218ac; // 2131892396
public static final int tgs_token_reset_dialog = 0x7f1218ad; // 2131892397
public static final int tgs_token_reset_menu = 0x7f1218ae; // 2131892398
public static final int update_err = 0x7f1218af; // 2131892399
public static final int update_latest = 0x7f1218b0; // 2131892400
}
public static final class style {
@@ -20786,5 +20890,6 @@ public final class R {
public static final int preferences_proxy = 0x7f15001c; // 2132082716
public static final int preferences_tgs = 0x7f15001d; // 2132082717
public static final int preferences_themes = 0x7f15001e; // 2132082718
public static final int locales_config = 0x7f15001f; // 2132082719
}
}
@@ -1,8 +1,5 @@
package com.vtosters.lite.ui.bottomnavigation;
import static ru.vtosters.lite.utils.Preferences.vkme;
import static ru.vtosters.lite.utils.ThemesUtils.getCSTDock;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -18,7 +15,6 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.view.menu.MenuItemImpl;
@@ -26,28 +22,29 @@ import androidx.appcompat.view.menu.MenuView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vtosters.lite.R;
import me.grishka.appkit.utils.V;
import static ru.vtosters.hooks.other.Preferences.vkme;
import static ru.vtosters.hooks.other.ThemesUtils.getCSTDock;
public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView {
private final TextView mSmallLabel;
private final TextView mLargeLabel;
private final View mCounterContainer;
private final TextView mCounter;
private final View mDot;
private final int mMargin;
private final float mSmallScale;
private final ImageView mIcon;
private final ImageView mActionIcon;
private int mItemPos;
private MenuItemImpl mItemData;
private ColorStateList mColorStateList;
private final int mMargin;
private final float mSmallScale;
private boolean mShiftingModeEnabled;
private boolean mIconsModeEnabled;
private boolean mStaticModeEnabled;
private final ImageView mIcon;
private final ImageView mActionIcon;
public BottomNavigationItemView(@NonNull Context context) {
this(context, null);
@@ -72,10 +69,10 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
this.mIcon.getLayoutParams().height = V.a(28.0F);
this.mSmallLabel = this.findViewById(R.id.smallLabel);
mSmallLabel.setTextColor(getCSTDock());
mSmallLabel.setTextColor(getCSTDock(this.getContext()));
this.mLargeLabel = this.findViewById(R.id.largeLabel);
mLargeLabel.setTextColor(getCSTDock());
mLargeLabel.setTextColor(getCSTDock(this.getContext()));
LayoutInflater.from(this.getContext()).inflate(R.layout.navigation_bottom_counter, this, true);
this.mCounterContainer = this.findViewById(R.id.counter_container);
@@ -104,6 +101,10 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
return this.mItemPos;
}
public void setItemPosition(int pos) {
this.mItemPos = pos;
}
@SuppressLint("RestrictedApi")
public void initialize(MenuItemImpl impl, int i) {
this.mItemData = impl;
@@ -224,7 +225,7 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
int textsize;
if (vkme()){
if (vkme()) {
textsize = 11;
} else {
textsize = 10;
@@ -313,10 +314,6 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
ViewCompat.setBackground(this, resId != 0 ? ContextCompat.getDrawable(this.getContext(), resId) : null);
}
public void setItemPosition(int pos) {
this.mItemPos = pos;
}
public void setShiftingMode(boolean var1) {
this.mShiftingModeEnabled = var1;
}
@@ -0,0 +1,34 @@
package ru.vtosters.hooks;
import android.view.View;
import com.vtosters.lite.R;
import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.VersionReader;
import static ru.vtosters.hooks.other.Preferences.devmenu;
import static ru.vtosters.hooks.other.Preferences.getBuildName;
import static ru.vtosters.lite.utils.AndroidUtils.getApplicationName;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
public class AboutHook {
public static void inject(View view) {
view.setOnLongClickListener(v -> {
if (devmenu()) {
sendToast(AndroidUtils.getString(R.string.debug_menu_already_activated));
} else {
Preferences.getPreferences().edit().putBoolean("devmenu", true).apply();
sendToast(AndroidUtils.getString(R.string.debug_menu_activated));
}
return true;
});
}
public static String getCommitLink() {
return "https://github.com/vtosters/lite/commit/" + VersionReader.getVersionCommit();
}
public static String getAppVersion() {
return getApplicationName() + " " + getBuildName() + " | " + VersionReader.getVersionFull();
}
}
@@ -0,0 +1,25 @@
package ru.vtosters.hooks;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.newsfeed.NewsFeedFiltersUtils;
public class AdBlockHook {
public static JSONObject discoverInject(JSONObject json) throws JSONException {
return NewsFeedFiltersUtils.discoverInject(json);
}
public static JSONArray feedInject(JSONArray items) {
return NewsFeedFiltersUtils.feedInject(items, false);
}
public static JSONArray wallGetById(JSONArray items) {
return NewsFeedFiltersUtils.feedInject(items, true);
}
public static JSONObject storiesads(JSONObject json, boolean isDeleteFix) {
return NewsFeedFiltersUtils.storiesAds(json, isDeleteFix);
}
}

Some files were not shown because too many files have changed in this diff Show More