Compare commits

...
Author SHA1 Message Date
aliveoutside a4a5eb5638 init feat 2023-03-27 18:03:08 +03:00
gdlbo f29532b518 fix system theme changer 2023-03-15 23:49:25 +03:00
gdlbo ebb6d7d5fd update dev icon
vk premium shock content
2023-03-15 21:49:20 +03:00
gdlbo 46ad8635f1 fix all RadioButtons and SwitchCompats buttons 2023-03-15 21:29:50 +03:00
gdlbo 507dbee4c0 switch tricks 2023-03-15 20:45:59 +03:00
aliveoutside 1dfe5971be track opened settings 2023-03-15 19:18:48 +03:00
aliveoutside 898967128a fixup build 2023-03-15 16:02:45 +03:00
aliveoutside ffa71a8087 add buildtypes 2023-03-15 15:51:36 +03:00
aliveoutside fbb470ed74 remove duplicated crash logging 2023-03-15 15:20:19 +03:00
aliveoutside 0954c8de6e move exportDex task under deps 2023-03-15 13:17:31 +03:00
aliveoutside 39585b053b fix recursive method in AnalyticsHelper 2023-03-15 13:09:57 +03:00
gdlbo 20e966e039 updated start information 2023-03-15 11:07:59 +03:00
gdlbo 99e50e4122 fixed app crash 2023-03-15 11:00:09 +03:00
gdlbo e8477097b6 Update OtherFragment.java 2023-03-15 03:29:16 +03:00
gdlbo 241b6bace5 Update build.gradle 2023-03-15 03:27:26 +03:00
gdlbo 1a5a325ff3 Update OtherFragment.java 2023-03-15 02:36:20 +03:00
gdlbo 3352b0f01c fixed metrics disabler 2023-03-15 01:37:21 +03:00
gdlbo a90ae1be64 added appcenter metrics/crash logger 2023-03-15 01:19:25 +03:00
gdlbo 4d7ff28d97 added support for language config 2023-03-14 02:52:06 +03:00
gdlbo f72967b0d3 Update apktool framework to sdk 33 2023-03-14 02:51:49 +03:00
gdlbo ea2531eaca Update apktool.jar 2023-03-14 02:50:52 +03:00
gdlbo d36fe43541 fix action menu icons color 2023-03-12 00:27:38 +03:00
gdlbo 3acec6a29d Update ThemesFragment.java 2023-03-11 23:32:08 +03:00
gdlbo 5a73f8a4ed Update write_bar_audio.xml 2023-03-11 23:24:28 +03:00
gdlbo 6ecc44c29e Update M3UDownloader.java 2023-03-11 23:21:18 +03:00
gdlbo 2e180707db Update FriendRequestHolder.smali 2023-03-11 22:35:32 +03:00
gdlbo 5176a51c95 fixed crash 2023-03-11 22:33:28 +03:00
gdlbo 0f298b4fe3 Update M3UDownloader.java 2023-03-11 22:26:05 +03:00
gdlbo 51c94d7334 some fixes 2023-03-07 12:49:33 +03:00
spiky-s 07156f1c6a added VKWebAppGetConfig vkbridge event support (#1134) 2023-03-05 16:26:43 +03:00
gdlbo 4a933110e9 fixed false applying theme 2023-03-05 15:56:46 +03:00
gdlbo 8bf424f549 fixed choice icons for photo engine selection 2023-03-05 15:45:03 +03:00
gdlbo f477232cbe Update ThemesUtils.java 2023-03-05 15:05:11 +03:00
gdlbo 80af934df3 upgraded system theming 2023-03-05 14:55:53 +03:00
gdlbo 6d97ee38cd fixed theme applying 2023-03-05 14:09:01 +03:00
gdlbo dedf1a0b1c added debugging for webview + reworked vkui inj 2023-03-04 22:02:18 +03:00
gdlbo 692e7cefad Update VerifyInfoHelper.java 2023-03-02 18:06:08 +03:00
gdlbo dc91f59048 added dev verifs
test test
2023-03-02 15:56:47 +03:00
gdlbo 4b59f732e9 senseless reapplication of filters removed 2023-03-02 14:09:24 +03:00
gdlbo 7e0b083a9a Update AssetManagerHelper.java 2023-03-02 11:57:17 +03:00
gdlbo 60336238f2 fixes 2023-02-28 18:22:36 +03:00
gdlbo 1dd12c45ef Update ThemesCore.java 2023-02-28 18:07:29 +03:00
gdlbo 91fc6f4cad Update VTSettings.java 2023-02-28 18:05:47 +03:00
gdlbo 9e07ddcdf5 improvements 2023-02-25 19:24:37 +03:00
gdlbo 23e89c31eb Update MainActivityInjector.java 2023-02-25 19:14:32 +03:00
Vologhat 876478f2c6 add implementation of https://developer.android.com/reference/android/content/Intent#ACTION_APPLICATION_PREFERENCES 2023-02-25 18:27:05 +03:00
gdlbo 96b8f99e93 some fixes 2023-02-25 15:37:23 +03:00
gdlbo d31fa5cdcc Update AndroidManifest.xml 2023-02-25 15:26:12 +03:00
Vologhat cc034226a0 add navigation click action 2023-02-24 01:39:09 +03:00
Vologhat 785c51a64a fix mod apk making 2023-02-23 16:17:10 +03:00
gdlbo 478206e0ac Update .gitignore 2023-02-21 22:33:03 +03:00
aliveoutside 6f474408a5 update readme 2023-02-21 18:42:19 +03:00
Vologhat b74cbe54ed Performance improvements and cache invalidation fix
* Combining assets.zip and res.zip for better performance
* Fix apk path on cache invalidation
2023-02-21 11:31:49 +03:00
Saoqqandsaoq a45e6a7f2a Refactor wp (#944)
* Extract couple filters to separate classes

* Move ImageEffects to separate file

* Refactor effects to use bitmap

* Reformat code, extract last batch of effects to classes

* Shitty intermediate refactor

* Still intermediate sh**, buildable btw

* More refactoring, still looks disgusting, but a bit better)

* Add fixme about order

* Remove unused stuff

* Remove unused code, prevent re-creating of drawable

* Refactor img effects to native

* Add native lib sources

* Implement Dim, Mosaic and Blur in native

* Add pre-built libs

* Get rid of filter abstraction

* Include native build

* Force rust toolchains

* Fix typos

* Comment not used smali

* Simplify wallpapers handling

* Fixes after main merge

* Minor fixes, add prebuilt lib, disable auto re-builds for native

* Fix missed copyscpec for native builds

* Fix entry for dim effect

* Fix resizing to respect current image dimensions

* Wtf? who stole NDK from actions

* Learning GH actions

I don't want to read the docs

* Revert "Learning GH actions"

This reverts commit f6f9a97d10.

* Revert "Wtf? who stole NDK from actions"

This reverts commit 1d0f1b8879.

* Try out latest LTS just why not

---------

Co-authored-by: saoq <wfmfe@ya.ru>
2023-02-21 09:20:35 +03:00
gdlbo 0d8c52548f Update ThemesManager.java 2023-02-19 21:15:09 +03:00
gdlbo 973b4980a1 Update VKThemeHelper$b.smali 2023-02-19 19:35:46 +03:00
gdlbo b85305e416 fixed emoji 2023-02-19 19:10:46 +03:00
gdlbo 942f049bc5 fixed some hardcode 2023-02-19 18:51:33 +03:00
gdlbo e01322c8d5 fixed crash 2023-02-19 18:32:24 +03:00
gdlbo 1764dc015d fixed all theme bugs 2023-02-19 18:10:33 +03:00
gdlbo 44aff4f30d refactor code 2023-02-19 16:49:02 +03:00
Vologhat 9fd01db880 revert attribute hooks for monet 2023-02-19 16:48:20 +03:00
gdlbo 9264e7f5df Update DNRInjector.java 2023-02-19 16:17:15 +03:00
gdlbo 4fcbea2a35 Update Device.java 2023-02-19 16:01:48 +03:00
gdlbo 60dcb4fb87 fixes 2023-02-19 15:38:57 +03:00
Vologhat 314e4de27d new way of color modification 2023-02-19 15:31:50 +03:00
gdlbo c9eac43919 Update StickersFragment.java 2023-02-19 15:28:33 +03:00
gdlbo 52806a85cc genius 2023-02-19 14:43:22 +03:00
gdlbo e3512bc832 Update Genius.java 2023-02-18 17:03:47 +03:00
gdlbo d43aef7526 genius switch moved to mediacontent 2023-02-18 14:57:16 +03:00
gdlbo 46da6e1ee1 Update Genius.java 2023-02-18 14:55:08 +03:00
gdlbo ed1f99a490 fixed feat moment 2023-02-18 14:19:27 +03:00
gdlbo e46d078ec0 genius lyrics test
only for songs with vk lyrics
2023-02-17 23:09:22 +03:00
gdlbo 1c0048c57c Update AudioGetLyrics.java 2023-02-17 18:36:54 +03:00
gdlbo bb94460c35 added support for new lyrics
thanks @egormetlitsky
2023-02-17 18:36:22 +03:00
gdlbo 0fb5e5b17e splash a12+ 2023-02-15 22:02:23 +03:00
gdlbo 196610f5fe Update Device.java 2023-02-15 21:54:58 +03:00
gdlbo 46579a9f40 fixed translations 2023-02-15 21:53:27 +03:00
gdlbo 424413c3da Update LibVKXClient.java 2023-02-15 21:41:23 +03:00
gdlbo b0753eefcf Update preferences_feed.xml 2023-02-15 09:26:02 +03:00
gdlbo 39e72cf766 animated splash for sdk 12
not ready lol
2023-02-14 21:51:25 +03:00
gdlbo 4e51e65bc3 updated translations 2023-02-14 21:43:42 +03:00
gdlbo e3c694d7c3 added group stories adblock 2023-02-13 18:57:29 +03:00
gdlbo b44dae69a6 some proxy updates 2023-02-13 12:35:38 +03:00
gdlbo efb909ce64 fix artists catalog 2023-02-12 14:24:17 +03:00
Saoqqandsaoq 62c11b90dc Forward calls from msg to off VK apps (#1094)
Co-authored-by: saoq <wfmfe@ya.ru>
2023-02-12 02:18:27 +03:00
gdlbo a15cd9bd0b fixed compressing jpegs 2023-02-08 18:32:58 +03:00
gdlbo 6d9f4a23c8 added newsfeed update dialog 2023-02-08 18:00:36 +03:00
gdlbo 0677f77fde Update BackupManager.java 2023-02-08 17:55:26 +03:00
gdlbo 1b274fdb91 Update DockBarEditorManager.java 2023-02-08 17:08:22 +03:00
gdlbo c4bf9f8f88 some switch fixes 2023-02-08 16:41:56 +03:00
gdlbo e9a98afae9 Update ThemesCore.java 2023-02-07 01:35:36 +03:00
gdlbo 856bd63b43 updated VTSettings.java 2023-02-07 00:48:35 +03:00
gdlbo 606c908b90 some color fixes 2023-02-07 00:09:52 +03:00
gdlbo 0adc9ca3fd updated switch cases 2023-02-06 21:47:33 +03:00
gdlbo 3345382383 jdk upped to 14 2023-02-06 21:35:48 +03:00
gdlbo 7e7f8d0efa Update InterfaceFragment.java 2023-02-06 20:00:17 +03:00
gdlbo 84e0601b76 refactoring 2023-02-06 02:51:32 +03:00
gdlbo bc43b788d8 Update ThemesCore.java 2023-02-06 02:19:36 +03:00
gdlbo 22bd830353 fix #1062 2023-02-06 01:03:31 +03:00
gdlbo c2ff38044c fixed проксi moment 2023-02-05 23:53:28 +03:00
Pavel af312540df Escape character in values-be (#1088) 2023-02-04 21:44:00 +03:00
mysh228 1cee582dbf Apostrophes fixes in arrays 2023-02-04 00:52:05 +03:00
mysh228 664ea02dd0 Apostrophes fix 2023-02-04 00:52:05 +03:00
mysh228 4fe85d264d Belarusian translation 2023-02-04 00:52:05 +03:00
gdlbo e8a1d814c1 fixed false injection 2023-02-03 16:07:42 +03:00
gdlbo 76d0dd2ad6 disabled vigo 2023-02-03 15:56:17 +03:00
gdlbo 4658338fbb Update strings.xml 2023-02-03 15:25:07 +03:00
awers24 745ccd6e1d ukrainian translate (#1083)
* ukrainian translation
2023-02-02 20:52:24 +03:00
k1zn da76da8e35 Update strings.xml 2023-02-01 20:25:08 +05:00
gdlbo 249d1de1a1 Update R.java 2023-01-31 14:50:54 +03:00
gdlbo 1a82b40a7f Update RandomProxy.java 2023-01-31 14:43:01 +03:00
k1zn 83093be566 another translate 2023-01-31 15:58:24 +05:00
gdlbo 5994172095 fixed undelete msg
fixed all bugs omg
2023-01-30 21:36:13 +03:00
gdlbo a645e2fe32 Update CallsHook.java 2023-01-30 18:07:55 +03:00
gdlbo 87f59b2ea8 Update R.java 2023-01-29 15:44:26 +03:00
gdlbo 131bd0bdcc fixed def music catalog 2023-01-29 15:24:19 +03:00
gdlbo 7618c37d4c fix samsung awful icon system 2023-01-27 19:35:37 +03:00
gdlbo 41f978cc26 added monochrome for old icon 2023-01-27 18:44:58 +03:00
gdlbo 489246347f code cleanup 2023-01-27 17:37:49 +03:00
gdlbo a68e580436 fix null link crash for music tracks 2023-01-27 16:12:27 +03:00
gdlbo 0ef33f5c52 Update AndroidUtils.java 2023-01-27 15:41:02 +03:00
gdlbo daac037285 code cleanup 2023-01-24 18:12:01 +03:00
k1zn 09f9adafb0 translate 2023-01-24 15:26:08 +05:00
gdlbo f746753b15 updated translator 2023-01-21 17:28:00 +03:00
gdlbo 2b3da25099 Update AndroidUtils.java 2023-01-21 16:56:00 +03:00
gdlbo 94e8c49784 added links verif dialog 2023-01-21 16:50:23 +03:00
gdlbo afef886293 fixed calls moment 2023-01-21 15:58:37 +03:00
gdlbo acf3a77fe7 Implementation of domain verification 2023-01-16 17:42:03 +03:00
gdlbo 7de0d9a269 updated device info collector for simplify gh issues 2023-01-16 17:26:04 +03:00
dalvesrever 75732deb61 simplify OnPreferenceClickListener implementations
add NavigatorUtils
2023-01-15 19:45:12 +03:00
dalvesrever 923586cc33 close #1014; Make message text selectable 2023-01-15 18:30:15 +03:00
abslike eac9b2f83a Fix highlight on Amoled+Monet theme (#1041)
* Fix monet

* Try to fix part 2

* Fixed outgoing message

* Update styles.xml

* FIX MONET PART IV

* fix monet part of endless

* fix monet on white theme

* Fix highlight

* Fix highlight and other

* Fix text + highlight

* Fix highlight and text

* highlight

* Monet fixes

* Fix monet

* build fix

* build fixes

* Update styles.xml

* fix highlightes Amoled theme

* fix highlight Amoled theme
2023-01-14 12:53:48 +03:00
xsafter bfc718bc98 Рефактор Utils (#1000)
* Refactor:обновил Deprecated в новых Андроидах вызовы в некоторых утилитах

* Refactor:обновил Deprecated в новых Андроидах вызовы в некоторых утилитах

* Пофиксил
2023-01-11 13:41:46 +03:00
gdlbo effab03fd2 Merge pull request #1027 from Saoqq/vk-im-calls-forward
Quick calls integration with VK messenger app
2023-01-11 12:55:34 +03:00
saoq 6b1501b0fa Fix fun name 2023-01-10 23:52:58 +03:00
saoq 4f2d97fd7b Support VK off client 2023-01-10 23:39:48 +03:00
saoq 26b1db0900 Quick calls integration with VK messenger app 2023-01-10 19:21:24 +03:00
k1zn 6936079803 fix #1026 2023-01-10 20:51:25 +05:00
gdlbo eba6c43d4c dockbar and superapp editor moved 2023-01-06 18:48:49 +03:00
gdlbo 6667dc0be8 added manual verif data update 2023-01-06 16:32:14 +03:00
gdlbo 26e59629aa some settings refactoring 2023-01-06 16:25:58 +03:00
gdlbo 14f9f6dcf8 fixed msg preview 2023-01-06 16:10:41 +03:00
gdlbo 0a8ecd8b0a Update FeedFragment.java 2023-01-06 14:47:23 +03:00
gdlbo a4113ba6d5 auto refactor xmls 2023-01-06 14:47:16 +03:00
gdlbo 4f5193f170 Update Preferences.java 2023-01-06 14:28:52 +03:00
gdlbo 81a62d5400 some code updates 2023-01-03 13:59:39 +03:00
gdlbo 1a7031ead1 fixed init filters 2023-01-01 18:43:05 +03:00
Vologhat 40a1ab64cb add generateRjava task to gradle script 2023-01-01 18:30:48 +03:00
gdlbo 78f0eae9c3 fix #1001 2023-01-01 18:23:32 +03:00
Vologhat b5de88a370 update R.java 2023-01-01 18:19:32 +03:00
Vologhat 1e9a6c6840 fix shell script 2023-01-01 18:12:46 +03:00
gdlbo eb8939021d Update styles.xml 2023-01-01 18:04:34 +03:00
Vologhat a53f90367e fix item swipe disappearance 2023-01-01 17:45:01 +03:00
gdlbo f19aae542e fixed dialog stats from dialogs list 2023-01-01 16:15:38 +03:00
gdlbo f34b895485 added some photo quality magic 2023-01-01 16:13:48 +03:00
gdlbo 5c194891ba disabled more stats 2023-01-01 15:45:25 +03:00
Vologhat e7c08406a5 replace tags with ids 2023-01-01 15:29:42 +03:00
Vologhat c3a08ea7a8 change item width to match_parent 2023-01-01 15:23:25 +03:00
Vologhat 69ea193771 refactor CategorizedAdapter, DockBarEditor, SuperAppEditor 2023-01-01 15:08:32 +03:00
gdlbo 562bfc0ed0 added webview wrapper 2023-01-01 15:08:22 +03:00
Vologhat 919d7cb19d refactor CategorizedAdapter, DockBarEditor, SuperAppEditor 2023-01-01 15:06:55 +03:00
gdlbo 0a766038da updated icon for dialog stats 2022-12-31 23:04:26 +03:00
gdlbo dabc79cdb2 chat stats 2022-12-31 21:42:42 +03:00
gdlbo 4cb73d669a Update StickerPackAdapter.java 2022-12-30 17:50:03 +03:00
gdlbo dfddb13d2d Update CatalogJsonInjector.java 2022-12-30 17:45:39 +03:00
Vologhat f17c57bac2 add tgs stickers preview in settings 2022-12-30 17:32:14 +03:00
Vologhat a6c4c376c3 fix tgs stickers updating on keyboard stickers panel 2022-12-30 17:12:16 +03:00
gdlbo 3e4f14678e fixed nonmilk profile hide 2022-12-30 16:35:20 +03:00
gdlbo f860eb2cc0 Merge pull request #995 from psamur/main
New icons in profile buttons
2022-12-30 16:01:46 +03:00
gdlbo 2a6b9da4ef ImBanner cleanup code 2022-12-30 15:58:10 +03:00
Pavel 6224a4afdf new icons in profile buttons 2022-12-30 15:54:12 +03:00
gdlbo 8650124860 refactored ssfs utils 2022-12-30 15:35:55 +03:00
gdlbo 5e35a61473 refactored profile buttons injector 2022-12-30 14:52:29 +03:00
Vologhat 578f1a2221 add launch task 2022-12-30 14:28:30 +03:00
gdlbo 0fb3ff6c53 deleted useless logs 2022-12-30 02:38:28 +03:00
abslike 7fbfbd1e1e Fix Monet on nonmilkshake (#991) 2022-12-30 02:35:08 +03:00
gdlbo 71de558e5c ssfs update
added support for im banners
2022-12-30 02:31:09 +03:00
gdlbo 606b89a4ec ssfs update
added profile hide
2022-12-30 01:34:15 +03:00
Vologhat b4bdb3070a fix tgs preference summary 2022-12-29 20:53:01 +03:00
Vologhat 9f4b5990b7 refactor PreferencesUtil.java to PreferenceFragmentUtils.java 2022-12-29 19:08:15 +03:00
Vologhat 93b28b0a8f add non-null annotation 2022-12-29 19:04:11 +03:00
gdlbo ed0587b9b6 Merge pull request #988 from psamur/main
"View all..." normal translation
2022-12-29 10:43:16 +03:00
Pavel 6ce72dfc8b - bad translate + normal translate requested in VKTCH 2022-12-29 01:21:10 +03:00
gdlbo 65f44d32ca Update ThemesUtils.java 2022-12-28 23:41:45 +03:00
k1zn 8e57d5ab44 fix crash notf name and translate
crash
2022-12-29 00:52:17 +05:00
gdlbo c2b6bffb17 added articles colors inj 2022-12-28 22:17:43 +03:00
gdlbo 550fbf8f6c Merge pull request #982 from abslike/patch-2
Fix monet in messages(nomilkshake)
2022-12-28 22:01:49 +03:00
gdlbo 79d3287143 fixed #969 again for muted accent 2022-12-28 21:56:34 +03:00
gdlbo 2242e37315 fixed outgoing msg highlighted status 2022-12-28 21:50:21 +03:00
gdlbo 7099da0387 fixed searchDialog 2022-12-28 21:39:56 +03:00
k1zn 9b29288351 fix #986 2022-12-28 22:07:03 +05:00
abslike e000bd7971 Fix monet in messages(nomilkshake) 2022-12-28 00:40:24 +05:00
gdlbo 8eb725148f added search engine selection 2022-12-27 18:05:54 +03:00
gdlbo af1d175415 fix #969 2022-12-27 16:28:19 +03:00
Vologhat 92edbd4686 add keyaboard popup 2022-12-27 16:20:31 +03:00
gdlbo 8d9812ebe0 fix #978 2022-12-27 15:54:55 +03:00
gdlbo 33477e809c fixed #974 2022-12-27 15:22:10 +03:00
Vologhat 04f4f7d2ad fix #972 2022-12-27 11:54:08 +03:00
gdlbo 80b8399cb8 added link to bot in tg stickers info 2022-12-27 01:05:51 +03:00
gdlbo 483c9eda4d added more anime pic search engine 2022-12-27 00:38:38 +03:00
gdlbo c76a7a5332 fixed crash SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 2022-12-26 23:20:07 +03:00
gdlbo c470062d70 fixed crash getColor 2022-12-26 23:11:42 +03:00
gdlbo 52f69c792d fixed google pic search for Ukraine users 2022-12-26 21:51:45 +03:00
Vologhat 33f150eb43 uncomment and refactor ota code 2022-12-26 21:49:48 +03:00
gdlbo e165f388f5 fixed google pic search 2022-12-26 21:39:24 +03:00
Vologhat 761ff263be fix qualities list sorting 2022-12-26 20:46:47 +03:00
gdlbo 8918f203fc disabled accent for nonmilk 2022-12-26 19:29:46 +03:00
gdlbo 6039d0cf43 fixed msgs 2022-12-26 13:32:30 +03:00
gdlbo af902f945e Update styles.xml 2022-12-26 13:12:44 +03:00
gdlbo a90d96b00f disabled accents in nonmilk + enabled monet for nonmilk 2022-12-26 12:14:25 +03:00
gdlbo 550d3dadd8 some themesutils code fixes 2022-12-26 11:58:53 +03:00
gdlbo 82720adb61 fixed statusbar in audioplayer 2022-12-26 11:29:41 +03:00
gdlbo 2a3e12b39a fixed borders in monet 2022-12-26 11:06:43 +03:00
gdlbo 87407b0007 fixed accent hardcode in search 2022-12-26 10:50:44 +03:00
gdlbo ff2e33d9a2 fixed header text in monet nonmilk 2022-12-25 19:02:05 +03:00
Vologhat 61b6b0a1ec replace qualified name with import 2022-12-25 18:43:36 +03:00
gdlbo 9db18f526a Update ThemesCore.java 2022-12-25 18:31:10 +03:00
gdlbo 043253ffff disabled outgoing msg accenting in dark theme 2022-12-25 18:02:56 +03:00
gdlbo 756ab2b7c0 fixed muted colors on nonmilk 2022-12-25 17:56:59 +03:00
gdlbo 2ba5aef993 fixed accents exceptions 2022-12-25 17:54:08 +03:00
gdlbo ab3bd09ab7 fixed colors exceptions 2022-12-25 17:35:58 +03:00
gdlbo 7990d269aa refactored icon manager 2022-12-25 16:58:13 +03:00
gdlbo fffda61a83 fixup! disabled monet for nonmilk 2022-12-25 16:04:25 +03:00
gdlbo d817ccd292 disabled monet for nonmilk 2022-12-25 16:03:31 +03:00
gdlbo 02de3699cc fixed fix separator
dead fix xd
2022-12-24 13:53:53 +03:00
aliveoutside 81b214bcff fix vkui BottomConfirmButton 2022-12-24 13:24:17 +03:00
gdlbo 5dcf50fdc2 some sharing dialog fixes 2022-12-24 12:46:38 +03:00
gdlbo 81d4f9ec99 fixed search bar tint 2022-12-23 17:19:30 +03:00
gdlbo 29596b5dd1 fixed menu items coloring with monet theme 2022-12-23 17:10:30 +03:00
gdlbo b789840498 fixed control_foreground + control_background 2022-12-23 16:18:40 +03:00
gdlbo fce6c2b14f fixed vkim unread dot text 2022-12-23 16:05:54 +03:00
aliveoutside f2ac2c6a37 [monet] impl disabled buttons color 2022-12-23 15:45:40 +03:00
aliveoutside 61881f4e9d [monet] improve vkui buttons 2022-12-23 15:31:54 +03:00
aliveoutside 92f74aa5fb [monet] fix friends recom card background 2022-12-23 15:13:48 +03:00
aliveoutside 0c968de977 [monet] fix buttons tint in friends list 2022-12-23 14:51:50 +03:00
gdlbo 51efc8f4c6 disabled alpha picker 2022-12-21 19:41:33 +03:00
aliveoutside 4dbf1756e1 [monet] fix icon placeholder color on amoled theme 2022-12-21 19:16:24 +03:00
aliveoutside 8d4d88e099 [monet] fix icon placeholder color on dark theme 2022-12-21 19:10:28 +03:00
aliveoutside dd953d39e7 [monet] fix icon placeholder color 2022-12-21 18:52:26 +03:00
aliveoutside 4902938016 [monet] add a small accent to background_content 2022-12-21 18:36:16 +03:00
gdlbo 54de4fd060 Update styles.xml 2022-12-21 18:33:08 +03:00
gdlbo 6d9f3029d3 Update styles.xml 2022-12-19 14:23:52 +03:00
gdlbo 332fe678b9 Update styles.xml 2022-12-19 13:49:55 +03:00
gdlbo 04eee5cc26 Update JsonInjectors.java 2022-12-19 13:33:35 +03:00
Vologhat bc94f1ac77 reduce redundant reflection usage 2022-12-17 14:51:39 +03:00
gdlbo fc423d5ccd fixed cards colors 2022-12-16 20:31:28 +03:00
gdlbo f0993bee15 Update HomeFragment.smali 2022-12-16 19:58:28 +03:00
k1zn 22ef4bd646 Update strings.xml 2022-12-16 19:24:16 +05:00
k1zn c4cc678c82 add summary to themes 2022-12-16 19:22:47 +05:00
k1zn 350c25faff translate 2022-12-16 19:08:41 +05:00
aliveoutside 89ea35dd82 [monet] make newsfeed less ugly 2022-12-16 17:04:46 +03:00
aliveoutside cd858266a8 fix highlight monet color by @absl1ke 2022-12-16 16:42:11 +03:00
gdlbo 0549b07c49 Update stickers_keyboard_navigation.xml 2022-12-14 17:22:46 +03:00
gdlbo a7fd5536f6 fixed writebar issues 2022-12-14 17:11:29 +03:00
gdlbo 1397653b55 disabled NOTIFICATION_ANIM toggle 2022-12-14 15:57:59 +03:00
gdlbo ec1a0fc2d5 separator fixes 2022-12-14 15:57:47 +03:00
gdlbo b75dca5515 disabled shadow in milk news 2022-12-14 15:57:36 +03:00
gdlbo f31fade75a disabled clipdata ddos 2022-12-14 15:57:24 +03:00
gdlbo 347e0fb9c4 some monet fixes 2022-12-14 15:01:07 +03:00
gdlbo bf1a73f606 Update build.gradle 2022-12-13 22:10:23 +03:00
gdlbo 82e268ffa7 Update styles.xml 2022-12-13 22:10:11 +03:00
gdlbo f3acbf74a8 Update ThemesUtils.java 2022-12-13 20:37:22 +03:00
gdlbo 7126af6240 Update styles.xml 2022-12-13 20:20:29 +03:00
gdlbo ef2946a700 Update PhotoViewer.java 2022-12-13 20:08:09 +03:00
gdlbo 8821574412 fix 2022-12-13 19:59:20 +03:00
gdlbo 8257a374da monet separator fixes 2022-12-13 19:46:47 +03:00
gdlbo 3985593842 Update build.gradle 2022-12-13 18:54:23 +03:00
gdlbo 92d8c9a90d Update styles.xml 2022-12-12 22:41:56 +03:00
gdlbo 6f9c325b0d Update styles.xml 2022-12-12 22:37:02 +03:00
gdlbo 1010821cf4 Revert "add CompoundButton hook"
This reverts commit 04488314b5.
2022-12-12 22:16:08 +03:00
gdlbo 012dfa7063 fixed cards in monet style 2022-12-12 21:56:49 +03:00
gdlbo 01adf656e2 added support for monet light style 2022-12-12 17:04:37 +03:00
gdlbo bb64ed8d2a monet prefs imp 2022-12-12 16:47:08 +03:00
gdlbo 1ec78a8fbd some theme upgrades
monet theme disabled
amoled theme switch
some fixes with vkui inj
2022-12-12 16:34:52 +03:00
k1zn f1436ebbde fix music downloading with proxy 2022-12-11 23:32:13 +05:00
k1zn c4efb751c9 miui checks fix 2022-12-11 18:23:40 +05:00
Vologhat 6e902d4bca add miui checks 2022-12-11 15:54:03 +03:00
Vologhat 07b49a4a74 Merge branch 'main' of https://github.com/vtosters/lite 2022-12-11 15:51:22 +03:00
gdlbo c6fdf65137 fixed btns color 2022-12-11 15:50:59 +03:00
gdlbo 18e2554d5e fixed btns color 2022-12-11 14:17:03 +03:00
Vologhat 04488314b5 add CompoundButton hook 2022-12-11 14:12:29 +03:00
gdlbo 3eec52db3b fixed null crash csl in BottomNavigationItemView 2022-12-11 14:00:25 +03:00
gdlbo 132df6080e added useful feature 2022-12-11 13:33:55 +03:00
gdlbo d8f91bc730 fixed vologhat's best code reviewing 2022-12-11 13:32:44 +03:00
Vologhat 270c2af5c1 small messages downloader refactoring 2022-12-10 22:41:31 +03:00
Vologhat 99c500ed69 load placeholder image firstly 2022-12-10 22:21:40 +03:00
gdlbo 8174be1768 updated monet theme 2022-12-10 12:51:14 +03:00
Vologhat a88f59bac1 load user avatar image asynchronously 2022-12-09 21:03:50 +03:00
aliveoutside 1eaa3748c4 Merge remote-tracking branch 'origin/main' 2022-12-09 18:39:18 +03:00
aliveoutside 27836f72f5 Merge branch 'accents' 2022-12-09 18:35:03 +03:00
Saoqqandsaoq 70e048a1ad Add zipalign to CI and build.sh (#928)
* Add zipalign to CI and build.sh

* Revert accidentally removed cleanup

Co-authored-by: saoq <wfmfe@ya.ru>
2022-12-09 18:33:44 +03:00
aliveoutside e915b26918 fix incorrect downloaded tracks length
closes #868
2022-12-09 18:07:40 +03:00
gdlbo 8a6a92f77b upgraded color refs 2022-12-08 14:36:33 +03:00
gdlbo cb492b5949 handle color + highlight color fix 2022-12-08 13:43:57 +03:00
gdlbo f15e4c004d color handles 2022-12-08 13:28:50 +03:00
gdlbo f7f2cf3e58 color cursor 2022-12-08 13:01:22 +03:00
gdlbo 578fa86f19 Update ThemesUtils.java 2022-12-07 18:46:23 +03:00
gdlbo a9bcb19ebf Update StandartFilter.txt 2022-12-07 18:40:07 +03:00
gdlbo cf6b993172 Update EditTextExt.smali 2022-12-07 18:32:34 +03:00
gdlbo 145a6a668b Update date_picker_selector.xml 2022-12-07 18:08:37 +03:00
gdlbo 6b2752db67 Update ColorReferences.java 2022-12-07 18:03:51 +03:00
gdlbo 58fb6224f4 color imp 2022-12-07 17:51:53 +03:00
gdlbo b6406bcebf fixed some coloring 2022-12-07 17:33:21 +03:00
gdlbo 6c7733f1a0 updated code 2022-12-07 17:17:04 +03:00
gdlbo 5373002666 some mbsh updates 2022-12-07 16:49:58 +03:00
gdlbo 4005ac2ef1 Update ColorReferences.java 2022-12-07 10:26:47 +03:00
gdlbo 30de30cf64 fixed many colors 2022-12-07 01:41:22 +03:00
gdlbo 0feeacb533 Update ColorReferences.java 2022-12-06 22:03:17 +03:00
gdlbo de511d15a6 some code upgrades 2022-12-06 18:45:33 +03:00
aliveoutside dc1be1434a Merge pull request #931 from rekayno/patch-1
Update build.yml
2022-12-06 18:32:45 +03:00
gdlbo 6b88996962 fixed ssfs 2022-12-06 16:59:22 +03:00
gdlbo bb35b2b252 Update styles.xml 2022-12-05 17:14:51 +03:00
gdlbo b2f1cfea5e Update ThemesUtils.java 2022-12-05 16:38:44 +03:00
gdlbo 2748ec6902 added muted coloring 2022-12-05 16:19:05 +03:00
gdlbo 17b9c7075e muted accent 2022-12-05 15:20:28 +03:00
gdlbo fd070323b0 Update TextViewHook.java 2022-12-05 14:49:26 +03:00
gdlbo 03a43326b7 tablayout color fix 2022-12-05 14:34:02 +03:00
gdlbo a0b28b6c12 fixed crash 2022-12-05 13:34:48 +03:00
gdlbo 5d46edb91a Update TogglesHook.java 2022-12-05 13:32:41 +03:00
gdlbo 0e751533b5 fixed crashes 2022-12-05 12:53:58 +03:00
gdlbo 32d5ba792a Update PhotoListFragment.smali 2022-12-05 12:47:45 +03:00
gdlbo eb96137c3a ultra upgraded theming csl 2022-12-05 01:20:02 +03:00
gdlbo 09f7a07226 Update SuperAppEditorFragment.java 2022-12-05 00:52:56 +03:00
gdlbo 251f9935aa fixed toolbar 2022-12-05 00:43:20 +03:00
gdlbo a75070f8d3 Update ToolbarHook.java 2022-12-05 00:04:04 +03:00
gdlbo 972921a05c OverlayTextView hooked 2022-12-04 23:31:38 +03:00
gdlbo 3b2586ade5 some hooks update 2022-12-04 23:11:07 +03:00
gdlbo 58ea05cc9d updated toolbar icons hook 2022-12-04 22:33:13 +03:00
gdlbo 441baac671 Update ViewInjector.java 2022-12-04 22:23:34 +03:00
Vologhat a1efd38048 add toolbar icons hook 2022-12-04 22:21:40 +03:00
gdlbo d8be2fe7b7 some hooks updates 2022-12-04 22:14:29 +03:00
k1zn 27fdbf2407 [accents] translate colorpickers 2022-12-04 17:27:31 +05:00
k1zn fd954f5bab [accents] translate 2022-12-04 01:21:48 +05:00
gdlbo 3295e40231 Update vkim_bg_dialog_bar_button_primary.xml 2022-12-03 16:15:54 +03:00
gdlbo f88596025f fixed sending icon im msgs 2022-12-03 15:22:34 +03:00
gdlbo 9c5f584d1b disabled calls button 2022-12-03 15:19:20 +03:00
gdlbo 537a3759d0 fixed accent resetting 2022-12-03 14:57:12 +03:00
gdlbo f2fcc5f4d1 some hardcode color fixes 2022-12-02 19:58:09 +03:00
gdlbo db651c462a Update ThemesFragment.java 2022-12-02 19:27:13 +03:00
gdlbo 4f06fda96d Update styles.xml 2022-12-02 19:19:22 +03:00
gdlbo 1344e88b95 added alpha dark/amoled theme monet on sdk 31
todo light theme
2022-12-02 18:53:20 +03:00
Vologhat 23dd5a71e0 add new accent choices 2022-12-02 18:48:47 +03:00
Vologhat 810e5c72c1 add palettes 2022-12-02 18:48:22 +03:00
Vologhat 045daeea47 update ModalBottomSheetWrapper 2022-12-02 18:45:40 +03:00
Vologhat f45de44c75 add color picker by Martin Stone
Add own modification of HSV-Alpha Color Picker for Android https://github.com/martin-stone/hsv-alpha-color-picker-android
2022-12-02 18:37:12 +03:00
gdlbo d2911e5b6b updated msg colors 2022-12-02 14:52:54 +03:00
gdlbo 576890189c fixed accent fragment and color resetting 2022-12-02 13:44:08 +03:00
gdlbo 5ce6ab3aa8 removed global context from adding prefs 2022-12-02 13:43:50 +03:00
Vologhat 1451e2d3ac downgrade buildToolsVersion 2022-12-02 11:53:39 +03:00
Vologhat 95958a85e9 Update IOUtils.java 2022-12-02 11:52:49 +03:00
Vologhat 9f2b3edbe8 Update IOUtils.java 2022-12-01 23:23:55 +03:00
gdlbo ee4249c688 Revert "Update IOUtils.java"
This reverts commit 2f46c193dd.
2022-12-01 23:17:40 +03:00
Vologhat 2f46c193dd Update IOUtils.java 2022-12-01 23:08:29 +03:00
Vologhat 09807a2954 coloring outgoing messages in accent 2022-12-01 22:52:21 +03:00
gdlbo 163cd44dcc getAccenedColorStateList 2022-12-01 21:45:40 +03:00
gdlbo 2bdff722d4 Updated default accent color 2022-12-01 20:46:49 +03:00
gdlbo e206e85324 updated color hooks 2022-12-01 20:34:48 +03:00
rekayno 40159a2a9f Update build.yml
update actions
2022-11-30 17:00:49 +03:00
Denis 54d59e1f2f Update README.md 2022-11-30 15:01:33 +05:00
Vologhat 1b797325f0 Update MessagesPreviewAdapter.java 2022-11-30 11:49:02 +03:00
Vologhat 63c3c0ee32 refactoring 2022-11-30 00:26:58 +03:00
Vologhat 4b6a7aeb84 invalidate color state of dockbar tab titles 2022-11-29 23:16:36 +03:00
Vologhat 38f65997a6 add input format for color string, and reset button 2022-11-29 22:13:30 +03:00
Vologhat d718851d0c fix accent color resetting 2022-11-29 16:00:31 +03:00
gdlbo b30ad26f54 fixed vk cringe bg color in volume control 2022-11-28 12:23:26 +03:00
gdlbo 5f5fb650f2 fixed volume control color 2022-11-28 12:06:05 +03:00
gdlbo 76260981c5 disabled useless hide button 2022-11-28 12:01:29 +03:00
gdlbo ceb84ed6f6 reply bar fix 2022-11-28 11:51:47 +03:00
gdlbo e8a2c3af56 ssfs update 2022-11-27 15:20:27 +03:00
gdlbo 80e78fa2c1 some music coloring 2022-11-27 02:34:22 +03:00
gdlbo 7b35501461 coloring playlists icons 2022-11-27 02:13:40 +03:00
gdlbo c9cfc5e749 updated msg recoloring 2022-11-27 00:36:39 +03:00
gdlbo f0a915be05 Merge pull request #920 from Saoqq/r/sh-build-script
Refactor buildscripts
2022-11-26 18:19:26 +03:00
saoq f2b8624cc4 Update README.md 2022-11-26 01:38:33 +03:00
saoq ccaa10d224 Extract dex move to gradle task, apply to CI .yml 2022-11-26 01:38:22 +03:00
saoqq 6325d8fe68 Remove complication for Win script call, fix sh script invocation 2022-11-25 23:04:37 +03:00
saoqq e243522f95 Ensure Win buildscript works 2022-11-25 21:52:22 +03:00
saoq d87a59bcb0 Refactor build scripts 2022-11-25 08:56:52 +03:00
saoq 724fafc3fd Make build.sh dir independent 2022-11-25 06:13:31 +03:00
gdlbo 8c03ab60fc updated attrs and some swipe coloring 2022-11-24 15:00:02 +03:00
gdlbo 985ced6e3c deleted useless hook 2022-11-24 11:57:49 +03:00
aliveoutside 88339feb12 [accents] create a separate fragment for accents 2022-11-23 23:29:01 +03:00
aliveoutside 2bcf5d1c2d [accents] highlight edittext field 2022-11-23 23:08:01 +03:00
aliveoutside 77f9c60be7 [accents] replace AlertDialog with VkAlertDialog 2022-11-23 23:06:31 +03:00
aliveoutside 76468d8863 [WIP] Accent picker in settings 2022-11-23 22:24:25 +03:00
gdlbo c17e604db6 fixed cringe code + coloring bug 2022-11-22 22:31:32 +03:00
gdlbo 1a96f77aa1 some crash fixes 2022-11-22 21:34:48 +03:00
gdlbo 3fd0757fd4 Update DialogActionsListView.smali 2022-11-22 21:06:26 +03:00
gdlbo 989075a3d2 Update DrawableExt.smali 2022-11-22 20:21:29 +03:00
aliveoutside 802d88cad7 fix AppCompatResources 2022-11-22 20:09:12 +03:00
gdlbo 95a0451c6b fixed dw coloring 2022-11-22 20:06:56 +03:00
aliveoutside 3b83f05bf0 init ThemesCore 2022-11-22 19:08:47 +03:00
aliveoutside c7d350f5b1 remove logger from TextViewHook 2022-11-22 19:08:39 +03:00
gdlbo d3eeaf5f23 removed some hardcode 2022-11-22 19:06:06 +03:00
aliveoutside 4dcea46be7 recolor CSL where it is possible 2022-11-22 18:06:32 +03:00
aliveoutside 96de61fd15 move some stuff from ThemesUtils 2022-11-22 18:00:12 +03:00
aliveoutside 47f4c5c911 Init 2022-11-22 16:58:09 +03:00
aliveoutside fcbf9238cf wait for the pfp loaded in settings
fixes #912
2022-11-21 10:51:05 +03:00
gdlbo 4a8a2adb4e Update MediaFragment.java 2022-11-14 21:06:03 +03:00
gdlbo 501e39429b Update WebCachePreloader.smali 2022-11-14 15:38:05 +03:00
gdlbo 604d19cc40 upgraded link parser 2022-11-14 14:56:21 +03:00
gdlbo a29e0dcf4b fixed articles 2022-11-14 14:26:42 +03:00
gdlbo ec551dcb33 BusinessNotif disabler 2022-11-13 20:33:46 +03:00
gdlbo cc4690965a Update preferences_feed.xml 2022-11-13 19:47:31 +03:00
gdlbo 65f32d3686 fixed articles 2022-11-13 18:15:33 +03:00
gdlbo bf15bb93ee support story download to video downloader btn 2022-11-10 15:27:15 +03:00
gdlbo cb3c537f1d updated ad filter 2022-11-10 14:58:44 +03:00
Vologhat bb0c845e74 Merge branch 'main' of https://github.com/vtosters/lite 2022-11-09 17:15:32 +03:00
Vologhat a4c88d0924 Fix video downloader 2022-11-09 17:14:00 +03:00
gdlbo 7f0b6c3c3d disabled undelete by default 2022-11-09 16:20:17 +03:00
gdlbo 31eedf4d6d updated feed settings 2022-11-09 11:48:31 +03:00
gdlbo bf23cc7305 deleted unused features 2022-11-09 11:09:57 +03:00
gdlbo 6bef2f1ea9 upd proxy information 2022-11-09 11:02:06 +03:00
gdlbo e561356690 deleted video analytics 2022-11-09 10:31:59 +03:00
gdlbo 64d6e4b529 deleted old abt lib 2022-11-09 10:20:40 +03:00
gdlbo 34d067d192 deleted some vk perf trackers 2022-11-08 18:53:02 +03:00
gdlbo c50511a12f deleted vk updates + google play core 2022-11-08 18:33:37 +03:00
gdlbo 0b748c16e4 deleted google ads lib 2022-11-08 18:21:46 +03:00
gdlbo f88dd17480 deleted measurement lib 2022-11-08 18:16:49 +03:00
gdlbo 892e9874cb deleted 60% of firebase lib 2022-11-08 18:11:34 +03:00
gdlbo 82a24e4abf Update GetFullUserProfile.smali 2022-11-08 16:18:57 +03:00
gdlbo 2631757a05 updated TGS Grabber
@egormetlitsky
2022-11-08 16:01:26 +03:00
gdlbo b2f7754ec9 updated old token req 2022-11-08 15:29:28 +03:00
aliveoutside 2175800f33 run auto code reformat 2022-11-02 16:10:56 +03:00
aliveoutside ab16fdcecb optimize imports 2022-11-02 16:01:38 +03:00
aliveoutside a34cad6837 move everything music related to "music" package 2022-11-02 15:57:23 +03:00
k1zn 34b2357a20 translate + move music settings above 2022-10-31 13:35:12 +05:00
gdlbo e47d6acbf1 fixed show cached pl 2022-10-30 00:54:59 +03:00
aliveoutside a5bd532570 use a singleton for okhttp so it will reduce OkHttpClients count 2022-10-29 19:16:21 +03:00
aliveoutside e4124f459f [music] move catalog json injector to separate package 2022-10-29 18:45:48 +03:00
aliveoutside 03328137df [music] fix not loading "main" page 2022-10-29 18:19:27 +03:00
Vologhat 906036854c maybe music fix 2022-10-29 17:57:39 +03:00
gdlbo 1dad82d61c Update MediaFragment.java 2022-10-28 03:51:39 +03:00
gdlbo 0d9f8360ce Update MediaFragment.java 2022-10-28 03:43:59 +03:00
gdlbo 5dc5f001f2 new feature old music catalog 2022-10-28 03:41:55 +03:00
gdlbo b6d26515ce added support of cache pl to empty pl catalog 2022-10-28 03:37:30 +03:00
gdlbo f02971e00d Update build.gradle 2022-10-28 03:00:01 +03:00
gdlbo 827bb9d9ce refactored catalog injector for caching 2022-10-28 02:57:28 +03:00
gdlbo 4a5a6b58e1 Update JsonInjectors.java 2022-10-26 10:36:25 +03:00
gdlbo fa2a1b083d AppVerHook 2022-10-26 10:11:24 +03:00
gdlbo d372b670c0 AppVerHook 2022-10-26 08:01:33 +03:00
gdlbo 86afcaeacb Update ModernSearchView.smali 2022-10-26 07:50:41 +03:00
gdlbo 027c7a7151 fix null crash 2022-10-26 00:09:32 +03:00
aliveoutside 7f304950a8 [music] support new catalogs 2022-10-22 20:32:16 +03:00
Vologhat fc44a9b263 fix apk install 2022-10-20 21:28:11 +03:00
1947 changed files with 35966 additions and 409837 deletions
+1 -4
View File
@@ -19,9 +19,6 @@ assignees: ''
**Скриншот ошибки**
**Информация об устройстве:**
- Устройство: Samsung Galaxy J1 Mini
- OS: Android 9 | OneUi 3.0
- Коммит: fa12e8f (В О Приложении написана версия коммита)
**Скопируйте сюда информацию об устройстве нажав на кнопку "Скопировать информацию об устройстве" в Настройках -> Другое и удалите этот текст**
**Дополнительная информация о баге или мыслях почему оно может происходить (если есть)**
Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+14 -7
View File
@@ -21,33 +21,40 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 11
uses: actions/setup-java@v2
- name: Set up JDK 14
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '14'
distribution: 'adopt'
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download tools
run: |
sudo apt update
sudo apt install default-jre
sudo apt install python3
sudo apt install zipalign
chmod +x scripts/apktool.jar
chmod +x scripts/apksigner.jar
rustup target add armv7-linux-androideabi
rustup target add aarch64-linux-android
- name: Build VTLite
run: |
bash scripts/buildworkflow.sh
gradle generateBuildTasks
gradle prepareDexForMergeDev
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
- name: Sign and verify the apk
run: |
java -jar scripts/apksigner.jar sign --ks scripts/debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android smali/VTLite.apk
java -jar scripts/apksigner.jar verify smali/VTLite.apk
- name: Upload apk to artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: VTLite.apk
path: smali/VTLite.apk
+3
View File
@@ -30,3 +30,6 @@ smali/build/resources.zip
smali/assets/version.properties
*.keystore
gradle.properties
scripts/build.sh
smali/lib/arm64-v8a/libnative_effects.so
smali/lib/armeabi-v7a/libnative_effects.so
+11 -10
View File
@@ -46,8 +46,7 @@
- [Разработка](#разработка)
- [Всё необходимое для сборки и компиляции](#всё-необходимое-для-сборки-и-компиляции)
- [Необходимые зависимости](#необходимые-зависимости)
- [Linux/macOS](#linuxmacos)
- [Windows](#windows)
- [Linux/MacOS/Windows](#LinuxmacOSWindows)
## Функциональность
@@ -118,16 +117,18 @@
* Все необходимые скрипты/.jar файлы для компиляции APK, его подписывании и не только находятся в `/scripts`
### Необходимые зависимости
<a href="https://www.oracle.com/java/technologies/downloads/"><img src="https://i.imgur.com/z8ZN19a.png" align="center" width="20" height="23"/> **JDK**</a> >11 версии
<img src="https://i.imgur.com/z8ZN19a.png" align="center" width="20" height="23"/> [**JDK**](https://adoptium.net/temurin/releases/) >14 версии
<a href="https://developer.android.com/studio"><img src="https://i.imgur.com/cPvvFDP.png" align="center" width="20" height="23"/> **Android Studio**</a>
<img src="https://www.rust-lang.org/logos/rust-logo-32x32.png" align="center" width="20" height="20"/> [**Rust**](https://www.rust-lang.org/learn/get-started) с Android ARM тулчейнами: `rustup target add aarch64-linux-android armv7-linux-androideabi`
### Linux/macOS
Для сборки используется файл `./build.sh`
<img src="https://developer.android.com/static/images/logos/android.svg" align="center" width="20" height="20"/> Android SDK и NDK (устанавливаются через
[sdkmanager](https://developer.android.com/studio/command-line/sdkmanager) или [Android Studio](https://developer.android.com/studio))
<img src="https://i.imgur.com/8BMX8lG.png" width="550" height="368"/>
### Windows
Для сборки используется файл `build.bat`
### Linux/macOS/Windows
<img src="https://i.imgur.com/iZSUKJ6.png" width="550" height="368"/>
Для сборки необходимо вызвать одну из следующих gradle tasks:
+ `./gradlew buildVTL` - соберет и подпишет APK
+ `./gradlew buildAndInstallVTL` - соберет, подпишет и попытается установить через ADB
+ [FOR DEVS] `./gradlew prepareDexForMerge` - соберет все необходимое для сборки в `smali/`
+181 -58
View File
@@ -1,3 +1,5 @@
import org.apache.tools.ant.taskdefs.condition.Os
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
@@ -5,71 +7,38 @@ import java.util.zip.ZipInputStream
plugins {
id 'com.android.application'
id 'net.nemerosa.versioning' version '2.15.1'
id 'org.mozilla.rust-android-gradle.rust-android'
}
cargo {
profile = 'release'
module = '../native-effects'
libname = 'native-effects'
targets = ["arm", "arm64"]
prebuiltToolchains = true
pythonCommand = "python3"
}
def smaliFolder = projectDir.toPath().resolve("../smali").normalize()
def scriptsFolder = projectDir.toPath().resolve("../scripts").normalize()
versionFile {
File moduleDir = projectDir
file = new File(moduleDir.getAbsolutePath() + "/src/main/assets/version.properties")
file = projectDir.toPath().resolve("src/main/assets/version.properties").toFile()
}
task exportDex {
doLast {
try {
File moduleDir = projectDir
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(moduleDir.getAbsolutePath() + "/build/outputs/apk/release/app-release-unsigned.apk"))
ZipFile file = new ZipFile(moduleDir.getAbsolutePath() + "/build/outputs/apk/release/app-release-unsigned.apk");
ZipEntry entry
while ((entry = inputStream.nextEntry) != null) {
if (entry.getName().equals("classes.dex")) {
print('classes.dex found!')
FileOutputStream output = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes6.dex"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = inputStream.read(buffer)) > 0) {
output.write(buffer, 0, len)
}
output.close()
}
if (entry.getName().equals("classes2.dex")) {
print('\nclasses2.dex found!')
FileOutputStream output = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes7.dex"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = inputStream.read(buffer)) > 0) {
output.write(buffer, 0, len)
}
output.close()
}
}
for (ZipEntry e : Collections.list(file.entries())) {
if (e.getName().endsWith("assets/version.properties")) {
var is = file.getInputStream(e)
FileOutputStream output = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "version.properties"))
byte[] buffer = new byte[1024]
int len = 0
while ((len = is.read(buffer)) > 0) {
output.write(buffer, 0, len)
}
output.close()
print('\nversion.properties found!')
}
}
} catch (Exception e) {
e.printStackTrace()
}
}
task cleanVersionFile {
projectDir.toPath().resolve("src/main/assets/version.properties").toFile().delete()
}
android {
compileSdkVersion 32
buildToolsVersion '33.0.0'
buildToolsVersion '32.0.0'
sourceSets {
main {
java.srcDirs += 'build/generated/aidl_source_output_dir/release/out'
}
}
defaultConfig {
applicationId "ru.vtosters.lite.utils.lite"
@@ -80,17 +49,171 @@ android {
multiDexEnabled true
}
buildTypes {
dev {
initWith release
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
beta {
initWith release
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
release {
buildConfigField 'String', 'APPCENTER_KEY', '""'
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_14
targetCompatibility JavaVersion.VERSION_14
}
namespace 'ru.vtosters.lite'
ndkVersion '25.2.9519653'
}
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 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
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()
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!')
}
}
} catch (Exception e) {
e.printStackTrace()
}
}
}
task "prepareDexForMerge$buildType"(type: Copy) {
dependsOn('exportDex' + buildType)
// Remove comment for native lib build
dependsOn('cargoBuild')
into smaliFolder.toString()
from(projectDir) {
include 'classes6.dex'
include 'classes7.dex'
}
from(projectDir) {
include 'version.properties'
into 'assets'
}
// Remove comment for native lib build
from('../native-effects/target/aarch64-linux-android/release'){
include 'libnative_effects.so'
into 'lib/arm64-v8a'
}
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 "buildVTL$buildType"(type: Exec) {
dependsOn('prepareDexForMerge' + buildType)
workingDir(projectDir.parentFile)
if (Os.isFamily("unix") || Os.isFamily("mac")) {
commandLine 'bash', '-c', './scripts/build.sh'
} else {
commandLine 'cmd', '/C', '.\\scripts\\build.bat'
}
}
task "buildAndInstallVTL$buildType"(type: Exec) {
dependsOn('buildVTL' + buildType)
workingDir(scriptsFolder.toString())
ignoreExitValue true
if (Os.isFamily("unix") || Os.isFamily("mac")) {
commandLine 'bash', '-c', 'adb install VTLite.apk'
} else {
commandLine 'adb', 'install', 'VTLite.apk'
}
}
task "buildAndLaunchVTL$buildType"(type: Exec) {
dependsOn('buildAndInstallVTL' + buildType)
if (Os.isFamily("unix") || Os.isFamily("mac"))
commandLine 'bash', '-c', 'adb shell monkey -p com.vtosters.lite -c android.intent.category.LAUNCHER 1'
else
commandLine 'adb', 'shell', 'monkey', '-p', 'com.vtosters.lite', '-c', 'android.intent.category.LAUNCHER', '1'
}
}
}
}
task generateRjava(type: Exec) {
workingDir(projectDir.parentFile)
if (Os.isFamily("unix") || Os.isFamily("mac"))
commandLine "bash", "-c", "./scripts/rclass_regenerator.sh"
else
commandLine "cmd", "/C", ".\\scripts\\rclass_regenerator.sh"
}
clean.dependsOn('cleanVersionFile')
versionFile.dependsOn('cleanVersionFile')
preBuild.dependsOn('versionFile')
@@ -56,22 +56,22 @@ public class LibVKXClient {
}
public static void lambdaplay(List<MusicTrack> list, MusicTrack musicTrack, ILibVkxService iLibVkxService, MusicPlaybackLaunchContext playerRefer) {
var playbackContext = "";
if (playerRefer != null) {
playbackContext = playerRefer.v0();
}
int indexOf = list.indexOf(musicTrack);
var ids = new ArrayList<String>();
for (var track : list) {
ids.add(asId(track));
}
if (indexOf == -1) {
indexOf = 0;
}
try {
var playbackContext = "";
if (playerRefer != null) {
playbackContext = playerRefer.v0();
}
int indexOf = list.indexOf(musicTrack);
var ids = new ArrayList<String>();
for (var track : list) {
ids.add(asId(track));
}
if (indexOf == -1) {
indexOf = 0;
}
iLibVkxService.play(ids, indexOf, playbackContext);
} catch (RemoteException e) {
e.printStackTrace();
@@ -1,16 +1,14 @@
package com.aefyr.tsg.g2;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
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.tgstickers.StickersFragment;
import java.io.File;
import java.util.ArrayList;
@@ -19,7 +17,7 @@ import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
/**
* Created by Aefyr on 19.05.2018.
@@ -227,6 +225,7 @@ public class TelegramStickersService {
currentlyDownloading.add(id.toLowerCase());
notificationsHelper.packStartedDownloading(newPack);
grabber.enableProxy();
grabber.grabPack(id, packFolder, pack.version, new TelegramStickersGrabber.PackDownloadListener() {
@Override
@@ -383,7 +382,7 @@ public class TelegramStickersService {
getPacksListReference().set(li1, p2);
}
interface StickersEventsListener {
public interface StickersEventsListener {
void onPackAdded(TelegramStickersPack pack, int atIndex);
void onPackRemoved(TelegramStickersPack pack, int atIndex);
@@ -184,7 +184,7 @@ public class TelegramStickersGrabber {
}
@Override
public void onResponse(NetCall call, NetResponse response) throws IOException {
public void onResponse(NetCall call, NetResponse response) {
if (!response.isSuccessful()) {
if (response.getData() != null)
listener.onPackDownloadError(new TSGException(response.getDataString() + "\nURL: " + call.request().url()));
@@ -211,21 +211,24 @@ public class TelegramStickersGrabber {
return;
}
if (jPackInfo.optBoolean("is_animated")) {
listener.onPackDownloadError(new TSGException("Animated stickerpacks are not supported!"));
return;
}
ArrayList<Sticker> stickers = new ArrayList<>(jStickers.length());
Log.d(TAG, String.format("Parsing stickers in pack %s", packName));
for (int i = 0; i < jStickers.length(); i++) {
JSONObject jSticker = jStickers.getJSONObject(i);
stickers.add(new Sticker(jSticker.getString("file_id"), jSticker.getString("emoji")));
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")));
}
}
set = new StickerSet(jPackInfo.getString("name"), jPackInfo.getString("title"), packVersion, stickers);
} catch (Exception e) {
listener.onPackDownloadError(e);
@@ -0,0 +1,277 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.util.*;
import java.util.Map.Entry;
/**
* Analyzes an APK to:
*
* <ul>
* <li>Blame resource configurations on their entry count (entries keeping the config around).
* <li>Blame strings in resources.arsc that have no base configuration.
* <li>Blame resources on their different configurations.
* </ul>
*/
public class ArscBlamer {
/** 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 package to blamed resources. */
private final Multimap<PackageChunk, ResourceEntry> packageToBlame = HashMultimap.create();
/** 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 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);
}
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;
}
}
}
}
/** 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);
}
private ResourceEntry(String packageName, String typeName, String entryName) {
this.packageName = packageName;
this.typeName = typeName;
this.entryName = 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);
}
}
}
@@ -0,0 +1,745 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.UnsignedBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import static java.nio.charset.StandardCharsets.US_ASCII;
/**
* Describes a particular resource configuration.
*/
public class BinaryResourceConfiguration implements SerializableResource {
private final int size;
private final int mcc;
private final int mnc;
private final byte[] language;
private final byte[] region;
private final int orientation;
private final int touchscreen;
private final int density;
private final int keyboard;
private final int navigation;
private final int inputFlags;
private final int screenWidth;
private final int screenHeight;
private final int sdkVersion;
private final int minorVersion;
private final int screenLayout;
private final int uiMode;
private final int smallestScreenWidthDp;
private final int screenWidthDp;
private final int screenHeightDp;
private final byte[] localeScript;
private final byte[] localeVariant;
private final int screenLayout2;
private final byte[] unknown;
static BinaryResourceConfiguration create(ByteBuffer buffer) {
int startPosition = buffer.position(); // The starting buffer position to calculate bytes read.
int size = buffer.getInt();
int mcc = buffer.getShort() & 0xFFFF;
int mnc = buffer.getShort() & 0xFFFF;
byte[] language = new byte[2];
buffer.get(language);
byte[] region = new byte[2];
buffer.get(region);
int orientation = UnsignedBytes.toInt(buffer.get());
int touchscreen = UnsignedBytes.toInt(buffer.get());
int density = buffer.getShort() & 0xFFFF;
int keyboard = UnsignedBytes.toInt(buffer.get());
int navigation = UnsignedBytes.toInt(buffer.get());
int inputFlags = UnsignedBytes.toInt(buffer.get());
buffer.get(); // 1 byte of padding
int screenWidth = buffer.getShort() & 0xFFFF;
int screenHeight = buffer.getShort() & 0xFFFF;
int sdkVersion = buffer.getShort() & 0xFFFF;
int minorVersion = buffer.getShort() & 0xFFFF;
// At this point, the configuration's size needs to be taken into account as not all
// configurations have all values.
int screenLayout = 0;
int uiMode = 0;
int smallestScreenWidthDp = 0;
int screenWidthDp = 0;
int screenHeightDp = 0;
byte[] localeScript = new byte[4];
byte[] localeVariant = new byte[8];
int screenLayout2 = 0;
if (size >= SCREEN_CONFIG_MIN_SIZE) {
screenLayout = UnsignedBytes.toInt(buffer.get());
uiMode = UnsignedBytes.toInt(buffer.get());
smallestScreenWidthDp = buffer.getShort() & 0xFFFF;
}
if (size >= SCREEN_DP_MIN_SIZE) {
screenWidthDp = buffer.getShort() & 0xFFFF;
screenHeightDp = buffer.getShort() & 0xFFFF;
}
if (size >= LOCALE_MIN_SIZE) {
buffer.get(localeScript);
buffer.get(localeVariant);
}
if (size >= SCREEN_CONFIG_EXTENSION_MIN_SIZE) {
screenLayout2 = UnsignedBytes.toInt(buffer.get());
buffer.get(); // Reserved padding
buffer.getShort(); // More reserved padding
}
// After parsing everything that's known, account for anything that's unknown.
int bytesRead = buffer.position() - startPosition;
byte[] unknown = new byte[size - bytesRead];
buffer.get(unknown);
return new BinaryResourceConfiguration(size, mcc, mnc, language, region, orientation,
touchscreen, density, keyboard, navigation, inputFlags, screenWidth, screenHeight,
sdkVersion, minorVersion, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
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.
*/
public int size() {
return size;
}
public int mcc() {
return mcc;
}
public int mnc() {
return mnc;
}
/**
* Returns a packed 2-byte language code.
*/
@SuppressWarnings("mutable")
public byte[] language() {
return language;
}
/**
* Returns {@link #language} as an unpacked string representation.
*/
public final String languageString() {
return unpackLanguage();
}
/**
* Returns a packed 2-byte region code.
*/
@SuppressWarnings("mutable")
public byte[] region() {
return region;
}
/**
* Returns {@link #region} as an unpacked string representation.
*/
public final String regionString() {
return unpackRegion();
}
public int orientation() {
return orientation;
}
public int touchscreen() {
return touchscreen;
}
public int density() {
return density;
}
public int keyboard() {
return keyboard;
}
public int navigation() {
return navigation;
}
public int inputFlags() {
return inputFlags;
}
public final int keyboardHidden() {
return inputFlags() & KEYBOARDHIDDEN_MASK;
}
public final int navigationHidden() {
return inputFlags() & NAVIGATIONHIDDEN_MASK;
}
public int screenWidth() {
return screenWidth;
}
public int screenHeight() {
return screenHeight;
}
public int sdkVersion() {
return sdkVersion;
}
/**
* Returns a copy of this resource configuration with a different {@link #sdkVersion}, or this
* configuration if the {@code sdkVersion} is the same.
*
* @param sdkVersion The SDK version of the returned configuration.
* @return A copy of this configuration with the only difference being #sdkVersion.
*/
public final BinaryResourceConfiguration withSdkVersion(int sdkVersion) {
if (sdkVersion == sdkVersion()) {
return this;
}
return new BinaryResourceConfiguration(size(), mcc(), mnc(), language(), region(),
orientation(), touchscreen(), density(), keyboard(), navigation(), inputFlags(),
screenWidth(), screenHeight(), sdkVersion, minorVersion(), screenLayout(), uiMode(),
smallestScreenWidthDp(), screenWidthDp(), screenHeightDp(), localeScript(), localeVariant(),
screenLayout2(), unknown());
}
public int minorVersion() {
return minorVersion;
}
public int screenLayout() {
return screenLayout;
}
public final int screenLayoutDirection() {
return screenLayout() & SCREENLAYOUT_LAYOUTDIR_MASK;
}
public final int screenLayoutSize() {
return screenLayout() & SCREENLAYOUT_SIZE_MASK;
}
public final int screenLayoutLong() {
return screenLayout() & SCREENLAYOUT_LONG_MASK;
}
public final int screenLayoutRound() {
return screenLayout2() & SCREENLAYOUT_ROUND_MASK;
}
public int uiMode() {
return uiMode;
}
public final int uiModeType() {
return uiMode() & UI_MODE_TYPE_MASK;
}
public final int uiModeNight() {
return uiMode() & UI_MODE_NIGHT_MASK;
}
public int smallestScreenWidthDp() {
return smallestScreenWidthDp;
}
public int screenWidthDp() {
return screenWidthDp;
}
public int screenHeightDp() {
return screenHeightDp;
}
/**
* The ISO-15924 short name for the script corresponding to this configuration.
*/
@SuppressWarnings("mutable")
public byte[] localeScript() {
return localeScript;
}
/**
* A single BCP-47 variant subtag.
*/
@SuppressWarnings("mutable")
public byte[] localeVariant() {
return localeVariant;
}
/**
* An extension to {@link #screenLayout}. Contains round/notround qualifier.
*/
public int screenLayout2() {
return screenLayout2;
}
/**
* Any remaining bytes in this resource configuration that are unaccounted for.
*/
@SuppressWarnings("mutable")
public byte[] unknown() {
return unknown;
}
private String unpackLanguage() {
return unpackLanguageOrRegion(language(), 0x61);
}
private String unpackRegion() {
return unpackLanguageOrRegion(region(), 0x30);
}
private String unpackLanguageOrRegion(byte[] value, int base) {
Preconditions.checkState(value.length == 2, "Language or region value must be 2 bytes.");
if (value[0] == 0 && value[1] == 0) {
return "";
}
if ((UnsignedBytes.toInt(value[0]) & 0x80) != 0) {
byte[] result = new byte[3];
result[0] = (byte) (base + (value[1] & 0x1F));
result[1] = (byte) (base + ((value[1] & 0xE0) >>> 5) + ((value[0] & 0x03) << 3));
result[2] = (byte) (base + ((value[0] & 0x7C) >>> 2));
return new String(result, US_ASCII);
}
return new String(value, US_ASCII);
}
/**
* Returns true if this is the default "any" configuration.
*/
public final boolean isDefault() {
return mcc() == 0
&& mnc() == 0
&& Arrays.equals(language(), new byte[2])
&& Arrays.equals(region(), new byte[2])
&& orientation() == 0
&& touchscreen() == 0
&& density() == 0
&& keyboard() == 0
&& navigation() == 0
&& inputFlags() == 0
&& screenWidth() == 0
&& screenHeight() == 0
&& sdkVersion() == 0
&& minorVersion() == 0
&& screenLayout() == 0
&& uiMode() == 0
&& smallestScreenWidthDp() == 0
&& screenWidthDp() == 0
&& screenHeightDp() == 0
&& Arrays.equals(localeScript(), new byte[4])
&& Arrays.equals(localeVariant(), new byte[8])
&& screenLayout2() == 0;
}
@Override
public final byte[] toByteArray() {
return toByteArray(false);
}
@Override
public final byte[] toByteArray(boolean shrink) {
ByteBuffer buffer = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(size());
buffer.putShort((short) mcc());
buffer.putShort((short) mnc());
buffer.put(language());
buffer.put(region());
buffer.put((byte) orientation());
buffer.put((byte) touchscreen());
buffer.putShort((short) density());
buffer.put((byte) keyboard());
buffer.put((byte) navigation());
buffer.put((byte) inputFlags());
buffer.put((byte) 0); // Padding
buffer.putShort((short) screenWidth());
buffer.putShort((short) screenHeight());
buffer.putShort((short) sdkVersion());
buffer.putShort((short) minorVersion());
if (size() >= SCREEN_CONFIG_MIN_SIZE) {
buffer.put((byte) screenLayout());
buffer.put((byte) uiMode());
buffer.putShort((short) smallestScreenWidthDp());
}
if (size() >= SCREEN_DP_MIN_SIZE) {
buffer.putShort((short) screenWidthDp());
buffer.putShort((short) screenHeightDp());
}
if (size() >= LOCALE_MIN_SIZE) {
buffer.put(localeScript());
buffer.put(localeVariant());
}
if (size() >= SCREEN_CONFIG_EXTENSION_MIN_SIZE) {
buffer.putInt(screenLayout2()); // Writing an unsigned byte + 3 padding
}
buffer.put(unknown());
return buffer.array();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BinaryResourceConfiguration that = (BinaryResourceConfiguration) o;
return size == that.size &&
mcc == that.mcc &&
mnc == that.mnc &&
orientation == that.orientation &&
touchscreen == that.touchscreen &&
density == that.density &&
keyboard == that.keyboard &&
navigation == that.navigation &&
inputFlags == that.inputFlags &&
screenWidth == that.screenWidth &&
screenHeight == that.screenHeight &&
sdkVersion == that.sdkVersion &&
minorVersion == that.minorVersion &&
screenLayout == that.screenLayout &&
uiMode == that.uiMode &&
smallestScreenWidthDp == that.smallestScreenWidthDp &&
screenWidthDp == that.screenWidthDp &&
screenHeightDp == that.screenHeightDp &&
screenLayout2 == that.screenLayout2 &&
Arrays.equals(language, that.language) &&
Arrays.equals(region, that.region) &&
Arrays.equals(localeScript, that.localeScript) &&
Arrays.equals(localeVariant, that.localeVariant) &&
Arrays.equals(unknown, that.unknown);
}
@Override
public int hashCode() {
return Objects
.hash(size, mcc, mnc, Arrays.hashCode(language), Arrays.hashCode(region), orientation, touchscreen, density, keyboard, navigation, inputFlags, screenWidth,
screenHeight,
sdkVersion, minorVersion, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, screenHeightDp, Arrays.hashCode(localeScript),
Arrays.hashCode(localeVariant),
screenLayout2, Arrays.hashCode(unknown));
}
@Override
public final String toString() {
if (isDefault()) { // Prevent the default configuration from returning the empty string
return "default";
}
Collection<String> parts = toStringParts().values();
parts.removeAll(Collections.singleton(""));
return Joiner.on('-').join(parts);
}
/**
* Returns a map of the configuration parts for {@link #toString}.
*
* <p>If a configuration part is not defined for this {@link BinaryResourceConfiguration}, its value
* will be the empty string.
*/
public final Map<Type, String> toStringParts() {
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.REGION_STRING, !regionString().isEmpty() ? "r" + regionString() : "");
result.put(Type.SCREEN_LAYOUT_DIRECTION,
getOrDefault(SCREENLAYOUT_LAYOUTDIR_VALUES, screenLayoutDirection(), ""));
result.put(Type.SMALLEST_SCREEN_WIDTH_DP,
smallestScreenWidthDp() != 0 ? "sw" + smallestScreenWidthDp() + "dp" : "");
result.put(Type.SCREEN_WIDTH_DP, screenWidthDp() != 0 ? "w" + screenWidthDp() + "dp" : "");
result.put(Type.SCREEN_HEIGHT_DP, screenHeightDp() != 0 ? "h" + screenHeightDp() + "dp" : "");
result.put(Type.SCREEN_LAYOUT_SIZE,
getOrDefault(SCREENLAYOUT_SIZE_VALUES, screenLayoutSize(), ""));
result.put(Type.SCREEN_LAYOUT_LONG,
getOrDefault(SCREENLAYOUT_LONG_VALUES, screenLayoutLong(), ""));
result.put(Type.SCREEN_LAYOUT_ROUND,
getOrDefault(SCREENLAYOUT_ROUND_VALUES, screenLayoutRound(), ""));
result.put(Type.ORIENTATION, getOrDefault(ORIENTATION_VALUES, orientation(), ""));
result.put(Type.UI_MODE_TYPE, getOrDefault(UI_MODE_TYPE_VALUES, uiModeType(), ""));
result.put(Type.UI_MODE_NIGHT, getOrDefault(UI_MODE_NIGHT_VALUES, uiModeNight(), ""));
result.put(Type.DENSITY_DPI, getOrDefault(DENSITY_DPI_VALUES, density(), density() + "dpi"));
result.put(Type.TOUCHSCREEN, getOrDefault(TOUCHSCREEN_VALUES, touchscreen(), ""));
result.put(Type.KEYBOARD_HIDDEN, getOrDefault(KEYBOARDHIDDEN_VALUES, keyboardHidden(), ""));
result.put(Type.KEYBOARD, getOrDefault(KEYBOARD_VALUES, keyboard(), ""));
result.put(Type.NAVIGATION_HIDDEN,
getOrDefault(NAVIGATIONHIDDEN_VALUES, navigationHidden(), ""));
result.put(Type.NAVIGATION, getOrDefault(NAVIGATION_VALUES, navigation(), ""));
result.put(Type.SDK_VERSION, sdkVersion() != 0 ? "v" + sdkVersion() : "");
return result;
}
private <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) {
// TODO(acornwall): Remove this when Java 8's Map#getOrDefault is available.
// Null is not returned, even if the map contains a key whose value is null. This is intended.
V value = map.get(key);
return value != null ? value : defaultValue;
}
}
@@ -0,0 +1,74 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** 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<>();
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);
}
/** 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(boolean shrink) throws IOException {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
for (Chunk chunk : chunks) {
output.write(chunk.toByteArray(shrink));
}
return output.toByteArray();
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import java.util.Objects;
/**
* Resources in a {@link ResourceTableChunk} are identified by an integer of the form 0xpptteeee,
* where pp is the {@link PackageChunk} id, tt is the {@link TypeChunk} id, and eeee is the index of
* the entry in the {@link TypeChunk}.
*/
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 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;
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);
}
/** 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);
}
private BinaryResourceIdentifier(int packageId, int typeId, int entryId) {
this.packageId = packageId;
this.typeId = typeId;
this.entryId = entryId;
}
/** 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 (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; }
@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 String toString() {
int v = packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId;
return String.format("0x%1$08x", v);
}
}
@@ -0,0 +1,164 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.UnsignedBytes;
import java.nio.ByteBuffer;
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. */
public final class BinaryResourceString {
/** 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;
}
}
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;
}
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);
}
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;
}
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 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));
}
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);
}
return length;
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.primitives.UnsignedBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
import java.util.Objects;
/** 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();
}
Type(int code) {
this.code = UnsignedBytes.checkedCast(code);
}
public byte code() {
return code;
}
public static Type fromCode(byte code) {
return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code);
}
}
/** The serialized size in bytes of a {@link BinaryResourceValue}. */
public static final int SIZE = 8;
private final int size;
private final Type type;
private final int data;
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 BinaryResourceValue(int size, Type type, int data) {
this.size = size;
this.type = type;
this.data = data;
}
/** The length in bytes of this value. */
public int size() { return size; }
/** The raw data type of this value. */
public Type type() { return type; }
/** The actual 4-byte value; interpretation of the value depends on {@code dataType}. */
public int data() { return data; }
@Override
public byte[] toByteArray() {
return toByteArray(false);
}
@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();
}
@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;
}
@Override
public int hashCode() {
return Objects.hash(size, type, data);
}
}
@@ -0,0 +1,301 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.io.LittleEndianDataOutputStream;
import com.google.common.primitives.Shorts;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
/** 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);
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();
}
Type(int code) {
this.code = Shorts.checkedCast(code);
}
public short code() {
return code;
}
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);
}
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);
}
result.init(buffer);
result.seekToEndOfChunk(buffer);
return result;
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
/** 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<>();
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();
}
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);
}
}
}
@@ -0,0 +1,136 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Contains a list of package-id to package name mappings for any shared libraries used in this
* {@link ResourceTableChunk}. The package-id's encoded in this resource table may be different
* than the id's assigned at runtime
*/
public final class LibraryChunk extends Chunk {
/** 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<>();
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);
}
@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);
}
}
}
@@ -0,0 +1,239 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
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;
import java.util.Collection;
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. */
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 keyStringsOffset} can be found. */
private static final int KEY_OFFSET_OFFSET = 276;
/** 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()));
}
}
}
/** 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);
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/** Provides utility methods for package names. */
public final class PackageUtils {
public static final int PACKAGE_NAME_SIZE = 256;
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;
}
}
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]);
}
}
}
@@ -0,0 +1,280 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import com.google.devrel.gmscore.tools.apk.arsc.ArscBlamer.ResourceEntry;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
/**
* Calculates extra information about an {@link ResourceEntry}, such as the total
* APK size the entry is responsible for.
*
* 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 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;
private final Map<ResourceEntry, ResourceStatistics> stats = new HashMap<>();
private final ArscBlamer blamer;
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;
}
// 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;
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a resource table structure. Its sub-chunks contain:
*
* <ul>
* <li>A {@link StringPoolChunk} containing all string values in the entire resource table. It does
* not, however, contain the names of entries or type identifiers.
* <li>One or more {@link PackageChunk}.
* </ul>
*/
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<>();
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;
}
}
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;
}
/** Returns the package with the given {@code packageName}. Else, returns null. */
@Nullable
public PackageChunk getPackage(String packageName) {
return packages.get(packageName);
}
/** Returns the packages contained in this resource table. */
public Collection<PackageChunk> getPackages() {
return Collections.unmodifiableCollection(packages.values());
}
@Override
protected Type getType() {
return Type.TABLE;
}
@Override
protected void writeHeader(ByteBuffer output) {
super.writeHeader(output);
output.putInt(packages.size());
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
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;
}
@@ -0,0 +1,423 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
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;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
/** 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;
/** 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;
/** Index from header of the string data. */
private final int stringsStart;
/** 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 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<>();
/**
* 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;
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;
}
}
// 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);
}
}
// 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;
}
@Override
public byte[] toByteArray() throws IOException {
return toByteArray(false);
}
@Override
public byte[] toByteArray(boolean shrink) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
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);
}
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);
}
/**
* 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);
}
}
/** Represents a styled span associated with a specific string. */
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;
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);
}
private StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) {
this.nameIndex = nameIndex;
this.start = start;
this.stop = stop;
this.parent = 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);
}
}
}
@@ -0,0 +1,449 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
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;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
/**
* Represents a type chunk, which contains the resource values for a specific resource type and
* configuration in a {@link PackageChunk}. The resource values in this chunk correspond to
* the array of type strings in the enclosing {@link PackageChunk}.
*
* <p>A {@link PackageChunk} can have multiple of these chunks for different
* (configuration, resource type) combinations.
*/
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; }
/**
* 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;
}
/**
* 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);
}
@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);
}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
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. */
public final class TypeSpecChunk extends Chunk {
/** The id of the resource type that this type spec refers to. */
private final int id;
/** 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];
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();
}
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);
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* A chunk whose contents are unknown. This is a placeholder until we add a proper chunk for the
* unknown type.
*/
public final class UnknownChunk extends Chunk {
private final Type type;
private final byte[] header;
private final byte[] payload;
protected 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);
}
@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 Type getType() {
return type;
}
}
@@ -0,0 +1,148 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
/** 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;
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;
}
/** 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 a string containing the character value. */
public int rawValueIndex() {
return rawValueIndex;
}
/** 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 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 raw character value. */
public final String rawValue() {
return getString(rawValueIndex());
}
private String getString(int index) {
return parent().getString(index);
}
@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 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);
}
/**
* 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());
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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. */
public final class XmlCdataChunk extends XmlNodeChunk {
/** 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;
protected 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 {@link BinaryResourceValue} instance containing the parsed cdata value. */
public BinaryResourceValue getResourceValue() {
return binaryResourceValue;
}
@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());
}
/**
* 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());
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/**
* Represents an XML chunk structure.
*
* <p>An XML chunk can contain many nodes as well as a string pool which contains all of the strings
* referenced by the nodes.
*/
public final class XmlChunk extends ChunkWithChunks {
protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@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);
}
}
throw new IllegalStateException("XmlChunk did not contain a string pool.");
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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. */
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 attribute name. */
private final int name;
protected 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 attribute name. */
public String getName() {
return getString(name);
}
@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);
}
/**
* 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());
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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. */
public abstract class XmlNamespaceChunk extends XmlNodeChunk {
/** A string reference to the namespace prefix. */
private final int prefix;
/** 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();
}
/** Returns the namespace prefix. */
public String getPrefix() {
return getString(prefix);
}
/** 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);
}
/**
* 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());
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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. */
public final class XmlNamespaceEndChunk extends XmlNamespaceChunk {
protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected Type getType() {
return Type.XML_END_NAMESPACE;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.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. */
public final class XmlNamespaceStartChunk extends XmlNamespaceChunk {
protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) {
super(buffer, parent);
}
@Override
protected Type getType() {
return Type.XML_START_NAMESPACE;
}
}
@@ -0,0 +1,96 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
/** 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;
/** 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 "";
}
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());
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an XML resource map chunk.
*
* <p>This chunk maps attribute ids to the resource ids of the attribute resource that defines the
* attribute (e.g. type, enum values, etc.).
*/
public class XmlResourceMapChunk extends Chunk {
/** 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<>();
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());
}
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);
}
}
}
@@ -0,0 +1,145 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devrel.gmscore.tools.apk.arsc;
import com.google.common.base.Preconditions;
import androidx.annotation.Nullable;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** 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 element name that this chunk represents. */
private final int name;
/** 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 (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 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<>();
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);
// 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;
}
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());
}
}
@@ -0,0 +1,76 @@
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 ru.vtosters.lite.music.Genius;
import ru.vtosters.lite.utils.Preferences;
import java.util.ArrayList;
public class AudioGetLyrics extends ApiRequest<AudioGetLyrics.a> {
private final MusicTrack musicTrack;
public AudioGetLyrics(int lyrics_id, String audio_id, MusicTrack mt) {
super("audio.getLyrics");
c("audio_id", audio_id);
musicTrack = mt;
}
/* renamed from: a */
public a a(@NonNull JSONObject jSONObject) {
var aVar = new a();
if (Preferences.getBoolValue("useGenius", false)) {
aVar.a = Genius.getTextMusic(musicTrack);
return aVar;
}
try {
var lines = new ArrayList<>();
var lyrics = jSONObject.getJSONObject("response").getJSONObject("lyrics");
if (lyrics.has("timestamps")) {
var timestamps = lyrics.getJSONArray("timestamps");
for (int i = 0; i < timestamps.length(); i++) {
var timestamp = timestamps.getJSONObject(i);
if (timestamp.has("line")) {
lines.add(timestamp.getString("line"));
}
if (timestamp.optString("interlude").length() > 0) {
lines.add("♪");
}
}
aVar.a = TextUtils.join("\n", lines);
} else if (lyrics.has("text")) {
var text = lyrics.getJSONArray("text");
for (int i = 0; i < text.length(); i++) {
lines.add(text.getString(i));
}
aVar.a = TextUtils.join("\n", lines);
} else {
L.e("vk", "No text found");
aVar.a = "No text found";
}
return aVar;
} catch (Exception e) {
L.e("vk", e);
aVar.a = "Ошибка получения текста" + "\n\n" + "Ошибка: \n" + e.getLocalizedMessage();
return aVar;
}
}
public static class a {
public String a;
}
}
@@ -0,0 +1,132 @@
package com.vk.core.drawable;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import ru.vtosters.lite.themes.utils.RecolorUtils;
public class RecoloredDrawable extends Drawable implements Drawable.Callback {
private Drawable drawable;
private ColorStateList colorStateList;
private int c;
private int d;
public RecoloredDrawable(Drawable drawable, ColorStateList colorStateList) {
this.c = 255;
this.d = 255;
try {
this.drawable = drawable.mutate();
} catch (Exception e) {
e.printStackTrace();
}
this.drawable.setCallback(this);
this.colorStateList = RecolorUtils.themeCSL(colorStateList);
onStateChange(new int[0]);
}
public void a(int i) {
a(new ColorStateList(new int[][]{new int[0]}, new int[]{i}));
}
@Override
public void draw(@NonNull Canvas canvas) {
this.drawable.draw(canvas);
}
@Override
public int getIntrinsicHeight() {
return this.drawable.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return this.drawable.getIntrinsicWidth();
}
@Override
public int getOpacity() {
return this.drawable.getOpacity();
}
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
invalidateSelf();
}
@Override
public boolean isStateful() {
return this.colorStateList.isStateful();
}
@Override
protected boolean onStateChange(int[] iArr) {
super.onStateChange(iArr);
ColorStateList colorStateList = this.colorStateList;
this.drawable.setColorFilter(colorStateList.getColorForState(iArr, colorStateList.getDefaultColor()), PorterDuff.Mode.SRC_IN);
int length = iArr.length;
boolean z = false;
int i = 0;
while (true) {
if (i >= length) {
break;
} else if (iArr[i] == 0x010100a1) {
z = true;
break;
} else {
i++;
}
}
this.drawable.setState(iArr);
this.drawable.setAlpha(z ? this.c : this.d);
return true;
}
@Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long j) {
scheduleSelf(runnable, j);
}
@Override
public void setAlpha(int i) {
this.c = i;
this.d = i;
this.drawable.setAlpha(i);
}
@Override
public void setBounds(int i, int i2, int i3, int i4) {
super.setBounds(i, i2, i3, i4);
this.drawable.setBounds(i, i2, i3, i4);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
unscheduleSelf(runnable);
}
public void a(ColorStateList colorStateList) {
this.colorStateList = colorStateList;
onStateChange(getState());
invalidateSelf();
}
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;
}
}
@@ -191,7 +191,7 @@ public class DownloadUtils {
if (!file.exists()) {
return false;
}
Intent intent = new Intent("android.intent.action.VIEW");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
var mimeType = FileUtils.f(file);
var uri = FileUtils.uriFromFile(file);
@@ -222,7 +222,7 @@ public class DownloadUtils {
var mimeType = FileUtils.f(file);
var uri = FileUtils.uriFromFile(file);
Intent intent = new Intent("android.intent.action.VIEW");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimeType);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Log.d("DownloadUtils", "openFile: " + file.getAbsolutePath() + "| mimetype " + mimeType + "| uri " + uri);
@@ -0,0 +1,270 @@
package com.vk.core.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import b.h.z.e;
import com.vk.core.drawable.RecoloredDrawable;
import com.vk.core.extensions.TextViewExt;
import com.vk.core.ui.themes.MilkshakeHelper;
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;
public final class VerifyInfoHelper {
public static final VerifyInfoHelper h;
public static int devIcon = R.drawable.ic_favorite_16;
public static Integer enumSwitchMapping(VerifyInfoHelper.ColorTheme color) {
return switch (color) {
case normal -> 1;
case light -> 2;
case ultraLight -> 3;
case white -> 4;
};
}
public enum ColorTheme {
white,
normal,
light,
ultraLight
}
static {
h = new VerifyInfoHelper();
}
private VerifyInfoHelper() {
}
private int a() {
return ThemesUtils.getAccentColor();
}
private int b() {
return ThemesUtils.getAccentColor();
}
private int c() {
return ThemesUtils.getColor(R.color.fire_orange);
}
private int d() {
return ThemesUtils.getColor(R.color.light_blue);
}
private int e() {
return ThemesUtils.getColor(R.color.white_alpha60);
}
private int f() {
return ThemesUtils.getColor(R.color.white);
}
public int a(ColorTheme colorTheme) {
return switch (enumSwitchMapping(colorTheme)) {
case 1, 2 -> c();
case 3 -> e();
case 4 -> d();
default -> 0;
};
}
public Drawable a(VerifyInfo verifyInfo, Context context) {
return a(this, verifyInfo, context, null, 4, null);
}
public Drawable a(boolean z, boolean z2, boolean dev, Context context) {
return b(this, z, z2, dev, context, null, 8, null);
}
public Drawable a(boolean z, boolean z2, Context context) {
return b(this, z, z2, false, context, null, 8, null);
}
public int b(ColorTheme colorTheme) {
return a(colorTheme);
}
public int c(ColorTheme colorTheme) {
return switch (enumSwitchMapping(colorTheme)) {
case 1 -> MilkshakeHelper.e() ? b() : a();
case 2 -> d();
case 3 -> e();
case 4 -> f();
default -> 0;
};
}
public Drawable b(Context context, VerifyInfo verifyInfo) {
if (verifyInfo.dev()) {
return new RecoloredDrawable(ContextCompat.getDrawable(context, devIcon), c(ColorTheme.normal));
} else if (verifyInfo.t1() && verifyInfo.u1()) {
return ContextCompat.getDrawable(context, e.ic_fire_verified_border_composite_20_xml);
} else if (verifyInfo.t1()) {
return ContextCompat.getDrawable(context, e.ic_fire_border_composite_20_xml);
} else if (verifyInfo.u1()) {
return new RecoloredDrawable(ContextCompat.getDrawable(context, e.verified_16), c(ColorTheme.normal));
}
return null;
}
public Drawable b(VerifyInfo verifyInfo, Context context, ColorTheme colorTheme) {
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));
} else if (verifyInfo.t1() && verifyInfo.u1()) {
return ContextCompat.getDrawable(context, e.ic_fire_verified_border_composite_20_xml);
} else if (verifyInfo.t1()) {
return ContextCompat.getDrawable(context, e.ic_fire_border_composite_20_xml);
} else if (verifyInfo.u1() && VKThemeHelper.s()) {
return ContextCompat.getDrawable(context, e.verified_badge_light_24);
} else if (verifyInfo.u1() && VKThemeHelper.r()) {
return ContextCompat.getDrawable(context, e.verified_badge_dark_24);
}
return null;
}
public Drawable b(boolean z, boolean z2, Context context, ColorTheme colorTheme) {
return b(z, z2, false, context, colorTheme);
}
public Drawable b(boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme) {
int i = 0;
int c2 = 0;
if (dev) {
i = devIcon;
c2 = c(colorTheme);
} else if (z2 && z) {
i = e.ic_fire_verified_16;
c2 = b(colorTheme);
} else if (z2) {
i = e.ic_fire_16;
c2 = a(colorTheme);
} else if (z) {
i = e.verified_16;
c2 = c(colorTheme);
} else {
Log.d("VerifyInfoHelper", "Use VerifyInfo.has()");
}
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);
}
public Drawable a(boolean z, boolean z2, boolean dev, Context context, ColorTheme colorTheme) {
int i = 0;
int c2 = 0;
if (dev) {
i = devIcon;
c2 = c(colorTheme);
} else if (z2 && z) {
i = e.ic_fire_verified_12;
c2 = b(colorTheme);
} else if (z2) {
i = e.ic_fire_12;
c2 = a(colorTheme);
} else if (z) {
i = e.verified_12;
c2 = c(colorTheme);
} else {
Log.d("VerifyInfoHelper", "Use VerifyInfo.has()");
}
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();
if (verifyInfo == null || !verifyInfo.v1() || context == null) {
drawable = null;
} else {
drawable = z ? a(verifyInfo, context, colorTheme) : b(verifyInfo, context, colorTheme);
}
TextViewExt.a(textView, drawable);
}
public void a(ImageView imageView, boolean z, VerifyInfo verifyInfo) {
Drawable b2;
if (verifyInfo != null && verifyInfo.v1()) {
if (z) {
Context context = imageView.getContext();
b2 = a(context, verifyInfo);
} else {
Context context2 = imageView.getContext();
b2 = b(context2, verifyInfo);
}
imageView.setImageDrawable(b2);
ViewExtKt.r(imageView);
return;
}
ViewExtKt.p(imageView);
}
}
@@ -0,0 +1,239 @@
package com.vk.im.engine.commands.dialogs;
import androidx.collection.ArraySet;
import b.h.g.p.Transliteration;
import com.vk.auth.y.a.BuildConfig;
import com.vk.im.engine.ImEnvironment;
import com.vk.im.engine.commands.etc.ProfilesGetCmd;
import com.vk.im.engine.commands.etc.ProfilesInfoGetArgs;
import com.vk.im.engine.i.BaseImEngineCmd;
import com.vk.im.engine.internal.storage.utils.StringMatchStrategy;
import com.vk.im.engine.models.*;
import com.vk.im.engine.models.dialogs.DialogMember;
import com.vk.im.engine.utils.ImDialogsUtils;
import kotlin.NoWhenBranchMatchedException;
import kotlin.collections.l;
import kotlin.jvm.b.Functions2;
import kotlin.jvm.internal.Intrinsics;
import kotlin.sequences.m;
import kotlin.text.Regex;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
public final class DialogGetMentionSuggestionCmd extends BaseImEngineCmd<Suggestion> {
private final int f2365b;
private final String c;
private final Source d;
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;
this.d = source;
this.e = z;
this.f = obj;
}
private ProfilesInfo a(ImEnvironment imEnvironment, Collection<Member> collection, Source source, boolean z, Object obj) {
ProfilesInfoGetArgs.a aVar = new ProfilesInfoGetArgs.a();
aVar.a(collection);
aVar.a(source);
aVar.a(z);
aVar.a(obj);
Object a2 = null;
try {
a2 = imEnvironment.a(this, new ProfilesGetCmd(aVar.a()));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
Intrinsics.a(a2, "env.submitCommandDirect(this, cmd)");
return (ProfilesInfo) a2;
}
private String a(String str) {
String a2 = new Regex("\\W*").a(str, BuildConfig.g);
int length = a2.length() - 1;
int i = 0;
boolean z = false;
while (i <= length) {
boolean z2 = a2.charAt(!z ? i : length) <= ' ';
if (z) {
if (!z2) {
break;
}
length--;
} else if (z2) {
i++;
} else {
z = true;
}
}
return a2.subSequence(i, length + 1).toString();
}
private Collection<Member> a(ImEnvironment imEnvironment, int i) {
var d = imEnvironment.a0().f().b().d(i);
var arrayList = new ArrayList(l.a(d, 10));
for (DialogMember dialogMember : d) {
arrayList.add(dialogMember.Z());
}
// changed
var fakeMember = new Member();
fakeMember.b(MemberType.CUSTOM_BOT);
arrayList.add(fakeMember);
return arrayList;
}
private Collection<Member> a(ImEnvironment imEnvironment, int i, String str) {
if (a(str).length() == 0) {
return l.a();
}
List<String> c = l.c(Transliteration.a.a(str), Transliteration.a.b(str));
ArraySet arraySet = new ArraySet();
for (String str2 : c) {
arraySet.addAll(imEnvironment.a0().m().a(i, str2, StringMatchStrategy.STARTING_WITH));
}
// changed
var fakeMember = new Member();
fakeMember.b(MemberType.CUSTOM_BOT);
arraySet.add(fakeMember);
return arraySet;
}
private Comparator<Member> a(List<Member> list) {
return new a(list);
}
private void a(ImEnvironment imEnvironment, int i, Source source, boolean z, Object obj) {
try {
imEnvironment.a(this, new DialogGetMembersCmd(i, source, z, obj));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private List<Member> b(ImEnvironment imEnvironment, int i) {
return imEnvironment.a0().j().a(i, Weight.d.c(), Direction.BEFORE, 100);
}
@Override // com.vk.im.engine.i.ImEngineCmd
public Suggestion a(ImEnvironment imEnvironment) {
Collection<Member> a2;
Source source;
if (ImDialogsUtils.d(this.f2365b) != PeerType.CHAT) {
return new Suggestion((EntityValue) null, (ProfilesInfo) null);
}
Member Z = imEnvironment.Z();
int d = imEnvironment.a0().n().d();
Integer e = imEnvironment.a0().f().b().e(this.f2365b);
boolean z = false;
boolean z2 = e == null;
boolean z3 = e == null || e.intValue() != d;
boolean z4 = z2 || z3;
Source source2 = this.d;
boolean z5 = source2 == Source.NETWORK || (z4 && source2 == Source.ACTUAL);
if (z5) {
a(imEnvironment, this.f2365b, this.d, this.e, this.f);
z3 = false;
z2 = false;
}
List<Member> b2 = b(imEnvironment, this.f2365b);
if (this.c.length() == 0) {
z = true;
}
if (z) {
a2 = a(imEnvironment, this.f2365b);
} else if (z) {
throw new NoWhenBranchMatchedException();
} else {
a2 = a(imEnvironment, this.f2365b, this.c);
}
var sequense = l.d(a2);
var l = m.l(m.a(m.b(sequense, new Functions2<Member, Boolean>() {
@Override
public Boolean invoke(Member member) {
return member.e(Z) && (member.a(MemberType.USER) || member.a(MemberType.GROUP));
}
}), a(b2)));
EntityValue entityValue = new EntityValue(z2 ? null : l, z3);
switch (this.d)
{
case CACHE:
source = Source.CACHE;
break;
case ACTUAL:
source = Source.ACTUAL;
break;
case NETWORK:
source = Source.NETWORK;
break;
default:
throw new NoWhenBranchMatchedException();
}
return new Suggestion(entityValue, a(imEnvironment, l, source, this.e, this.f));
}
public boolean equals(Object obj) {
if (this != obj) {
if (obj instanceof DialogGetMentionSuggestionCmd) {
DialogGetMentionSuggestionCmd dialogGetMentionSuggestionCmd = (DialogGetMentionSuggestionCmd) obj;
return this.f2365b == dialogGetMentionSuggestionCmd.f2365b && this.e == dialogGetMentionSuggestionCmd.e;
}
return false;
}
return true;
}
public int hashCode() {
int i = this.f2365b;
String str = this.c;
int i2 = 0;
int hashCode = str != null ? str.hashCode() : 0;
Source source = this.d;
int hashCode2 = source != null ? source.hashCode() : 0;
boolean z = this.e;
int i3 = z ? 1 : 0;
if (z) {
i3 = 1;
}
Object obj = this.f;
if (obj != null) {
i2 = obj.hashCode();
}
return (((((((i * 31) + hashCode) * 31) + hashCode2) * 31) + i3) * 31) + i2;
}
public String toString() {
return "DialogGetMentionSuggestionCmd(dialogId=" + this.f2365b + ", query=" + this.c + ", source=" + this.d + ", isAwaitNetwork=" + this.e + ", changerTag=" + this.f + ")";
}
}
@@ -0,0 +1,32 @@
package com.vk.im.engine.models;
public enum MemberType {
UNKNOWN(0),
USER(1),
GROUP(2),
EMAIL(3),
CONTACT(4),
CUSTOM_BOT(5);
private final int mTypeAsInt;
MemberType(int i) {
this.mTypeAsInt = i;
}
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;
}
throw new IllegalArgumentException("Unknown member type value: " + i);
}
public int a() {
return this.mTypeAsInt;
}
}
@@ -36,5 +36,6 @@ public enum DialogAction {
unpinmsg,
ENCRYPT_SETT,
ENCRYPT,
DOWNLOAD
DOWNLOAD,
STAT
}
@@ -0,0 +1,72 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.vk.im.ui.components.viewcontrollers.msg_list.adapter;
import android.util.SparseIntArray;
import androidx.annotation.ColorInt;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.vk.im.engine.j.StickersAnimationLoader;
import com.vk.im.engine.models.ImExperimentsProvider;
import com.vk.im.engine.models.Member;
import com.vk.im.engine.models.ProfilesSimpleInfo;
import com.vk.im.engine.models.attaches.Attach;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.engine.models.messages.NestedMsg;
import com.vk.im.ui.media.audio.AudioTrack;
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
public class MsgPartHolderBindArgs {
public SparseIntArray A;
public AudioTrack B;
public AudioMsgInfo C;
public StickerAnimationState D;
public StickersAnimationLoader E;
@Nullable
public OnSpanClickListener F;
@Nullable
public OnSpanLongPressListener G;
@Nullable
public MsgListAdapterCallback H;
@Nullable
public VideoAutoPlayer I;
public Msg a;
public NestedMsg b;
public CharSequence c;
public Attach d;
public List<Attach> e;
public boolean f;
public int g;
public int h;
public int i;
public int j;
public int k;
@ColorInt
public int l;
public Member m;
public ProfilesSimpleInfo n;
public ImExperimentsProvider o;
public int p;
public int q;
public int r;
public boolean s;
public boolean t;
public boolean u;
public boolean v;
public boolean w;
public boolean x;
public boolean y;
public boolean isSelected;
public SparseIntArray z;
public MsgPartHolderBindArgs() {
}
}
@@ -0,0 +1,547 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.vk.im.ui.components.viewcontrollers.msg_list.adapter;
import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.vk.core.network.TimeProvider;
import com.vk.im.engine.j.StickersAnimationLoader;
import com.vk.im.engine.models.ImBgSyncState;
import com.vk.im.engine.models.ImExperimentsProvider;
import com.vk.im.engine.models.Member;
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.utils.collection.IntSet;
import com.vk.im.ui.components.viewcontrollers.msg_list.entry.AdapterEntry;
import com.vk.im.ui.media.audio.AudioTrack;
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;
public class VhBindArgs {
public MsgListAdapterCallback A;
@Nullable
public VideoAutoPlayer B = null;
@Nullable
public MsgIdType C = null;
public int D = -1;
public AdapterEntry a;
public AdapterEntry b;
public AdapterEntry c;
public int d;
@Nullable
public Dialog e;
@NonNull
public DialogTheme f;
public Member g;
public ProfilesSimpleInfo h;
public int i;
public boolean j;
public boolean k;
public boolean l;
public long m;
public ImExperimentsProvider n;
public ImBgSyncState o;
public int p;
public boolean q;
public IntSet r;
public SparseIntArray s;
public SparseIntArray t;
public AudioTrack u;
public AudioMsgInfo v;
public StickerAnimationState w;
public StickersAnimationLoader x;
@Nullable
public OnSpanClickListener y;
@Nullable
public OnSpanLongPressListener z;
public VhBindArgs() {
}
private long K() {
Msg var1 = this.b.c;
return var1 == null ? Long.MAX_VALUE : TimeProvider.f.b() - var1.getTime();
}
private boolean L() {
boolean var1;
if (this.K() <= this.m && !TimeProvider.f.c()) {
var1 = false;
} else {
var1 = true;
}
return var1;
}
public boolean A() {
AdapterEntry var1 = this.a;
boolean var2 = false;
boolean var3 = var2;
if (var1 != null) {
if (var1.d()) {
var3 = var2;
} else {
Msg var4 = this.a.c;
Msg var5 = this.b.c;
var3 = var2;
if (var4.W() == var5.W()) {
var3 = var2;
if (var4.e0() == var5.e0()) {
var3 = true;
}
}
}
}
return var3;
}
public boolean B() {
AdapterEntry var1 = this.a;
boolean var2;
if (var1 != null && var1.c()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
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;
}
return var2;
}
public boolean D() {
AdapterEntry var1 = this.a;
boolean var2;
if (var1 != null && var1.a == 83) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean E() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).i2()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean F() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).F0()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean G() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null && var1.a == 51) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public int H() {
return this.b.i;
}
public boolean I() {
long var1 = this.K();
long var3 = this.m;
boolean var5;
if (var1 > var3 && var1 < var3 * 2L) {
var5 = true;
} else {
var5 = false;
}
return var5;
}
public boolean J() {
Msg var1 = this.b.c;
boolean var2 = false;
if (var1 == null) {
return false;
} else if (!(var1 instanceof MsgFromUser)) {
return false;
} else {
MsgFromUser var7 = (MsgFromUser)var1;
Msg var3 = this.a.c;
boolean var4;
if (var3 != null && var3.d() == MsgSyncState.DONE) {
var4 = true;
} else {
var4 = false;
}
boolean var5;
if (this.o == ImBgSyncState.CONNECTED) {
var5 = true;
} else {
var5 = false;
}
boolean var6 = var2;
if (!var7.H0()) {
var6 = var2;
if (var5) {
var6 = var2;
if (!this.L()) {
var6 = var2;
if (var4) {
var6 = true;
}
}
}
}
return var6;
}
}
public int a() {
boolean var1 = this.h();
byte var2 = 0;
int var3 = var2;
if (var1) {
AttachAudioMsg var4 = (AttachAudioMsg)((MsgFromUser)this.b.c).c(AttachAudioMsg.class, false);
var3 = var2;
if (var4 != null) {
var3 = var4.f();
}
}
return var3;
}
public int b() {
Msg var1 = this.b.c;
int var2;
if (var1 != null) {
var2 = var1.B1();
} else {
var2 = 0;
}
return var2;
}
public boolean c() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).I0()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean d() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof WithUserContent && ((WithUserContent)var1).D0() != null) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean e() {
boolean var1;
if (this.b.i > 0) {
var1 = true;
} else {
var1 = false;
}
return var1;
}
public boolean f() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null) {
Msg var3 = var1.c;
if (var3 != null && var3.P1()) {
var2 = true;
return var2;
}
}
var2 = false;
return var2;
}
public boolean g() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).G0()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean h() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).Z1()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean i() {
boolean var1;
if (this.j && !this.u() && !this.l()) {
var1 = true;
} else {
var1 = false;
}
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;
}
return var1;
}
public boolean k() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.U1()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean l() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.V1()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean m() {
Dialog var1 = this.e;
boolean var2;
if (var1 != null && var1.W1()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean n() {
int var1 = this.b.a;
return var1 == 52 || var1 == 53 || var1 == 56 || var1 == 57 || var1 == 70 || var1 == 77 || var1 == 85 || var1 == 94 || var1 == 95;
}
public boolean o() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null && var1.a == 101) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean p() {
Msg var1 = this.b.c;
boolean var2;
if (var1 != null && var1.G1()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean q() {
AdapterEntry var1 = this.b;
boolean var3;
if (var1 != null) {
int var2 = var1.a;
if (var2 == 59 || var2 == 58 || var2 == 61 || var2 == 60) {
var3 = true;
return var3;
}
}
var3 = false;
return var3;
}
public boolean r() {
Msg var1 = this.b.c;
boolean var2;
if (var1 instanceof MsgFromUser && ((MsgFromUser)var1).e2()) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean s() {
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();
};
return false;
}
public boolean t() {
AdapterEntry var1 = this.b;
boolean var2;
if (var1 != null) {
Msg var3 = var1.c;
if (var3 != null && !var3.b(this.g)) {
var2 = true;
return var2;
}
}
var2 = false;
return var2;
}
public boolean u() {
Msg var1 = this.b.c;
if (var1 != null) {
return var1.b(this.g);
} else {
throw new RuntimeException();
}
}
public boolean v() {
IntSet var1 = this.r;
boolean var2;
if (var1 != null && var1.a(this.b.c.getLocalId())) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean w() {
return this.z() ^ true;
}
public boolean x() {
AdapterEntry var1 = this.c;
boolean var2;
if (var1 != null && var1.a == 100) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
public boolean y() {
AdapterEntry var1 = this.c;
boolean var2;
if (var1 != null && var1.a == 96) {
var2 = true;
} else {
var2 = false;
}
return var2;
}
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;
}
return var2;
}
public boolean isSelected() {
return this.b != null && this.b.c != null && this.r.a(this.b.c.getLocalId());
}
}
@@ -0,0 +1,176 @@
package com.vk.im.ui.components.viewcontrollers.msg_list.adapter.msgparts;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.vk.core.util.DurationFormatter;
import com.vk.im.engine.models.CallState;
import com.vk.im.engine.models.Member;
import com.vk.im.engine.models.attaches.Attach;
import com.vk.im.engine.models.attaches.AttachCall;
import com.vk.im.engine.models.dialogs.BubbleColors;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.ui.components.viewcontrollers.msg_list.adapter.MsgPartHolderBase;
import com.vk.im.ui.components.viewcontrollers.msg_list.adapter.MsgPartHolderBindArgs;
import com.vk.im.ui.components.viewcontrollers.msg_list.adapter.utils.MsgTimeFormatter;
import com.vk.im.ui.j;
import com.vk.im.ui.m;
import com.vk.im.ui.n;
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;
public final class MsgPartCallHolder extends MsgPartHolderBase<AttachCall> {
public static final a I = new a();
private final Context C;
private final DurationFormatter D;
private final StringBuilder E;
private final MsgTimeFormatter F;
private final StringBuilder G;
private final MsgPartIconTwoRowView H;
private boolean incoming;
public MsgPartCallHolder(MsgPartIconTwoRowView var1) {
this.H = var1;
this.C = this.H.getContext();
Context var2 = this.H.getContext();
Intrinsics.a(var2, "view.context");
this.D = new DurationFormatter(var2);
this.E = new StringBuilder();
this.F = MsgTimeFormatter.f;
this.G = new StringBuilder();
}
private final void a(Msg var1) {
this.G.setLength(0);
MsgTimeFormatter var2 = this.F;
Context var3 = this.C;
Intrinsics.a(var3, "context");
var2.a(var1, var3, this.G);
this.H.setTimeText(this.G);
}
private final void a(boolean var1) {
MsgPartIconTwoRowView var2 = this.H;
int var3;
if (var1) {
var3 = R.drawable.vkim_ic_attach_video_call;
} else {
var3 = R.drawable.vkim_ic_attach_call;
}
var2.setIcon(var3);
}
private final void a(boolean var1, AttachCall var2) {
boolean var3 = !var1;
if (var2.h() == CallState.ERROR) {
this.H.setSubtitleText(m.vkim_msg_list_call_error);
this.H.setSubtitleTextAppearance(n.VkIm_MsgPart_Call_Failure);
} else if (var2.h() == CallState.DONE) {
this.E.setLength(0);
this.D.a(var2.a(), this.E);
this.H.setSubtitleText(this.E);
this.H.setSubtitleTextAppearance(n.VkIm_MsgPart_Call_Duration);
} else if (var1) {
this.H.setSubtitleText(m.vkim_msg_list_call_missed);
this.H.setSubtitleTextAppearance(n.VkIm_MsgPart_Call_Failure);
} else if (var2.h() == CallState.CANCELLED) {
this.H.setSubtitleText(m.vkim_msg_list_call_cancelled);
this.H.setSubtitleTextAppearance(n.VkIm_MsgPart_Call_Failure);
} else if (var2.h() == CallState.DECLINED) {
this.H.setSubtitleText(m.vkim_msg_list_call_declined);
this.H.setSubtitleTextAppearance(n.VkIm_MsgPart_Call_Failure);
}
}
private void b(boolean var1) {
incoming = var1;
if (incoming) {
this.H.setTitleText(m.vkim_msg_list_call_incoming);
} else {
this.H.setTitleText(m.vkim_msg_list_call_outgoing);
}
}
private void c() {
this.H.setTimeText(null);
}
private void c(MsgPartHolderBindArgs var1) {
super.f = var1.H;
super.g = var1.a;
super.h = var1.b;
Attach var2 = var1.d;
if (var2 != null) {
super.B = (AttachCall) var2;
} else {
throw new TypeCastException("null cannot be cast to non-null type com.vk.im.engine.models.attaches.AttachCall");
}
}
protected View b(LayoutInflater var1, ViewGroup var2) {
this.H.setOnClickListener(var11 -> {
if (incoming) {
CallsHook.forwardToVkOffApps(C, super.B.f().getId());
} else {
CallsHook.forwardToVkOffApps(C, super.B.g().getId());
}
});
return this.H;
}
protected void b(BubbleColors var1) {
this.a(this.H, var1);
}
protected void b(MsgPartHolderBindArgs var1) {
Msg var2 = var1.a;
if (var2 != null) {
Attach var3 = var1.d;
if (var3 != null) {
AttachCall var4 = (AttachCall) var3;
Member var5 = var4.f();
Member var8 = var1.m;
Intrinsics.a(var8, "bindArgs.currentMember");
boolean var6 = var5.e(var8);
boolean var7 = var4.i();
this.c(var1);
this.b(var6);
this.a(var6, var4);
this.a(var7);
if (var1.f) {
this.a(var2);
} else {
this.c();
}
} else {
throw new TypeCastException("null cannot be cast to non-null type com.vk.im.engine.models.attaches.AttachCall");
}
} else {
Intrinsics.a();
throw null;
}
}
public static final class a {
private a() {
}
public MsgPartCallHolder a(LayoutInflater var1, ViewGroup var2) {
View var3 = var1.inflate(j.vkim_msg_part_call, var2, false);
if (var3 != null) {
return new MsgPartCallHolder((MsgPartIconTwoRowView) var3);
} else {
throw new TypeCastException("null cannot be cast to non-null type com.vk.im.ui.views.msg.MsgPartIconTwoRowView");
}
}
}
}
@@ -0,0 +1,48 @@
package com.vk.im.ui.components.viewcontrollers.msg_list.adapter.msgparts;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.vk.im.engine.models.attaches.Attach;
import com.vk.im.engine.models.dialogs.BubbleColors;
import com.vk.im.ui.components.viewcontrollers.msg_list.adapter.MsgPartHolderBase;
import com.vk.im.ui.components.viewcontrollers.msg_list.adapter.MsgPartHolderBindArgs;
import com.vk.im.ui.views.msg.MsgPartTextView;
import com.vk.im.ui.views.msg.WithTime;
import com.vtosters.lite.R;
public class MsgPartTextHolder extends MsgPartHolderBase<Attach> {
private FrameLayout mContainer;
private MsgPartTextView mTextPart;
@Override
protected View b(LayoutInflater inflater, ViewGroup parent) {
mTextPart = (MsgPartTextView) inflater.inflate(R.layout.vkim_msg_part_text, parent, false);
mTextPart.setOnClickListener(view -> {
MsgPartTextHolder.this.f.a(MsgPartTextHolder.this.g.getLocalId());
});
mTextPart.setOnLongClickListener(view -> {
if(!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId());
return false;
});
return mTextPart;
}
@Override
protected void b(BubbleColors colors) {
mTextPart.setTimeTextColor(colors.h);
mTextPart.setLinkTextColor(colors.d);
mTextPart.setTextColor(colors.f);
}
@Override
protected void b(MsgPartHolderBindArgs bindArgs) {
mTextPart.setOnSpanClickListener(bindArgs.F);
mTextPart.setOnSpanLongPressListener(bindArgs.G);
mTextPart.setText(bindArgs.c);
mTextPart.setTextIsSelectable(bindArgs.isSelected);
a(bindArgs, (WithTime) mTextPart);
}
}
@@ -0,0 +1,166 @@
package com.vk.im.ui.q.h.e;
import android.app.Activity;
import android.content.Context;
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;
import com.vk.core.util.ContextExtKt;
import com.vk.core.util.Screen;
import com.vk.extensions.ViewExtKt;
import com.vk.im.engine.ImEngine;
import com.vk.im.engine.models.Member;
import com.vk.im.engine.models.ProfilesInfo;
import com.vk.im.engine.models.Suggestion;
import com.vk.im.ui.components.dialog_mention.DialogMentionComponent;
import com.vk.im.ui.components.dialog_mention.DialogMentionComponentCallback;
import com.vk.im.ui.formatters.MentionNameFormatter;
import com.vk.im.ui.utils.j.MentionSuggestionsSupplier;
import com.vk.im.ui.utils.j.MentionsManager;
import com.vk.im.ui.views.RichEditText;
import com.vtosters.lite.R;
public final class MentionsController {
private final RichEditText a;
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();
}
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.mMentionsManager = new MentionsManager(this.a, new MentionSuggestionsSupplier() {
@Override
public boolean a() {
if(MentionsController.this.state)
return ViewExtKt.i(MentionsController.this.mCoordinatorLayout);
return false;
}
@Override
public boolean a(String str) {
if (MentionsController.this.c().c())
return false;
MentionsController.this.mDialogMentionComponent.a(MentionsController.this.d(), str);
MentionsController.this.e = true;
MentionsController.this.j();
return true;
}
@Override
public void b() {
MentionsController.this.mDialogMentionComponent.a(0, (String) null);
MentionsController.this.g();
}
});
this.mDialogMentionComponent = new DialogMentionComponent(this.mImEngine);
this.mDialogMentionComponent.a(new DialogMentionComponentCallback() {
@Override
public void a(Member member, ProfilesInfo profilesInfo) {
MentionsController.this.mMentionsManager.a(member, MentionNameFormatter.a.a(member, profilesInfo));
}
@Override
public void a(Suggestion suggestion) {
if (suggestion.b().b() == null || suggestion.b().b().isEmpty())
MentionsController.this.g();
else
MentionsController.this.i();
}
});
}
public final void g() {
this.e = false;
j();
}
private final 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);;
this.mBottomSheetBehavior = (VkBottomSheetBehavior<RecyclerView>) ((CoordinatorLayout.LayoutParams) viewGroup.getLayoutParams()).getBehavior();
this.mBottomSheetBehavior.a(new VkBottomSheetBehavior.b() {
@Override
public void a(View view, int i) {
if (i == 5) {
MentionsController.this.g();
}
}
});
this.mBottomSheetBehavior.b(true);
this.mBottomSheetBehavior.g(4);
this.mBottomSheetBehavior.e(Screen.a(160));
this.state = true;
}
public final void i() {
this.e = true;
j();
}
public final void j() {
if (this.k.d()) {
Context context = this.j.getContext();
Activity f = ContextExtKt.f(context);
boolean z = this.e && (Screen.k(f) || Screen.l(f));
if (z || this.state) {
if (!this.state) h();
this.mCoordinatorLayout.setVisibility(z ? View.VISIBLE : View.GONE);
if (z) return;
this.mCoordinatorLayout.postDelayed(() -> this.mBottomSheetBehavior.b(4), 300L);
}
}
}
public final void a() {
this.mDialogMentionComponent.g();
}
public final void b() {
this.mDialogMentionComponent.h();
}
public final b c() {
return this.k;
}
public final int d() {
return this.i;
}
public final void e() {
this.mDialogMentionComponent.q();
}
public final void f() {
this.mDialogMentionComponent.r();
}
}
@@ -0,0 +1,165 @@
package com.vk.music.view.x;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.vk.api.audio.AudioGetLyrics;
import com.vk.api.base.ApiCallback;
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;
import com.vk.music.dto.PlayerInfo;
import com.vk.music.player.PlayerModel;
import com.vk.music.player.TrackInfo;
import com.vk.profile.ui.BaseProfileFragment;
import com.vtosters.lite.LinkParserParams;
import com.vtosters.lite.R;
import com.vtosters.lite.ViewUtils;
import com.vtosters.lite.ui.MusicErrorViewHelper;
import com.vtosters.lite.ui.holder.RecyclerHolder;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/* loaded from: classes3.dex */
public class LyricsHolder extends RecyclerHolder<PlayerInfo> implements View.OnClickListener {
private final AtomicBoolean B;
private final MusicPlaybackLaunchContext C;
private final PodcastTimeCodeClickListener D;
private final LinkParserParams E;
private final TextView f18325c;
private final TextView f18326d;
private final TextView f18327e;
private final MusicErrorViewHelper f18328f;
private final View g;
private final View h;
public LyricsHolder(ViewGroup viewGroup, PlayerModel playerModel) {
super(R.layout.music_player_fr_lyrics, viewGroup);
this.B = new AtomicBoolean(false);
this.C = MusicPlaybackLaunchContext.p0.h(128);
this.E = new LinkParserParams();
this.f18325c = i(R.id.title);
this.f18326d = i(R.id.artist);
this.f18326d.setOnClickListener(this);
this.f18327e = i(R.id.lyrics);
this.g = i(R.id.progress);
this.h = i(R.id.content);
this.f18328f = new MusicErrorViewHelper.b(i(R.id.error)).a();
this.f18328f.a(new a());
this.f18328f.b(8);
this.D = new PodcastTimeCodeClickListener(viewGroup.getContext(), playerModel);
this.E.b(907);
this.E.a(this.D);
}
public void g0() {
TrackInfo trackInfo;
if (this.B.get() || (trackInfo = this.b.a) == null || !trackInfo.n() || trackInfo.e().G == 0) {
return;
}
MusicTrack e2 = trackInfo.e();
if (e2.G <= 0 || !TextUtils.isEmpty(e2.H)) {
return;
}
this.B.set(true);
this.f18328f.b(8);
ViewUtils.a(this.g, 0);
ViewUtils.a(this.h, 8);
new AudioGetLyrics(e2.G, e2.y1(), e2).a(new b(e2)).a();
}
@Override // android.view.View.OnClickListener
public void onClick(View view) {
Object obj = this.b;
MusicTrack e = ((PlayerInfo) obj).a != null ? ((PlayerInfo) obj).a.e() : null;
Activity e2 = ContextExtKt.e(view.getContext());
if (e == null || e2 == null) {
return;
}
if (e.D1()) {
new BaseProfileFragment.z(e.e).a(e2);
} else {
MusicArtistSelector.a(e2, e, this.b.d);
}
}
@Override // com.vtosters.lite.ui.holder.RecyclerHolder
public void b(PlayerInfo playerInfo) {
CharSequence charSequence;
TrackInfo trackInfo = playerInfo.a;
if (trackInfo == null || !trackInfo.n() || trackInfo.e().G == 0) {
return;
}
MusicTrack e2 = trackInfo.e();
this.f18325c.setText(e2.f);
this.f18325c.setSelected(true);
this.f18326d.setText(e2.C);
if (e2.D1()) {
this.E.a(e2.h);
this.D.a(e2, this.C);
charSequence = LinkParser.a(e2.H, this.E);
} else {
charSequence = e2.H;
}
if (!Objects.equals(this.f18327e.getText(), charSequence)) {
this.h.setScrollY(0);
}
this.f18327e.setText(charSequence);
g0();
}
class a implements View.OnClickListener {
a() {
}
@Override // android.view.View.OnClickListener
public void onClick(View view) {
LyricsHolder.this.g0();
}
}
public class b implements ApiCallback<AudioGetLyrics.a> {
final MusicTrack a;
b(MusicTrack musicTrack) {
this.a = musicTrack;
}
@Override // com.vk.api.base.ApiCallback
public void a(AudioGetLyrics.a aVar) {
this.a.H = aVar.a;
LyricsHolder.this.f18328f.b(8);
ViewUtils.a(LyricsHolder.this.g, 8);
ViewUtils.a(LyricsHolder.this.h, 0);
LyricsHolder.this.f0();
LyricsHolder.this.B.set(false);
}
@Override // com.vk.api.base.ApiCallback
public void a(VKApiExecutionException vKApiExecutionException) {
LyricsHolder.this.B.set(false);
LyricsHolder.this.f18328f.a(vKApiExecutionException);
LyricsHolder.this.f18328f.b(0);
ViewUtils.a(LyricsHolder.this.g, 8);
ViewUtils.a(LyricsHolder.this.h, 8);
}
}
}
@@ -1,7 +1,6 @@
package com.vk.sharing.view;
import static b.h.z.f.roboto_medium;
import static ru.vtosters.lite.utils.Preferences.preferences;
import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor;
import android.content.Context;
@@ -32,6 +31,7 @@ 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 java.util.Arrays;
@@ -208,7 +208,7 @@ public class TargetImageView extends VKMultiImageView {
@Override // android.view.View
protected void onDraw(Canvas canvas) {
Canvas canvas2 = this.f21631f;
int pref = preferences.getInt("pic_rounding", 0);
int pref = Preferences.getPreferences().getInt("pic_rounding", 0);
if (canvas2 != null) {
canvas2.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -0,0 +1,171 @@
package com.vk.stickers.details;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.Screen;
import com.vk.dto.stickers.StickerItem;
import com.vk.dto.stickers.StickerStockItem;
import com.vk.extensions.ViewExtKt;
import com.vk.imageloader.view.VKImageView;
import com.vk.stickers.views.VKStickerImageView;
import com.vtosters.lite.R;
import java.util.List;
public final class StickerDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<StickerItem> mItems;
private final StickerStockItem mItem;
private boolean mIsTelegramPack = false;
public StickerDetailsAdapter(StickerStockItem stickerStockItem) {
this.mItem = stickerStockItem;
this.mItems = this.mItem.T1();
}
public void setTelegramPack(boolean state) {
mIsTelegramPack = state;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return switch (i) {
case 0 -> new c(viewGroup);
case 1 -> new d(viewGroup);
case 2 -> new b(viewGroup);
default -> throw new IllegalArgumentException("Unknown viewType: " + i);
};
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
switch (getItemViewType(i)) {
case 0 -> ((c) viewHolder).a(this.mItem);
case 1 -> ((d) viewHolder).a(this.mItems.get(H(i)));
}
}
@Override
public int getItemCount() {
return mIsTelegramPack ? mItems.size() : this.mItems.size() + 2;
}
@Override
public int getItemViewType(int i) {
if (mIsTelegramPack) return 1;
if (i == 0) {
return 0;
}
return i == k() ? 2 : 1;
}
public int H(int i) {
return mIsTelegramPack ? i : i - j();
}
public boolean I(int i) {
return getItemViewType(i) == 1;
}
public int j() {
return 1;
}
public int k() {
return l() + 1;
}
public int l() {
return (this.mItems.size() + j()) - 1;
}
public int m() {
return 0;
}
public List<StickerItem> n() {
return this.mItems;
}
public boolean v() {
return false;
}
private static final class b extends RecyclerView.ViewHolder {
public b(ViewGroup viewGroup) {
super(ViewExtKt.a(viewGroup, R.layout.sticker_details_hold_to_view_item, false));
}
}
private final class c extends RecyclerView.ViewHolder {
private final VKImageView mImageBg;
private final VKImageView mPackImage;
private final ImageView mAnimPack;
private final TextView mPackTitle;
private final TextView mPackAuthor;
private final TextView mPackSubtitle;
private final TextView mDescription;
public c(ViewGroup viewGroup) {
super(LayoutInflater.from(VKThemeHelper.u()).inflate(R.layout.sticker_details_info_item, viewGroup, false));
this.mImageBg = this.itemView.findViewById(R.id.pack_image_bg);
this.mPackImage = this.itemView.findViewById(R.id.pack_image);
this.mAnimPack = this.itemView.findViewById(R.id.anim_pack);
this.mPackTitle = this.itemView.findViewById(R.id.pack_title);
this.mPackAuthor = this.itemView.findViewById(R.id.pack_author);
this.mPackSubtitle = this.itemView.findViewById(R.id.pack_subtitle);
this.mDescription = this.itemView.findViewById(R.id.description);
}
public void a(StickerStockItem stickerStockItem) {
this.mImageBg.a(stickerStockItem.R1());
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());
}
}
}
+93 -13
View File
@@ -221,8 +221,9 @@ public final class R {
public static final int targetlang_value = 0x7f030074; // 2130903156
public static final int undel_value = 0x7f030075; // 2130903157
public static final int wallpaper_change_dialog = 0x7f030076; // 2130903158
public static final int theme_type_name = 0x7f030077; // 2130903159
public static final int theme_type_name_checkbox = 0x7f030078; // 2130903160
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 class attr {
@@ -2136,6 +2137,7 @@ public final class R {
public static final int zOrderOnTop = 0x7f040773; // 2130970483
public static final int im_ic_msgdl = 0x7f040774; // 2130970484
public static final int im_ic_msgreload = 0x7f040775; // 2130970485
public static final int im_ic_stats = 0x7f040776; // 2130970486
}
public static final class bool {
@@ -2155,6 +2157,7 @@ public final class R {
public static final int spb_default_progressiveStart_activated = 0x7f05000d; // 2131034125
public static final int spb_default_reversed = 0x7f05000e; // 2131034126
public static final int vk_is_tablet = 0x7f05000f; // 2131034127
public static final int com_samsung_android_icon_container_has_icon_container = 0x7f050010; // 2131034128
}
public static final class color {
@@ -2964,6 +2967,7 @@ public final class R {
public static final int yellow_sunflower = 0x7f060324; // 2131100452
public static final int iclauncher = 0x7f060325; // 2131100453
public static final int abc_color_highlight_material = 0x7f060004; // 2131099652
public static final int alpha05 = 0x7f060326; // 2131100454
}
public static final class dimen {
@@ -4974,6 +4978,24 @@ public final class R {
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 checker_background = 0x7f080be8; // 2131233768
public static final int splash = 0x7f080be9; // 2131233769
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
@@ -11028,6 +11050,10 @@ public final class R {
public static final int savedmsg = 0x7f0a101e; // 2131365918
public static final int search_photo = 0x7f0a101f; // 2131365919
public static final int text_hex_wrapper = 0x7f0a1020; // 2131365920
public static final int color_preview = 0x7f0a1021; // 2131365921
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 class integer {
@@ -12848,6 +12874,7 @@ public final class R {
public static final int tgs_entry = 0x7f0d06de; // 2131560158
public static final int wppreview = 0x7f0d06df; // 2131560159
public static final int write_bar_i = 0x7f0d06e0; // 2131560160
public static final int color_picker_item = 0x7f0d06e1; // 2131560161
}
public static final class menu {
@@ -19517,17 +19544,52 @@ public final class R {
public static final int whitelisted_filters_groups_title = 0x7f121838; // 2131892280
public static final int why_news_not_updated_summ = 0x7f121839; // 2131892281
public static final int why_news_not_updated_title = 0x7f12183a; // 2131892282
public static final int blockminiapps_summ = 0x7f12183b; // 2131892283
public static final int blockminiapps_title = 0x7f12183c; // 2131892284
public static final int current_theme = 0x7f12183d; // 2131892285
public static final int invalid_pack_link = 0x7f12183e; // 2131892286
public static final int maxquality_summ = 0x7f12183f; // 2131892287
public static final int maxquality_title = 0x7f121840; // 2131892288
public static final int newsfeed_notif_summ = 0x7f121841; // 2131892289
public static final int newsfeed_notif_title = 0x7f121842; // 2131892290
public static final int promotedstickers_title = 0x7f121843; // 2131892291
public static final int vkx_integration_enabled_info = 0x7f121844; // 2131892292
public static final int writebar_ios_title = 0x7f121845; // 2131892293
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 class style {
@@ -20672,6 +20734,24 @@ public final class R {
public static final int Base_V26_Widget_AppCompat_Toolbar = 0x7f1300a1; // 2131951777
public static final int Base_V28_Theme_AppCompat = 0x7f1300a2; // 2131951778
public static final int Base_V28_Theme_AppCompat_Light = 0x7f1300a3; // 2131951779
public static final int Vk_Theme_VkApp_amoled_monet_generated = 0x7f130475; // 2131952757
public static final int Vk_Theme_VkApp_amoled_monet_old_generated = 0x7f130476; // 2131952758
public static final int Vk_Theme_VkApp_bright_light_monet_generated = 0x7f130477; // 2131952759
public static final int Vk_Theme_VkApp_client_dark_monet_generated = 0x7f130478; // 2131952760
public static final int Vk_Theme_VkApp_client_light_monet_generated = 0x7f130479; // 2131952761
public static final int Vk_Theme_VkApp_space_gray_monet_generated = 0x7f13047a; // 2131952762
public static final int VkAmoledMonetStyle = 0x7f13047b; // 2131952763
public static final int VkAmoledMonetTheme = 0x7f13047c; // 2131952764
public static final int VkDarkMonetStyle = 0x7f13047d; // 2131952765
public static final int VkDarkMonetTheme = 0x7f13047e; // 2131952766
public static final int VkLightMonetStyle = 0x7f13047f; // 2131952767
public static final int VkLightMonetTheme = 0x7f130480; // 2131952768
public static final int VkMilkAmoledMonetStyle = 0x7f130481; // 2131952769
public static final int VkMilkAmoledMonetTheme = 0x7f130482; // 2131952770
public static final int VkMilkDarkMonetStyle = 0x7f130483; // 2131952771
public static final int VkMilkDarkMonetTheme = 0x7f130484; // 2131952772
public static final int VkMilkLightMonetStyle = 0x7f130485; // 2131952773
public static final int VkMilkLightMonetTheme = 0x7f130486; // 2131952774
}
public static final class xml {
@@ -71,12 +71,11 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
this.mIcon.getLayoutParams().width = V.a(28.0F);
this.mIcon.getLayoutParams().height = V.a(28.0F);
var colorStateList = getCSTDock();
this.mSmallLabel = this.findViewById(R.id.smallLabel);
mSmallLabel.setTextColor(colorStateList);
mSmallLabel.setTextColor(getCSTDock());
this.mLargeLabel = this.findViewById(R.id.largeLabel);
mLargeLabel.setTextColor(colorStateList);
mLargeLabel.setTextColor(getCSTDock());
LayoutInflater.from(this.getContext()).inflate(R.layout.navigation_bottom_counter, this, true);
this.mCounterContainer = this.findViewById(R.id.counter_container);
@@ -280,12 +279,13 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
@SuppressLint("RestrictedApi")
public void setIcon(Drawable drawable) {
if (drawable != null) {
var csl = this.mColorStateList;
if (drawable != null && csl != null) {
if (drawable.getConstantState() != null)
drawable = drawable.getConstantState().newDrawable();
drawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTintList(drawable, this.mColorStateList);
DrawableCompat.setTintList(drawable, csl);
}
this.mIcon.setImageDrawable(drawable);
@@ -326,6 +326,8 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It
}
public void setTextColor(ColorStateList colorStateList) {
this.mSmallLabel.setTextColor(colorStateList);
this.mLargeLabel.setTextColor(colorStateList);
}
@SuppressLint("RestrictedApi")
@@ -25,14 +25,15 @@ public class VTExecutors {
}
static class VTMusicDownloadExecutor {
private VTMusicDownloadExecutor() {}
private static class LazyHolder {
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
private VTMusicDownloadExecutor() {
}
public static ExecutorService getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
}
}
}
@@ -1,5 +1,8 @@
package ru.vtosters.lite.deviceinfo;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.ThemesUtils;
import static ru.vtosters.lite.deviceinfo.OEMDetector.hasMiuiIncrCode;
import static ru.vtosters.lite.deviceinfo.OEMDetector.isEMUI;
import static ru.vtosters.lite.deviceinfo.OEMDetector.isMIUI;
@@ -89,8 +92,9 @@ public class Device {
public String toDeviceName() {
return "Device information: "
+ "commit='" + getBuildNumber()
+ "', sdkVersion=" + this.sdkVersion
+ ", productName='" + this.productName
+ "', isMilkshake='" + ThemesUtils.isMilkshake()
+ "', sdkVersion='" + this.sdkVersion
+ "', productName='" + this.productName
+ "', deviceName='" + this.deviceName
+ "', boardName='" + this.boardName
+ "', manufacturerName='" + this.manufacturerName
@@ -101,4 +105,20 @@ public class Device {
+ (isEMUI() ? "', EmuiVersion='" + this.EmuiVersion : "")
+ "', modelName='" + this.modelName + "'";
}
public String forLogging() {
return "**" + AndroidUtils.getString("device_info") + ":** " + "\n\n"
+ "- Commit: " + getBuildNumber() + "\n"
+ "- Android SDK: " + this.sdkVersion + "\n"
+ "- Product: " + this.productName + "\n"
+ "- Device: " + this.deviceName + "\n"
+ "- Board: " + this.boardName + "\n"
+ "- Manufacturer: " + this.manufacturerName + "\n"
+ "- Brand: " + this.brandName + "\n"
+ "- Model: " + this.modelName + "\n"
+ (isMIUI() ? "- Miui Version Code: " + this.MiuiUiVersionCode + "\n" + "- Miui Version Name: " + this.MiuiUiVersionName + "\n" : "")
+ (isSamsung() ? "- OneUi Major Version: " + this.OneUiMajorVersion + "\n" + "- OneUi Minor Version: " + this.OneUiMinorVersion + "\n" : "")
+ (hasMiuiIncrCode() ? "- Miui Version Incremental Code: " + this.MiuiIncrementalCode + "\n" : "")
+ (isEMUI() ? "- Emui Version: " + this.EmuiVersion : "");
}
}
@@ -23,8 +23,8 @@ public class OEMDetector {
static {
try {
getPropMtd = ReflectionUtils.getDeclaredMethod("android.os.SystemProperties", "get", String.class);
semPlatformIntField = ReflectionUtils.getDeclaredField(Build.VERSION.class, "SEM_PLATFORM_INT");
getPropMtd = ReflectionUtils.findMethod(Class.forName("android.os.SystemProperties"), "get", String.class);
semPlatformIntField = ReflectionUtils.findField(Build.VERSION.class, "SEM_PLATFORM_INT");
} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
}
@@ -70,8 +70,8 @@ public class OEMDetector {
}
}
public static boolean isIntentResolved(Context ctx, Intent intent ){
return (intent!=null && ctx.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null);
public static boolean isIntentResolved(Context ctx, Intent intent) {
return (intent != null && ctx.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null);
}
public static boolean detectMiuiV2() {
@@ -0,0 +1,13 @@
package ru.vtosters.lite.di.singleton;
import okhttp3.OkHttpClient;
public class VtOkHttpClient extends OkHttpClient {
public static VtOkHttpClient getInstance() {
return LazyHolder.instance;
}
private static class LazyHolder {
private static final VtOkHttpClient instance = new VtOkHttpClient();
}
}
@@ -1,20 +1,9 @@
package ru.vtosters.lite.dnr;
import static ru.vtosters.lite.dnr.DNRModule.hookDNR;
import static ru.vtosters.lite.dnr.DNRModule.hookDNT;
import static ru.vtosters.lite.dnr.DNRModule.isDnrEnabledFor;
import static ru.vtosters.lite.dnr.DNRModule.isDntEnabledFor;
import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.isTablet;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import com.vk.core.network.Network;
import com.vk.im.engine.ImEngine1;
import com.vk.im.engine.events.OnDialogUpdateEvent;
@@ -27,6 +16,16 @@ import com.vk.im.ui.components.common.MsgAction;
import com.vk.im.ui.components.viewcontrollers.popup.DelegateMsg;
import com.vk.im.ui.views.dialog_actions.DialogActionsListView;
import com.vtosters.lite.R;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.downloaders.messages.HtmlDialogDownloaderFormatProvider;
import ru.vtosters.lite.downloaders.messages.MessagesDownloader;
import ru.vtosters.lite.encryption.EncryptProvider;
import ru.vtosters.lite.encryption.base.IMProcessor;
import ru.vtosters.lite.hooks.CryptImHook;
import ru.vtosters.lite.ui.dialogs.Translate;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.File;
import java.io.IOException;
@@ -34,20 +33,16 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.downloaders.MessagesDownloader;
import ru.vtosters.lite.encryption.EncryptProvider;
import ru.vtosters.lite.encryption.base.IMProcessor;
import ru.vtosters.lite.hooks.CryptImHook;
import ru.vtosters.lite.ui.dialogs.Translate;
import ru.vtosters.lite.utils.AndroidUtils;
import static ru.vtosters.lite.dnr.DNRModule.*;
import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.isTablet;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
public class DNRInjector {
public static void inject(Dialog dialog, List<DialogAction> list) {
int peerId = 0;
if (dialog != null) {
try {
peerId = dialog.getId();
@@ -60,6 +55,7 @@ public class DNRInjector {
return;
}
list.add(DialogAction.STAT);
list.add(DialogAction.DOWNLOAD);
list.add(DialogAction.pinmsg);
@@ -85,6 +81,7 @@ public class DNRInjector {
}
public static LinkedHashMap<DialogAction, Integer> injectToHashMap(LinkedHashMap<DialogAction, Integer> hashMap) {
// hashMap.put(DialogAction.STAT, AndroidUtils.getIdentifier("dialogstats", "string"));
hashMap.put(DialogAction.DOWNLOAD, R.string.download_dl);
hashMap.put(DialogAction.DNR_ON, R.string.DNR_ON);
@@ -103,6 +100,8 @@ public class DNRInjector {
@SuppressLint("ResourceType")
public static List<Object> injectToList(List<Object> actions) {
var list = new ArrayList<>(actions);
// list.add(new DialogActionsListView.b.a(DialogAction.STAT, 1, R.attr.im_ic_stats, R.string.dialogstats)); // DialogAction, Int, Icon, String
list.add(new DialogActionsListView.b.a(DialogAction.DOWNLOAD, 1, R.attr.im_ic_msgdl, R.string.download_dl)); // DialogAction, Int, Icon, String
list.add(new DialogActionsListView.b.a(DialogAction.DNR_ON, 2, R.attr.im_ic_pinned_msg_hide, R.string.DNR_ON)); // DialogAction, Int, Icon, String
@@ -142,6 +141,8 @@ public class DNRInjector {
return actions;
}
actions.add(DialogAction.STAT);
actions.add(DialogAction.DOWNLOAD);
if (isDnrEnabledFor(peerId)) {
@@ -186,10 +187,15 @@ public class DNRInjector {
forceInvalidateDialogActions(dialog);
}
if (action == DialogAction.STAT) {
DNRModule.hookDialogInfo(dialog);
return true;
}
if (action == DialogAction.DOWNLOAD) {
File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "dialog-" + peerId + ".html");
try {
new MessagesDownloader().downloadDialog(peerId, new MessagesDownloader.HtmlDialogDownloaderFormatProvider(), out);
new MessagesDownloader().downloadDialog(peerId, new HtmlDialogDownloaderFormatProvider(), out);
} catch (Exception e) {
sendToast(AndroidUtils.getString(R.string.download_dl_error));
e.printStackTrace();
@@ -247,10 +253,14 @@ public class DNRInjector {
DNRModule.hookRead(dialog);
}
if (action == DialogAction.STAT) {
DNRModule.hookDialogInfo(dialog);
return true;
}
if (action == DialogAction.DOWNLOAD) {
File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), peerId + "-dialog.html");
try {
new MessagesDownloader().downloadDialog(peerId, new MessagesDownloader.HtmlDialogDownloaderFormatProvider(), out);
new MessagesDownloader().downloadDialog(peerId, new HtmlDialogDownloaderFormatProvider(), out);
} catch (Exception e) {
e.printStackTrace();
}
@@ -308,27 +318,14 @@ public class DNRInjector {
if (msg instanceof MsgFromUser) {
var text = ((MsgFromUser) msg).f();
var fullMsg = ((MsgFromUser) msg).j2(); // text + reply
var reply = fullMsg.substring(text.length() + 1);
var isTextExist = !text.isEmpty() && !text.equals(" ");
var isReplyExist = !reply.isEmpty() && !reply.equals(" ");
if (isReplyExist) {
reply = decryptMessage(reply, peerId);
}
if (isTextExist) {
text = decryptMessage(text, peerId);
}
if (action == MsgAction.valueOf("TRANSLATE")) {
if (isTextExist && isReplyExist) {
Translate.showTranslatedText(context, text + "\n\n" + context.getString(R.string.translator_reply) + "\n" + reply);
} else if (isReplyExist) {
Translate.showTranslatedText(context, context.getString(R.string.translator_reply) + "\n" + reply);
} else if (isTextExist) {
if (isTextExist) {
Translate.showTranslatedText(context, text);
} else {
sendToast(context.getString(R.string.translator_no_text));
@@ -4,16 +4,31 @@ import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import android.content.Intent;
import android.net.Uri;
import android.os.MessageQueue;
import android.util.Log;
import com.vk.core.util.LangUtils;
import com.vk.im.engine.commands.messages.SetUserActivityCmd;
import com.vk.im.engine.models.dialogs.Dialog;
import com.vk.im.engine.models.messages.Msg;
import java.util.List;
import com.vk.im.ui.fragments.ChatFragment;
import com.vk.navigation.Navigator;
import com.vk.webapp.VkUiFragment;
import com.vtosters.lite.general.fragments.WebViewFragment;
import ru.vtosters.lite.dnr.helpers.DoNotReadDBHelper;
import ru.vtosters.lite.dnr.helpers.DoNotTypeDBHelper;
import ru.vtosters.lite.ssfs.Utils;
import ru.vtosters.lite.ui.fragments.VKUIwrapper;
import ru.vtosters.lite.ui.fragments.VTSettings;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.ThemesUtils;
public class DNRModule {
public static DoNotReadDBHelper mDoNotReadDBHelper = new DoNotReadDBHelper();
@@ -63,6 +78,17 @@ public class DNRModule {
});
}
public static void hookDialogInfo(Dialog dialog) {
AndroidUtils.openWebView("https://vkscripts.ru/run/i/" +
AccountManagerUtils.getUserToken() +
"?peer_id=" +
dialog.getId() +
"&lang=" +
LangUtils.a() +
"&color=" +
ThemesUtils.hexx(ThemesUtils.getAccentColor()), LifecycleUtils.getCurrentActivity());
}
public static void hookDNR(int peerId) {
mDoNotReadDBHelper.setEnabledForPeerId(peerId, !mDoNotReadDBHelper.isEnabledForPeerId(peerId));
}
@@ -23,6 +23,7 @@ import ru.vtosters.lite.music.downloader.PlaylistDownloader;
import ru.vtosters.lite.music.downloader.TrackDownloader;
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
/**
@@ -63,6 +64,7 @@ public class AudioDownloader {
if (CacheDatabaseDelegate.isCached(trackId)) {
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
return;
}
@@ -1,621 +0,0 @@
package ru.vtosters.lite.downloaders;
import static android.widget.Toast.LENGTH_SHORT;
import static android.widget.Toast.makeText;
import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.getArray;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
import android.util.SparseArray;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
public class MessagesDownloader {
private static final String VK_DOMAIN = "vk.com";
private static SimpleDateFormat sdformat;
private static SparseArray<MiniUser> usersArray;
public static String formatTime(long now) {
return sdformat.format(new Date(now));
}
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
StringBuilder builder = new StringBuilder();
for (CharSequence cs : elements) {
builder.append(cs).append(delimiter);
}
return builder.toString();
}
private static String getPollHtml(JSONObject poll) throws JSONException {
StringBuilder pollHtml = new StringBuilder(
"<div class=\"vtex-milk-msg poll\">" +
"<p style=\"text-align:center;\">" + poll.optString("question") + "</p>"
);
var answers = poll.optJSONArray("answers");
for (int i = 0; i < answers.length(); i++) {
var jsonObject = answers.optJSONObject(i);
if (jsonObject != null)
pollHtml.append("<hr><p>").append(jsonObject.optString("text")).append(" · ").append(jsonObject.optString("votes")).append(" (").append(jsonObject.optString("rate")).append("%)</p>");
}
pollHtml.append("<hr><p style=\"text-align:center;\">")
.append(AndroidUtils.getString(R.string.chat_export_poll_voted))
.append(" ")
.append(poll.optString("votes"))
.append(" ")
.append(AndroidUtils.getString(R.string.chat_export_users_name))
.append("</p>");
return pollHtml.append("</div>").toString();
}
private static String getVideoHtml(JSONObject files) throws JSONException {
if (files == null) return AndroidUtils.getString(R.string.chat_export_video_nolinks);
Iterator<?> keys = files.keys();
StringBuilder videoHtml = new StringBuilder();
int qualityIndex = 0;
String[] quality = {"144", "240", "360", "480", "720", "1080", "1440", "2160"};
while (keys.hasNext()) {
String key = (String) keys.next();
if (key.equals("mp4_" + quality[qualityIndex])) {
videoHtml.append("<a class=\"msg-attach-link\" href=\"").append(files.get(key)).append("\">").append(quality[qualityIndex]).append("p</a>").append(" ");
if (qualityIndex == quality.length - 1) break;
++qualityIndex;
}
}
return videoHtml.toString();
}
public void downloadDialog(int peer_id, DialogDownloaderFormatProvider format, File out) throws Exception {
sdformat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", getResources().getConfiguration().locale);
FileOutputStream fos = new FileOutputStream(out);
// fix TODO with conversation name (API method messages.getConversationsById)
fos.write(format.provideDocumentStart("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8));
fetchDialogInfo(peer_id, 0, ((users, messages) -> {
try {
fos.write(format.provideHeader("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8));
for (MiniMsg msg : messages) {
fos.write(format.provideMessage(msg, users.get(msg.fromId)).getBytes(StandardCharsets.UTF_8));
}
fos.write(format.provideDocumentEnd().getBytes(StandardCharsets.UTF_8));
fos.close();
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), AndroidUtils.getString(R.string.saved_as) + " " + out.getAbsolutePath(), LENGTH_SHORT).show());
} catch (IOException | JSONException e) {
e.printStackTrace();
}
}));
}
public void fetchDialogInfo(int peer_id, int offset, FetchListener fl) {
makeRequest("https://" + getApi() + "/method/messages.getHistory?extended=1&v=5.140&offset=" + offset + "&count=200&peer_id=" + peer_id + "&access_token=" + getUserToken(),
response -> {
try {
JSONObject mainJson = new JSONObject(response);
JSONObject obj = mainJson.getJSONObject("response");
SparseArray<MiniUser> users = new SparseArray<>();
parseUsers(users, obj.optJSONArray("profiles"), false);
parseUsers(users, obj.optJSONArray("groups"), true);
List<MiniMsg> msgs = parseMessages(obj.getJSONArray("items"));
usersArray = users;
fl.onReceive(users, msgs);
} catch (JSONException e) {
e.printStackTrace();
}
});
}
private List<MiniMsg> parseMessages(JSONArray jsonArray) throws JSONException {
ArrayList<MiniMsg> thing = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
MiniMsg msg = new MiniMsg(jsonArray.getJSONObject(i));
thing.add(msg);
}
return thing;
}
private void parseUsers(SparseArray<MiniUser> array, @Nullable JSONArray jsonArray, boolean groups) throws JSONException {
if (jsonArray == null) return;
for (int i = 0; i < jsonArray.length(); i++) {
MiniUser user = new MiniUser(jsonArray.getJSONObject(i), groups);
array.put(groups ? -user.id : user.id, user);
}
}
interface FetchListener {
void onReceive(SparseArray<MiniUser> users, List<MiniMsg> messages);
}
static class MiniUser {
public String firstName;
public String lastName;
public boolean isGroup;
int id;
String photo100;
public MiniUser(JSONObject obj, boolean is_group) throws JSONException {
id = obj.getInt("id");
firstName = obj.optString("first_name", obj.optString("name"));
lastName = obj.optString("last_name", "");
photo100 = obj.getString("photo_100");
isGroup = is_group;
}
}
static class MiniMsg {
long date;
int fromId;
int id; // message id
String text;
List<JSONObject> attachments;
List<JSONObject> fwd_messages;
JSONObject reply_message; // one reply
JSONObject chat_action;
JSONObject geo;
public MiniMsg(JSONObject obj) throws JSONException {
date = obj.getLong("date") * 1000;
fromId = obj.getInt("from_id");
id = obj.getInt("id");
text = decryptMessage(obj.getString("text"), obj.getInt("peer_id"));
reply_message = obj.optJSONObject("reply_message");
chat_action = obj.optJSONObject("action");
geo = obj.optJSONObject("geo");
attachments = new ArrayList<>();
fwd_messages = new ArrayList<>();
JSONArray arr = obj.optJSONArray("attachments");
if (arr != null)
for (int i = 0; i < arr.length(); i++) attachments.add(arr.getJSONObject(i));
JSONArray fwdArr = obj.optJSONArray("fwd_messages");
if (fwdArr != null)
for (int i = 0; i < fwdArr.length(); i++) fwd_messages.add(fwdArr.getJSONObject(i));
}
}
// This class defines everything which is needed to create an output file
abstract static class DialogDownloaderFormatProvider {
// Internal, for JSON/HTML
abstract String provideDocumentStart(String dialogName, String date);
abstract String provideDocumentEnd();
abstract String provideHeader(String dialogName, String date);
abstract String provideMessage(MiniMsg message, MiniUser user) throws JSONException;
}
public static class HtmlDialogDownloaderFormatProvider extends DialogDownloaderFormatProvider {
@Override
String provideDocumentStart(String dialogName, String date) {
String style = ".vtex-milk-msg {\n" +
" border-radius: 8px;\n" +
" padding: 24px;\n" +
" background: white;\n" +
" margin: 16px;\n" +
" box-shadow: 0 4px 8px 0 rgba(0,0,0,0.07);\n" +
" word-wrap: break-word;\n" +
"}\n" +
"\n" +
".vtex-milk-msgcont br {\n" +
" display: none;\n" +
"}\n" +
"\n" +
".vtex-milk-msg p {\n" +
" margin: 0;\n" +
"}\n" +
"\n" +
".vtex-milk-msg.vtex-milk-msg-out {\n" +
" background: #ADD3FF;\n" +
"}\n" +
"\n" +
"p.msg-attaches {\n" +
" margin-top: 8px;\n" +
" font-style: italic;\n" +
"}\n" +
"p.chat-action {\n" +
" font-style: italic;\n" +
"}\n" +
"\n" +
".vtex-milk-header {\n" +
" border-radius: 8px;\n" +
" padding: 24px;\n" +
" background: white;\n" +
" margin: 16px;\n" +
" box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);\n" +
"}\n" +
"\n" +
"h4.vtex-milk-header-txt {\n" +
" margin: 0;\n" +
"}\n" +
"\n" +
"h5.vtex-milk-header-sub {\n" +
" margin: 0;\n" +
" padding-top: 8px;\n" +
"}\n" +
"\n" +
"a.msg-attach-link {\n" +
" color: #5181B8;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
".msg-from a {\n" +
" color: #5181B8;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
".msg-from-date {\n" +
" color: rgba(0, 0, 0, 0.75);\n" +
"}\n" +
"\n" +
"body {\n" +
" font-family: Roboto, system-ui, sans-serif;\n" +
"}\n" +
"\n" +
".msg-body {\n" +
" padding-top: 4px;\n" +
"}\n" +
".msg-reply {\n" +
" border-left: 3px solid #7aa7f6;\n" +
" margin-top: 5px;\n" +
" padding-left: 3px;\n" +
"}\n\n" +
".msg-reply p {\n" +
" padding-left: 5px;\n" +
"}\n\n" +
".msg-reply p.reply-header {\n" +
" color: #5181B8;\n" +
"}\n\n" +
".msg-reply p.reply-content {\n" +
" color: #000000;\n" +
"}\n" +
".vtex-milk-msg.poll {\n" +
" width: 45%;\n" +
" background: linear-gradient(to bottom right, #707c8c, #4d5565);\n" +
" font-style: normal;" +
"}\n" +
".vtex-milk-msg.poll p { color: white; }\n" +
".vtex-milk-msg.poll hr { border-top: 1px; background: #ffffff; }\n" +
"\n" +
"";
return "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"utf-8\"><title>"
+ AndroidUtils.getString(R.string.chat_export_title)
+ "</title><style>"
+ style
+ "</style></head><body>";
}
@Override
String provideDocumentEnd() {
return "</div></body></html>";
}
@Override
String provideHeader(String dialogName, String date) {
return "<div class=\"vtex-milk-header\"><h4 class=\"vtex-milk-header-txt\">VTLite</h4><h5 class=\"vtex-milk-header-sub\">" + AndroidUtils.getString(R.string.chat_export_header) + " (" + date + ")</h5></div><div class=\"vtex-milk-msgcont\">";
}
@Override
String provideMessage(MiniMsg message, MiniUser user) throws JSONException {
String isOut = (user.id == getUserId()) ? " vtex-milk-msg-out" : "";
String geoPosition = "";
if (message.geo != null) {
var coordinates = message.geo.optJSONObject("coordinates");
if (coordinates != null) {
var latitude = coordinates.optDouble("latitude");
var longitude = coordinates.optDouble("longitude");
var ll = longitude + "," + latitude;
geoPosition = "<a href=\"https://yandex.ru/maps/?ll=" + ll + "&mode=search&sll=" + ll + "&text=" + latitude + "," + longitude + "&z=12\">" +
"<img src=\"https://static-maps.yandex.ru/1.x/?ll=" + longitude + "," + latitude + "&z=12&l=map&size=300,200&pt=" + ll + ",vkgrm\"/>" +
"</a>";
}
}
String chatAction = (message.chat_action != null) ? getHtmlForChatAction(message.chat_action) : "";
String attaches = (message.attachments.size() > 0) ? "<p class=\"msg-attaches\">" + AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(message.attachments) + "</p>" : "";
String reply = (message.reply_message != null) ? provideReply(message.reply_message) : "";
String fwdMessages = (message.fwd_messages.size() > 0) ? provideForwardMessages(message.fwd_messages) : "";
String userLink = (user.isGroup ? "club" : "id") + user.id;
return "<div class=\"vtex-milk-msg" + isOut + "\" id=\"" + message.id + "\">" +
"<p class=\"msg-from\">" + AndroidUtils.getString(R.string.from_lower) + " <a href=\"https://" + VK_DOMAIN + "/" + userLink + "\">" + user.firstName + " " + user.lastName + "</a> <span class=\"msg-from-date\">" + formatTime(message.date) + "</span></p>" + // TODO photo100
"<p class=\"msg-body\">" + mentionsReplace(message.text) + "</p>" +
geoPosition +
chatAction +
attaches +
reply +
fwdMessages +
"</div>";
}
private String provideReply(JSONObject replyMessage) {
var messageId = replyMessage.optInt("id");
var userId = replyMessage.optInt("from_id");
var peerId = replyMessage.optInt("peer_id");
var text = mentionsReplace(decryptMessage(replyMessage.optString("text"), peerId));
var fwdMessages = replyMessage.optJSONArray("fwd_messages");
var user = usersArray.get(userId);
if (text.isEmpty()) {
if (fwdMessages != null) {
var length = fwdMessages.length();
if (length > 0) text = length + " " + pluralMsg(length);
} else text = AndroidUtils.getString(R.string.attachment); // Вложение
}
return "<a href=\"#" + messageId + "\" class=\"msg-attach-link\">" +
"<div class=\"msg-reply\">" +
"<p class=\"reply-header\">" + AndroidUtils.getString(R.string.vkim_msg_fwd_single) + "</p>" +
"<p class=\"reply-content\">" + user.firstName + " " + user.lastName + ": " + text + "</p>" +
"</div>" +
"</a>";
}
private String provideForwardMessages(List<JSONObject> fwd_messages) throws JSONException {
StringBuilder fwdmsgs = new StringBuilder("<div class=\"msg-reply\">");
for (JSONObject fwd_message : fwd_messages) {
if (fwd_message == null) continue;
List<JSONObject> attaches = new ArrayList<>();
JSONArray arr = fwd_message.optJSONArray("attachments");
if (arr != null) {
for (int i = 0; i < arr.length(); i++) {
try {
attaches.add(arr.getJSONObject(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
List<JSONObject> fwdsMsg = new ArrayList<>();
JSONArray fwd_msgs = fwd_message.optJSONArray("fwd_messages");
if (fwd_msgs != null) {
for (int i = 0; i < fwd_msgs.length(); i++) {
try {
fwdsMsg.add(fwd_msgs.getJSONObject(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
JSONObject reply_message = fwd_message.optJSONObject("reply_message");
var attachesHtml = (attaches.size() > 0) ? AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(attaches) : "";
var replyHtml = (reply_message != null) ? provideReply(reply_message) : "";
var fwdsHtml = (fwdsMsg.size() > 0) ? provideForwardMessages(fwdsMsg) : ""; // recursion XD :>
var userId = fwd_message.optInt("from_id");
var peerId = fwd_message.optInt("peer_id");
var text = mentionsReplace(decryptMessage(fwd_message.optString("text"), peerId));
var userLink = ((userId < 0) ? "club" : "id") + Math.abs(userId);
fwdmsgs.append("<p class=\"reply-content\"><a class=\"msg-attach-link\" href=\"https://" + VK_DOMAIN + "/").append(userLink).append("\">@").append(userLink).append("</a>: ").append(text).append(attachesHtml).append(replyHtml).append(fwdsHtml);
}
return fwdmsgs.append("</div>").toString();
}
private String mentionsReplace(String text) {
var matcher = Pattern.compile("\\[((?:club|id|public)\\d+)\\|([^]]+)\\]").matcher(text);
return matcher.replaceAll("<a class=\"msg-attach-link\" href=\"https://" + VK_DOMAIN + "/$1\">$2</a>");
}
private String pluralMsg(int count) {
String[] plurals = getArray(R.array.msg_plurals);
var hundredModulo = count % 100;
var tenModulo = count % 10;
if (hundredModulo > 10 && hundredModulo < 20) return plurals[2];
else if (tenModulo > 1 && tenModulo < 5) return plurals[1];
else if (tenModulo == 1) return plurals[0];
return plurals[2];
}
private String getHtmlForChatAction(JSONObject action) {
var type = action.optString("type");
StringBuilder htmlAction = new StringBuilder("<p class=\"chat-action\">");
switch (type) {
case "chat_photo_update":
htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_update));
break;
case "chat_photo_remove":
htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_remove));
break;
case "chat_create":
var name = action.optString("text");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_created)).append((!name.isEmpty()) ? " \"" + name + "\"" : "");
break;
case "chat_title_update":
var newName = action.optString("text");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_name_changed)).append((!newName.isEmpty()) ? (" " + AndroidUtils.getString(R.string.chat_export_to) + " \"" + newName + "\"") : "");
break;
case "chat_invite_user":
var newUserId = action.optInt("member_id");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_added) + " ").append((newUserId > 0) ? AndroidUtils.getString(R.string.chat_export_type_user) : AndroidUtils.getString(R.string.chat_export_type_group));
if (newUserId != 0) {
var userLink = ((newUserId < 0) ? "club" : "id") + Math.abs(newUserId);
htmlAction.append(" <a href=\"https://" + VK_DOMAIN + "/").append(userLink).append("\" class=\"msg-attach-link\">@").append(userLink).append("</a>");
}
break;
case "chat_kick_user":
var removedUserId = action.optInt("member_id");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_kicked) + " ").append((removedUserId > 0) ? AndroidUtils.getString(R.string.chat_export_type_user) : AndroidUtils.getString(R.string.chat_export_type_group));
if (removedUserId != 0) {
var userLink = ((removedUserId < 0) ? "club" : "id") + Math.abs(removedUserId);
htmlAction.append(" <a href=\"https://" + VK_DOMAIN + "/").append(userLink).append("\" class=\"msg-attach-link\">@").append(userLink).append("</a>");
}
break;
case "chat_pin_message":
var message = action.optString("message");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_pinned)).append((!message.isEmpty()) ? (" \"" + message + "\"") : "");
break;
case "chat_unpin_message":
htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_unpinned));
break;
case "chat_invite_user_by_link":
htmlAction.append(AndroidUtils.getString(R.string.chat_export_join_by_link));
break;
case "chat_screenshot":
htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_screenshot));
break;
case "custom":
htmlAction.append(action.optString("message"));
break;
default:
htmlAction.append(AndroidUtils.getString(R.string.vkim_msg_unsupported) + " (").append(type).append(")");
break;
}
return htmlAction.append("</p>").toString();
}
private String getHtmlForAttach(List<JSONObject> attaches) throws JSONException {
StringBuilder rs = new StringBuilder();
for (JSONObject attach : attaches) {
String type = attach.getString("type");
JSONObject item = attach.optJSONObject(type);
if (item == null) continue;
switch (type) { // market_album is not supported
case "photo":
var sizes = item.getJSONArray("sizes");
rs.append("<a href=\"").append(sizes.getJSONObject(sizes.length() - 1).getString("url")).append("\" class=\"msg-attach-link\">").append(AndroidUtils.getString(R.string.chat_export_type_photo)).append("</a>");
break;
case "doc":
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">").append(AndroidUtils.getString(R.string.chat_export_type_doc)).append("</a>");
break;
case "link":
rs.append("<a href=\"").append(item.getString("url")).append("\" class=\"msg-attach-link\">").append(AndroidUtils.getString(R.string.chat_export_type_link)).append("</a>");
break;
case "wall":
rs.append("<a href=\"https://" + VK_DOMAIN + "/wall").append(item.optString("to_id", item.optString("owner_id"))).append("_").append(item.getString("id")).append("\" class=\"msg-attach-link\">").append(AndroidUtils.getString(R.string.chat_export_type_wallpost)).append("</a>");
break;
case "sticker":
rs.append("<img style=\"width:128px;height:128px;\" src=\"").append(item.getJSONArray("images").getJSONObject(1).getString("url")).append("\"/>");
break;
case "audio_message":
rs.append("<a href=\"").append(item.optString("link_mp3")).append("\" class=\"msg-attach-link\">").append(AndroidUtils.getString(R.string.chat_export_type_audiomsg)).append("</a>");
break;
case "audio":
rs.append(AndroidUtils.getString(R.string.chat_export_type_audio)).append(" (").append(item.optString("artist")).append(" — ").append(item.optString("title")).append(")");
break;
case "video":
rs.append(AndroidUtils.getString(R.string.chat_export_type_video)).append(" (").append(getVideoHtml(item.optJSONObject("files"))).append(")");
break;
case "wall_reply":
var link = "https://" + VK_DOMAIN + "/wall" + item.optString("owner_id") + "_" + item.optString("post_id") + "?reply=" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_wallreply)).append("</a>");
break;
case "story":
var story_link = "https://" + VK_DOMAIN + "/story" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(story_link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_story)).append("</a>");
break;
case "podcast":
var podcast_link = "https://" + VK_DOMAIN + "/podcast" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(podcast_link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_podcast)).append("</a>");
break;
case "gift":
rs.append("<img src=\"").append(item.optString("thumb_96")).append("\"/>");
break;
case "mini_app":
var app = item.optJSONObject("app");
if (app != null) {
var miniapp_link = "https://" + VK_DOMAIN + "/app" + app.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(miniapp_link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_miniapp)).append("</a>");
break;
}
rs.append(AndroidUtils.getString(R.string.chat_export_error));
break;
case "audio_playlist":
var playlist_link = "https://" + VK_DOMAIN + "/music/album/" + item.optString("owner_id") + "_" + item.optString("id") + "_" + item.optString("access_key");
rs.append("<a class=\"msg-attach-link\" href=\"").append(playlist_link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_playlist)).append(" (").append(item.optString("title")).append(")</a>");
break;
case "poll":
rs.append(AndroidUtils.getString(item.optBoolean("anonymous") ? R.string.chat_export_poll_anon : R.string.chat_export_poll_publ)).append(" ").append(AndroidUtils.getString(R.string.chat_export_type_poll)).append(" ").append(getPollHtml(item));
break;
case "market":
var market_item_link = "https://" + VK_DOMAIN + "/market" + item.optString("owner_id") + "_" + item.optString("id");
rs.append("<a class=\"msg-attach-link\" href=\"").append(market_item_link).append("\">").append(AndroidUtils.getString(R.string.chat_export_type_market)).append("</a>");
break;
default:
rs.append(AndroidUtils.getString(R.string.vkim_msg_unsupported)).append(" (").append(type).append(")");
break;
}
rs.append(" ");
}
return rs.toString();
}
}
}
@@ -9,18 +9,20 @@ import android.content.IntentFilter;
import android.net.Uri;
import android.os.Environment;
import androidx.core.content.FileProvider;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import java.io.File;
import b.h.g.m.FileUtils;
import ru.vtosters.lite.ui.activities.APKInstallActivity;
import ru.vtosters.lite.utils.AndroidUtils;
public class OTADownloader {
private static final BroadcastReceiver callback = new BroadcastReceiver() {
@SuppressLint("Range")
@Override
public void onReceive(Context context, Intent intent) {
var extras = intent.getExtras();
@@ -30,24 +32,15 @@ public class OTADownloader {
var query = new DownloadManager.Query();
var id = extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
query.setFilterById(id);
var manager = (DownloadManager) AndroidUtils.getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE);
var manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
var cursor = manager.query(query);
if (cursor.moveToFirst()) {
@SuppressLint("Range") var status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
var downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
if (!downloadDir.exists()) return;
var list = downloadDir.listFiles();
if (list == null || list.length == 0) return;
var maxIndex = -1;
for (File file : list) {
var name = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("/") + 1);
if (file.isFile()
&& name.matches("VTLite-\\d+\\.apk"))
maxIndex = Math.max(maxIndex, Integer.parseInt(name.replaceFirst(".+-(\\d+).+", "$1")));
}
File apk = maxIndex == -1 ? new File(downloadDir, "VTLite.apk") : new File(downloadDir, "VTLite-" + maxIndex + ".apk");
APKInstallActivity.installOta(context, FileUtils.uriFromFile(apk));
var uriStr = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
var apk = new File(uriStr.replace("file://", ""));
var uri = FileProvider.getUriForFile(context, "com.vtosters.lite.common.VKFileProvider", apk);
APKInstallActivity.installOta(context, uri);
} else if (status == DownloadManager.STATUS_FAILED) {
AndroidUtils.sendToast(context.getString(R.string.downloaderr));
}
@@ -55,7 +48,7 @@ public class OTADownloader {
}
};
public static void downloadBuild(String url) {
public static void downloadBuild(String url, String commitSHA) {
var context = AndroidUtils.getGlobalContext();
try {
var uri = Uri.parse(url);
@@ -64,8 +57,8 @@ public class OTADownloader {
var request = new DownloadManager.Request(uri);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("VTLite.apk");
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "VTLite_" + System.currentTimeMillis() / 1000 + ".apk");
request.setTitle(AndroidUtils.getString("downloading_update"));
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "VTLite-" + commitSHA + ".apk");
var manager = (DownloadManager) AndroidUtils.getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
@@ -7,32 +7,35 @@ import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import com.vk.dto.common.ImageSize;
import com.vk.dto.stories.model.StoryEntry;
import org.json.JSONArray;
import java.util.List;
public class StoryDownloader {
public static Runnable injectButton(StoryEntry story) {
return () -> {
// if story is a pic
if (story.T1()) {
List<ImageSize> imageSizes = story.E.Q.t1();
String url = imageSizes.get(imageSizes.size() - 1).url;
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("photo" + story.E.a + ".jpg");
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "photo" + story.E.a + ".jpg");
request.allowScanningByMediaScanner();
((DownloadManager) getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
return;
}
return () -> downloadStory(story);
}
// if story is a video
if (story.F != null) {
downloadVideo(story.F, null);
}
};
public static void downloadStory(StoryEntry story){
if (story.T1()) {
List<ImageSize> imageSizes = story.E.Q.t1();
String url = imageSizes.get(imageSizes.size() - 1).url;
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("photo" + story.E.a + ".jpg");
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "photo" + story.E.a + ".jpg");
request.allowScanningByMediaScanner();
((DownloadManager) getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
return;
}
// if story is a video
if (story.F != null) {
downloadVideo(story.F, null);
}
}
}
@@ -1,38 +1,35 @@
package ru.vtosters.lite.downloaders;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.dialogs.bottomsheet.MenuBottomSheetAction;
import com.vk.core.util.ToastUtils;
import com.vk.dto.common.VideoFile;
import com.vk.dto.stories.model.StoryEntry;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.ExternalLinkParser;
import ru.vtosters.lite.utils.LifecycleUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import ru.vtosters.lite.utils.ExternalLinkParser;
import ru.vtosters.lite.utils.LifecycleUtils;
import static ru.vtosters.lite.net.Request.makeRequest;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken;
import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr;
public class VideoDownloader {
private static final int DOWNLOAD_ID = 0;
@@ -60,76 +57,105 @@ public class VideoDownloader {
}
public static void downloadVideo(VideoFile videoFile, Context context) {
final List<String> list = new ArrayList<>();
final List<String> qualities = new ArrayList<>();
final List<String> urls = new ArrayList<>();
if (!Objects.equals(videoFile.e, "")) {
list.add("240p");
urls.add(videoFile.e);
}
if (!Objects.equals(videoFile.f, "")) {
list.add("360p");
urls.add(videoFile.f);
}
if (!Objects.equals(videoFile.g, "")) {
list.add("480p");
urls.add(videoFile.g);
}
if (!Objects.equals(videoFile.h, "")) {
list.add("720p");
urls.add(videoFile.h);
}
if (!Objects.equals(videoFile.B, "")) {
list.add("1080p");
urls.add(videoFile.B);
}
if (!Objects.equals(videoFile.C, "")) {
list.add("1440p");
urls.add(videoFile.C);
}
if (!Objects.equals(videoFile.D, "")) {
list.add("2160p");
if (!TextUtils.isEmpty(videoFile.D)) {
qualities.add("2160p");
urls.add(videoFile.D);
}
if (list.isEmpty()) {
ToastUtils.a(context.getString(R.string.videodownloaderr));
if (!TextUtils.isEmpty(videoFile.C)) {
qualities.add("1440p");
urls.add(videoFile.C);
}
if (!TextUtils.isEmpty(videoFile.B)) {
qualities.add("1080p");
urls.add(videoFile.B);
}
if (!TextUtils.isEmpty(videoFile.h)) {
qualities.add("720p");
urls.add(videoFile.h);
}
if (!TextUtils.isEmpty(videoFile.g)) {
qualities.add("480p");
urls.add(videoFile.g);
}
if (!TextUtils.isEmpty(videoFile.f)) {
qualities.add("360p");
urls.add(videoFile.f);
}
if (!TextUtils.isEmpty(videoFile.e)) {
qualities.add("240p");
urls.add(videoFile.e);
}
Context finalContext = context != null ? context : LifecycleUtils.getCurrentActivity();
if (qualities.isEmpty()) {
ToastUtils.a(finalContext.getString(R.string.videodownloaderr));
return;
}
var max = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
max[i] = list.get(i);
}
if (context == null) context = LifecycleUtils.getCurrentActivity();
var adapter = new ArrayAdapter(context, android.R.layout.simple_list_item_1, max) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
var adapter = new ArrayAdapter(
finalContext,
android.R.layout.simple_list_item_1,
qualities.toArray(new String[0])) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(getTextAttr());
return textView;
}
};
new VkAlertDialog.Builder(context)
new VkAlertDialog.Builder(finalContext)
.setAdapter(adapter, (dialog, which) -> {
var url = urls.get(which);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle(videoFile.toString());
request.allowScanningByMediaScanner();
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MOVIES, videoFile + ".mp4");
((DownloadManager) getGlobalContext().getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
try {
var url = urls.get(which);
var request = new DownloadManager.Request(Uri.parse(url))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(videoFile.toString())
.setDestinationInExternalPublicDir(Environment.DIRECTORY_MOVIES, videoFile + ".mp4");
request.allowScanningByMediaScanner();
((DownloadManager) finalContext.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
} catch (Throwable ignored) {
}
})
.show();
}
public static void parseVideoLink(String url, Context ctx) {
if (url.startsWith("http://")) {
url.replace("http://", "https://");
} else if (url.startsWith("vk.com"))
url = "https://" + url;
if (url.contains("vk.com/story")) {
ToastUtils.a(ctx.getString(R.string.video_dl_stories_not_supported));
String storyId = url.substring(url.startsWith("https") ? 20 : 19) + "_story";
ProgressDialog progressDialog = new ProgressDialog(ctx);
progressDialog.setMessage(ctx.getString(R.string.video_dl_progress));
progressDialog.show();
makeRequest("https://" + getApi() + "/method/stories.getById?stories=" + storyId + "&v=5.99&access_token=" + getUserToken(),
response -> {
progressDialog.cancel();
try {
JSONObject mainJson = new JSONObject(response);
JSONObject responseJson = mainJson.getJSONObject("response").optJSONArray("items").optJSONObject(0);
StoryEntry story = new StoryEntry(responseJson);
StoryDownloader.downloadStory(story);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(ctx, ctx.getString(R.string.video_dl_error), Toast.LENGTH_SHORT).show();
}
});
return;
}
@@ -0,0 +1,96 @@
package ru.vtosters.lite.downloaders.messages;
public class Constants {
public static final String VK_DOMAIN = "vk.com";
public static final String HTML_DOC_STYLE = ".vtex-milk-msg {\n" +
" border-radius: 8px;\n" +
" padding: 24px;\n" +
" background: white;\n" +
" margin: 16px;\n" +
" box-shadow: 0 4px 8px 0 rgba(0,0,0,0.07);\n" +
" word-wrap: break-word;\n" +
"}\n" +
"\n" +
".vtex-milk-msgcont br {\n" +
" display: none;\n" +
"}\n" +
"\n" +
".vtex-milk-msg p {\n" +
" margin: 0;\n" +
"}\n" +
"\n" +
".vtex-milk-msg.vtex-milk-msg-out {\n" +
" background: #ADD3FF;\n" +
"}\n" +
"\n" +
"p.msg-attaches {\n" +
" margin-top: 8px;\n" +
" font-style: italic;\n" +
"}\n" +
"p.chat-action {\n" +
" font-style: italic;\n" +
"}\n" +
"\n" +
".vtex-milk-header {\n" +
" border-radius: 8px;\n" +
" padding: 24px;\n" +
" background: white;\n" +
" margin: 16px;\n" +
" box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);\n" +
"}\n" +
"\n" +
"h4.vtex-milk-header-txt {\n" +
" margin: 0;\n" +
"}\n" +
"\n" +
"h5.vtex-milk-header-sub {\n" +
" margin: 0;\n" +
" padding-top: 8px;\n" +
"}\n" +
"\n" +
"a.msg-attach-link {\n" +
" color: #5181B8;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
".msg-from a {\n" +
" color: #5181B8;\n" +
" text-decoration: none;\n" +
"}\n" +
"\n" +
".msg-from-date {\n" +
" color: rgba(0, 0, 0, 0.75);\n" +
"}\n" +
"\n" +
"body {\n" +
" font-family: Roboto, system-ui, sans-serif;\n" +
"}\n" +
"\n" +
".msg-body {\n" +
" padding-top: 4px;\n" +
"}\n" +
".msg-reply {\n" +
" border-left: 3px solid #7aa7f6;\n" +
" margin-top: 5px;\n" +
" padding-left: 3px;\n" +
"}\n\n" +
".msg-reply p {\n" +
" padding-left: 5px;\n" +
"}\n\n" +
".msg-reply p.reply-header {\n" +
" color: #5181B8;\n" +
"}\n\n" +
".msg-reply p.reply-content {\n" +
" color: #000000;\n" +
"}\n" +
".vtex-milk-msg.poll {\n" +
" width: 45%;\n" +
" background: linear-gradient(to bottom right, #707c8c, #4d5565);\n" +
" font-style: normal;" +
"}\n" +
".vtex-milk-msg.poll p { color: white; }\n" +
".vtex-milk-msg.poll hr { border-top: 1px; background: #ffffff; }\n" +
"\n" +
"";
}
@@ -0,0 +1,17 @@
package ru.vtosters.lite.downloaders.messages;
import org.json.JSONException;
import ru.vtosters.lite.downloaders.messages.items.MiniMsg;
import ru.vtosters.lite.downloaders.messages.items.MiniUser;
// This class defines everything which is needed to create an output file
public abstract class DialogDownloaderFormatProvider {
// Internal, for JSON/HTML
public abstract String provideDocumentStart(String dialogName, String date);
public abstract String provideDocumentEnd();
public abstract String provideHeader(String dialogName, String date);
public abstract String provideMessage(MiniMsg message, MiniUser user) throws JSONException;
}
@@ -0,0 +1,402 @@
package ru.vtosters.lite.downloaders.messages;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.downloaders.messages.items.MiniMsg;
import ru.vtosters.lite.downloaders.messages.items.MiniUser;
import ru.vtosters.lite.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage;
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
import static ru.vtosters.lite.utils.AndroidUtils.getArray;
public class HtmlDialogDownloaderFormatProvider extends DialogDownloaderFormatProvider {
@Override
public String provideDocumentStart(String dialogName, String date) {
return "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"utf-8\"><title>"
+ AndroidUtils.getString(R.string.chat_export_title)
+ "</title><style>"
+ Constants.HTML_DOC_STYLE
+ "</style></head><body>";
}
@Override
public String provideDocumentEnd() {
return "</div></body></html>";
}
@Override
public String provideHeader(String dialogName, String date) {
return "<div class=\"vtex-milk-header\"><h4 class=\"vtex-milk-header-txt\">VTLite</h4><h5 class=\"vtex-milk-header-sub\">"
+ AndroidUtils.getString(R.string.chat_export_header)
+ " (" + date + ")</h5></div><div class=\"vtex-milk-msgcont\">";
}
@Override
public String provideMessage(MiniMsg message, MiniUser user) throws JSONException {
String isOut = (user.id == getUserId()) ? " vtex-milk-msg-out" : "";
String geoPosition = "";
if (message.geo != null) {
var coordinates = message.geo.optJSONObject("coordinates");
if (coordinates != null) {
var latitude = coordinates.optDouble("latitude");
var longitude = coordinates.optDouble("longitude");
var ll = longitude + "," + latitude;
geoPosition = "<a href=\"https://yandex.ru/maps/?ll=" + ll + "&mode=search&sll=" + ll + "&text=" + latitude + "," + longitude + "&z=12\">" +
"<img src=\"https://static-maps.yandex.ru/1.x/?ll=" + longitude + "," + latitude + "&z=12&l=map&size=300,200&pt=" + ll + ",vkgrm\"/>" +
"</a>";
}
}
String chatAction = (message.chat_action != null) ? getHtmlForChatAction(message.chat_action) : "";
String attaches = (message.attachments.size() > 0) ? "<p class=\"msg-attaches\">" + AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(message.attachments) + "</p>" : "";
String reply = (message.reply_message != null) ? provideReply(message.reply_message) : "";
String fwdMessages = (message.fwd_messages.size() > 0) ? provideForwardMessages(message.fwd_messages) : "";
String userLink = (user.isGroup ? "club" : "id") + user.id;
return "<div class=\"vtex-milk-msg" + isOut + "\" id=\"" + message.id + "\">" +
"<p class=\"msg-from\">" + AndroidUtils.getString(R.string.from_lower)
+ " <a href=\"https://" + Constants.VK_DOMAIN + "/" + userLink + "\">" + user.firstName + " " + user.lastName
+ "</a> <span class=\"msg-from-date\">" + MessagesDownloader.formatTime(message.date) + "</span></p>" + // TODO photo100
"<p class=\"msg-body\">" + mentionsReplace(message.text) + "</p>" +
geoPosition +
chatAction +
attaches +
reply +
fwdMessages +
"</div>";
}
private String provideReply(JSONObject replyMessage) {
var text = mentionsReplace(decryptMessage(replyMessage.optString("text"), replyMessage.optInt("peer_id")));
var fwdMessages = replyMessage.optJSONArray("fwd_messages");
var user = MessagesDownloader.usersArray.get(replyMessage.optInt("from_id"));
if (text.isEmpty()) {
if (fwdMessages != null) {
var length = fwdMessages.length();
if (length > 0) text = length + " " + pluralMsg(length);
} else text = AndroidUtils.getString(R.string.attachment); // Вложение
}
return "<a href=\"#" + replyMessage.optInt("id") + "\" class=\"msg-attach-link\">" +
"<div class=\"msg-reply\">" +
"<p class=\"reply-header\">" + AndroidUtils.getString(R.string.vkim_msg_fwd_single) + "</p>" +
"<p class=\"reply-content\">" + user.firstName + " " + user.lastName + ": " + text + "</p>" +
"</div>" +
"</a>";
}
private String provideForwardMessages(List<JSONObject> fwd_messages) throws JSONException {
StringBuilder fwdmsgs = new StringBuilder("<div class=\"msg-reply\">");
for (JSONObject fwd_message : fwd_messages) {
if (fwd_message == null) continue;
List<JSONObject> attaches = new ArrayList<>();
JSONArray arr = fwd_message.optJSONArray("attachments");
if (arr != null) {
for (int i = 0; i < arr.length(); i++) {
try {
attaches.add(arr.getJSONObject(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
List<JSONObject> fwdsMsg = new ArrayList<>();
JSONArray fwd_msgs = fwd_message.optJSONArray("fwd_messages");
if (fwd_msgs != null) {
for (int i = 0; i < fwd_msgs.length(); i++) {
try {
fwdsMsg.add(fwd_msgs.getJSONObject(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
JSONObject reply_message = fwd_message.optJSONObject("reply_message");
var attachesHtml = (attaches.size() > 0) ? AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(attaches) : "";
var replyHtml = (reply_message != null) ? provideReply(reply_message) : "";
var fwdsHtml = (fwdsMsg.size() > 0) ? provideForwardMessages(fwdsMsg) : ""; // recursion XD :>
var userId = fwd_message.optInt("from_id");
var peerId = fwd_message.optInt("peer_id");
var text = mentionsReplace(decryptMessage(fwd_message.optString("text"), peerId));
var userLink = ((userId < 0) ? "club" : "id") + Math.abs(userId);
fwdmsgs.append("<p class=\"reply-content\"><a class=\"msg-attach-link\" href=\"https://" + Constants.VK_DOMAIN + "/")
.append(userLink)
.append("\">@")
.append(userLink)
.append("</a>: ")
.append(text)
.append(attachesHtml)
.append(replyHtml)
.append(fwdsHtml);
}
return fwdmsgs.append("</div>").toString();
}
private String mentionsReplace(String text) {
var matcher = Pattern.compile("\\[((?:club|id|public)\\d+)\\|([^]]+)\\]").matcher(text);
return matcher.replaceAll("<a class=\"msg-attach-link\" href=\"https://" + Constants.VK_DOMAIN + "/$1\">$2</a>");
}
private String pluralMsg(int count) {
String[] plurals = getArray(R.array.msg_plurals);
var hundredModulo = count % 100;
var tenModulo = count % 10;
if (hundredModulo > 10 && hundredModulo < 20) return plurals[2];
else if (tenModulo > 1 && tenModulo < 5) return plurals[1];
else if (tenModulo == 1) return plurals[0];
return plurals[2];
}
private String getHtmlForChatAction(JSONObject action) {
var type = action.optString("type");
StringBuilder htmlAction = new StringBuilder("<p class=\"chat-action\">");
switch (type) {
case "chat_photo_update" -> htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_update));
case "chat_photo_remove" -> htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_remove));
case "chat_create" -> {
htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_created));
var name = action.optString("text");
if (!name.isEmpty())
htmlAction.append(" \"").append(name).append("\"");
}
case "chat_title_update" -> {
var newName = action.optString("text");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_name_changed));
if (!newName.isEmpty())
htmlAction.append(" ")
.append(AndroidUtils.getString(R.string.chat_export_to))
.append(" \"")
.append(newName)
.append("\"");
}
case "chat_invite_user" -> {
var newMemberId = action.optInt("member_id");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_added))
.append(" ");
if (newMemberId != 0) {
int id;
String endpoint;
if (newMemberId >= 0) {
id = R.string.chat_export_type_user;
endpoint = "id" + newMemberId;
} else {
id = R.string.chat_export_type_group;
endpoint = "club" + -newMemberId;
}
htmlAction.append(AndroidUtils.getString(id))
.append(" <a href=\"https://" + Constants.VK_DOMAIN + "/")
.append(endpoint)
.append("\" class=\"msg-attach-link\">@")
.append(endpoint)
.append("</a>");
}
}
case "chat_kick_user" -> {
var removedMemberId = action.optInt("member_id");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_kicked))
.append(" ");
if (removedMemberId != 0) {
int id;
String endpoint;
if (removedMemberId >= 0) {
id = R.string.chat_export_type_user;
endpoint = "id" + removedMemberId;
} else {
id = R.string.chat_export_type_group;
endpoint = "club" + -removedMemberId;
}
htmlAction.append(AndroidUtils.getString(id))
.append(" <a href=\"https://" + Constants.VK_DOMAIN + "/")
.append(endpoint)
.append("\" class=\"msg-attach-link\">@")
.append(endpoint)
.append("</a>");
}
}
case "chat_pin_message" -> {
var message = action.optString("message");
htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_pinned));
if (!message.isEmpty()) htmlAction.append(" \"").append(message).append("\"");
}
case "chat_unpin_message" -> htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_unpinned));
case "chat_invite_user_by_link" ->
htmlAction.append(AndroidUtils.getString(R.string.chat_export_join_by_link));
case "chat_screenshot" -> htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_screenshot));
case "custom" -> htmlAction.append(action.optString("message"));
default ->
htmlAction.append(AndroidUtils.getString(R.string.vkim_msg_unsupported)).append(" (").append(type).append(")");
}
return htmlAction.append("</p>").toString();
}
private String getHtmlForAttach(List<JSONObject> attaches) throws JSONException {
StringBuilder rs = new StringBuilder();
for (JSONObject attach : attaches) {
String type = attach.getString("type");
JSONObject item = attach.optJSONObject(type);
if (item == null) continue;
switch (type) { // market_album is not supported
case "photo" -> {
var sizes = item.getJSONArray("sizes");
rs.append("<a href=\"")
.append(sizes.getJSONObject(sizes.length() - 1).getString("url"))
.append("\" class=\"msg-attach-link\">")
.append(AndroidUtils.getString(R.string.chat_export_type_photo))
.append("</a>");
}
case "doc" -> rs.append("<a href=\"")
.append(item.getString("url"))
.append("\" class=\"msg-attach-link\">")
.append(AndroidUtils.getString(R.string.chat_export_type_doc))
.append("</a>");
case "link" -> rs.append("<a href=\"")
.append(item.getString("url"))
.append("\" class=\"msg-attach-link\">")
.append(AndroidUtils.getString(R.string.chat_export_type_link))
.append("</a>");
case "wall" -> rs.append("<a href=\"https://" + Constants.VK_DOMAIN + "/wall")
.append(item.optString("to_id", item.optString("owner_id")))
.append("_").append(item.getString("id"))
.append("\" class=\"msg-attach-link\">")
.append(AndroidUtils.getString(R.string.chat_export_type_wallpost))
.append("</a>");
case "sticker" -> rs.append("<img style=\"width:128px;height:128px;\" src=\"")
.append(item.getJSONArray("images")
.getJSONObject(1)
.getString("url"))
.append("\"/>");
case "audio_message" -> rs.append("<a href=\"")
.append(item.optString("link_mp3"))
.append("\" class=\"msg-attach-link\">")
.append(AndroidUtils.getString(R.string.chat_export_type_audiomsg))
.append("</a>");
case "audio" -> rs.append(AndroidUtils.getString(R.string.chat_export_type_audio))
.append(" (")
.append(item.optString("artist"))
.append(" — ")
.append(item.optString("title"))
.append(")");
case "video" -> rs.append(AndroidUtils.getString(R.string.chat_export_type_video))
.append(" (")
.append(MessagesDownloader.getVideoHtml(item.optJSONObject("files")))
.append(")");
case "wall_reply" -> {
var link = String.format("https://" + Constants.VK_DOMAIN + "/wall%s_%s?reply=%s",
item.optString("owner_id"),
item.optString("post_id"),
item.optString("id"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(link).append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_wallreply))
.append("</a>");
}
case "story" -> {
var story_link = String.format("https://" + Constants.VK_DOMAIN + "/story%s_%s",
item.optString("owner_id"),
item.optString("id"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(story_link)
.append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_story))
.append("</a>");
}
case "podcast" -> {
var podcast_link = String.format("https://" + Constants.VK_DOMAIN + "/podcast%s_%s",
item.optString("owner_id"),
item.optString("id"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(podcast_link)
.append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_podcast))
.append("</a>");
}
case "gift" -> rs.append("<img src=\"")
.append(item.optString("thumb_96"))
.append("\"/>");
case "mini_app" -> {
var app = item.optJSONObject("app");
if (app != null) {
var miniapp_link = String.format("https://" + Constants.VK_DOMAIN + "/app%s", app.optString("id"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(miniapp_link)
.append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_miniapp))
.append("</a>");
break;
}
rs.append(AndroidUtils.getString(R.string.chat_export_error));
}
case "audio_playlist" -> {
var playlist_link = String.format("https://" + Constants.VK_DOMAIN + "/music/album/%s_%s_%s",
item.optString("owner_id"),
item.optString("id"),
item.optString("access_key"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(playlist_link)
.append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_playlist))
.append(" (")
.append(item.optString("title"))
.append(")</a>");
}
case "poll" ->
rs.append(AndroidUtils.getString(item.optBoolean("anonymous") ? R.string.chat_export_poll_anon : R.string.chat_export_poll_publ))
.append(" ")
.append(AndroidUtils.getString(R.string.chat_export_type_poll))
.append(" ")
.append(MessagesDownloader.getPollHtml(item));
case "market" -> {
var market_item_link = String.format("https://" + Constants.VK_DOMAIN + "/market%s_%s",
item.optString("owner_id"),
item.optString("id"));
rs.append("<a class=\"msg-attach-link\" href=\"")
.append(market_item_link)
.append("\">")
.append(AndroidUtils.getString(R.string.chat_export_type_market))
.append("</a>");
}
default -> rs.append(AndroidUtils.getString(R.string.vkim_msg_unsupported))
.append(" (")
.append(type)
.append(")");
}
rs.append(" ");
}
return rs.toString();
}
}
@@ -0,0 +1,162 @@
package ru.vtosters.lite.downloaders.messages;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.vk.core.util.ToastUtils;
import com.vtosters.lite.R;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.downloaders.messages.items.MiniMsg;
import ru.vtosters.lite.downloaders.messages.items.MiniUser;
import ru.vtosters.lite.proxy.ProxyUtils;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
public class MessagesDownloader {
private static final int[] VIDEO_QUALITIES = { 144, 240, 360, 480, 720, 1080, 1440, 2160 };
private static final SimpleDateFormat SDFORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", AndroidUtils.getResources().getConfiguration().locale);
public static SparseArray<MiniUser> usersArray;
public static String formatTime(long now) {
return SDFORMAT.format(new Date(now));
}
public static String getPollHtml(JSONObject poll) {
StringBuilder pollHtml = new StringBuilder(
"<div class=\"vtex-milk-msg poll\">" +
"<p style=\"text-align:center;\">" + poll.optString("question") + "</p>"
);
var answers = poll.optJSONArray("answers");
for (int i = 0; i < answers.length(); i++) {
var jsonObject = answers.optJSONObject(i);
if (jsonObject != null)
pollHtml.append("<hr><p>")
.append(jsonObject.optString("text"))
.append(" · ")
.append(jsonObject.optString("votes"))
.append(" (")
.append(jsonObject.optString("rate"))
.append("%)</p>");
}
pollHtml.append("<hr><p style=\"text-align:center;\">")
.append(AndroidUtils.getString(R.string.chat_export_poll_voted))
.append(" ")
.append(poll.optString("votes"))
.append(" ")
.append(AndroidUtils.getString(R.string.chat_export_users_name))
.append("</p>")
.append("</div>");
return pollHtml.toString();
}
public static String getVideoHtml(JSONObject files) throws JSONException {
if (files == null) return AndroidUtils.getString(R.string.chat_export_video_nolinks);
Iterator<? extends String> keys = files.keys();
StringBuilder videoHtml = new StringBuilder();
int i = 0;
while (keys.hasNext() && i < VIDEO_QUALITIES.length) {
var key = keys.next();
if (key.equals("mp4_" + VIDEO_QUALITIES[i])) {
videoHtml.append("<a class=\"msg-attach-link\" href=\"")
.append(files.get(key))
.append("\">")
.append(VIDEO_QUALITIES[i])
.append("p</a>")
.append(" ");
}
++i;
}
return videoHtml.toString();
}
public void downloadDialog(int peer_id, DialogDownloaderFormatProvider format, File out) throws Exception {
FileOutputStream fos = new FileOutputStream(out);
// fix TODO with conversation name (API method messages.getConversationsById)
fos.write(format.provideDocumentStart("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8));
var url = String.format("https://" + ProxyUtils.getApi() + "/method/messages.getHistory?extended=1&v=5.140&offset=%d&count=200&peer_id=%d&access_token=%s",
0,
peer_id,
AccountManagerUtils.getUserToken());
var req = new Request.a()
.b(url)
.a();
VtOkHttpClient.getInstance().a(req).a(new Callback() {
@Override
public void a(Call call, IOException e) {
Log.d("MessagesDownloader", e+"");
}
@Override
public void a(Call call, Response response)
throws IOException {
try {
JSONObject obj = new JSONObject(response.a().g()).getJSONObject("response");
SparseArray<MiniUser> users = new SparseArray<>();
parseUsers(users, obj.optJSONArray("profiles"), false);
parseUsers(users, obj.optJSONArray("groups"), true);
List<MiniMsg> msgs = parseMessages(obj.getJSONArray("items"));
usersArray = users;
fos.write(format.provideHeader("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8));
for (MiniMsg msg : msgs) {
fos.write(format.provideMessage(msg, users.get(msg.fromId)).getBytes(StandardCharsets.UTF_8));
}
fos.write(format.provideDocumentEnd().getBytes(StandardCharsets.UTF_8));
fos.close();
ToastUtils.a(AndroidUtils.getString(R.string.saved_as) + " " + out.getAbsolutePath());
} catch (IOException | JSONException e) {
e.printStackTrace();
}
}
});
}
private List<MiniMsg> parseMessages(JSONArray jsonArray) throws JSONException {
List<MiniMsg> thing = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++)
thing.add(new MiniMsg(jsonArray.getJSONObject(i)));
return thing;
}
private void parseUsers(SparseArray<MiniUser> array, @Nullable JSONArray jsonArray, boolean groups) throws JSONException {
if (jsonArray == null) return;
for (int i = 0; i < jsonArray.length(); i++) {
MiniUser user = new MiniUser(jsonArray.getJSONObject(i), groups);
array.put(groups ? -user.id : user.id, user);
}
}
}
@@ -0,0 +1,42 @@
package ru.vtosters.lite.downloaders.messages.items;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage;
public class MiniMsg {
public long date;
public int fromId;
public int id; // message id
public String text;
public List<JSONObject> attachments;
public List<JSONObject> fwd_messages;
public JSONObject reply_message; // one reply
public JSONObject chat_action;
public JSONObject geo;
public MiniMsg(JSONObject obj) throws JSONException {
date = obj.getLong("date") * 1000;
fromId = obj.getInt("from_id");
id = obj.getInt("id");
text = decryptMessage(obj.getString("text"), obj.getInt("peer_id"));
reply_message = obj.optJSONObject("reply_message");
chat_action = obj.optJSONObject("action");
geo = obj.optJSONObject("geo");
attachments = new ArrayList<>();
fwd_messages = new ArrayList<>();
JSONArray arr = obj.optJSONArray("attachments");
if (arr != null)
for (int i = 0; i < arr.length(); i++) attachments.add(arr.getJSONObject(i));
JSONArray fwdArr = obj.optJSONArray("fwd_messages");
if (fwdArr != null)
for (int i = 0; i < fwdArr.length(); i++) fwd_messages.add(fwdArr.getJSONObject(i));
}
}
@@ -0,0 +1,20 @@
package ru.vtosters.lite.downloaders.messages.items;
import org.json.JSONException;
import org.json.JSONObject;
public class MiniUser {
public String firstName;
public String lastName;
public boolean isGroup;
public int id;
public String photo100;
public MiniUser(JSONObject obj, boolean is_group) throws JSONException {
id = obj.getInt("id");
firstName = obj.optString("first_name", obj.optString("name"));
lastName = obj.optString("last_name", "");
photo100 = obj.getString("photo_100");
isGroup = is_group;
}
}
@@ -90,6 +90,14 @@ public class EncryptProvider {
return (String) decryptMessage(msgBody, peer, showEmoji);
}
public static CharSequence decryptMessage(CharSequence msgBody, MsgFromUser msgFromUser, boolean showEmoji) {
try {
return decryptMessage((String) msgBody, msgFromUser.v1(), showEmoji);
} catch (Exception e) {
return decryptMessage((String) msgBody, 0, showEmoji);
}
}
// For MentionsFormatter that requires CharSequence
public static CharSequence decryptMessage(String msgBody, int peer, boolean showEmoji) {
var decryptedMessage = decryptMessage(msgBody, peer);
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
public abstract class IMProcessor {
// By default it checks start and end tag.
@@ -67,20 +68,20 @@ public abstract class IMProcessor {
}
public void enableEncryptFor(int id) {
AndroidUtils.getDefaultPrefs().edit().putBoolean("VT_IMEncode_" + getPrefKey() + "_" + id, true).apply();
Preferences.getPreferences().edit().putBoolean("VT_IMEncode_" + getPrefKey() + "_" + id, true).apply();
}
public void disableEncryptFor(int id) {
AndroidUtils.getDefaultPrefs().edit().putBoolean("VT_IMEncode_" + getPrefKey() + "_" + id, false).apply();
Preferences.getPreferences().edit().putBoolean("VT_IMEncode_" + getPrefKey() + "_" + id, false).apply();
}
public void setEncryptionKeyFor(int id, @Nullable String key) {
if (key == null || key.isEmpty()) key = "VTAesDefault";
AndroidUtils.getDefaultPrefs().edit().putString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, key).apply();
Preferences.getPreferences().edit().putString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, key).apply();
}
@Nullable
public String getEncryptionKeyFor(int id) {
return AndroidUtils.getDefaultPrefs().getString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, null);
return Preferences.getPreferences().getString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, null);
}
}
@@ -18,6 +18,7 @@ import javax.crypto.spec.SecretKeySpec;
import ru.vtosters.lite.encryption.base.IMProcessor;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
public class VTostersAESProcessor extends IMProcessor {
private final static String CIPHER_INSTANCE = "AES/CBC/PKCS7Padding";
@@ -114,7 +115,7 @@ public class VTostersAESProcessor extends IMProcessor {
@Nullable
@Override
public String getEncryptionKeyFor(int id) {
return AndroidUtils.getDefaultPrefs().getString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, "VTAesDefault");
return Preferences.getPreferences().getString("VT_IMEncodeKey_" + getPrefKey() + "_" + id, "VTAesDefault");
}
public static class Triple<F, S, T> {
@@ -0,0 +1,31 @@
package ru.vtosters.lite.feature.msgDelete;
import com.vk.im.ui.components.viewcontrollers.popup.DelegateMsg;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import kotlin.jvm.internal.Lambda;
import ru.vtosters.lite.utils.ReflectionUtils;
public final class DelegateMsg$showDeleteSubmit$builder$2 extends Lambda<Unit> implements Functions<Unit> {
final DelegateMsg this$0;
public DelegateMsg$showDeleteSubmit$builder$2(DelegateMsg delegateMsg) {
super(0);
this.this$0 = delegateMsg;
}
@Override
public Unit invoke() {
invoke2();
return Unit.a;
}
public final void invoke2() {
try {
var dialog = ReflectionUtils.findField(this$0, "a");
dialog.setAccessible(true);
dialog.set(this$0, null);
} catch (Exception ignored) { }
}
}
@@ -0,0 +1,127 @@
package ru.vtosters.lite.feature.msgDelete;
import android.content.Context;
import android.content.DialogInterface;
import androidx.appcompat.app.AlertDialog;
import com.vk.im.engine.models.messages.Msg;
import com.vk.im.ui.components.viewcontrollers.msg_list.MsgListVc;
import com.vk.im.ui.components.viewcontrollers.msg_list.MsgListVcCallback;
import com.vk.im.ui.components.viewcontrollers.popup.DelegateMsg;
import com.vk.im.ui.components.viewcontrollers.popup.PopupUtils;
import com.vk.im.ui.l;
import com.vk.im.ui.m;
import java.util.Collections;
import java.util.List;
import kotlin.Unit;
import kotlin.jvm.b.Functions;
import kotlin.jvm.b.Functions2;
import kotlin.jvm.internal.Lambda;
import kotlin.jvm.internal.Ref$BooleanRef;
import ru.vtosters.lite.utils.AndroidUtils;
public class MsgDeleteDialogHook {
// давайте представим что здесь нет никакого кода и это лишь страшный сон
public static AlertDialog.Builder getDialog(int i, boolean z, boolean z2, Functions2<Boolean, Unit> functions2, Context context, DelegateMsg delegateMsg) {
int i2;
if (z && i == 1) {
i2 = m.vkim_popup_msg_delete_submit_title_question_single;
} else if (z) {
i2 = m.vkim_popup_msg_delete_submit_title_question_many;
} else if (i == 1) {
i2 = m.vkim_popup_msg_delete_submit_title_default_single;
} else {
i2 = m.vkim_popup_msg_delete_submit_title_default_many;
}
var i3 = i2;
String string;
if (context != null) {
string = context.getString(m.vkim_popup_msg_delete_submit_checkbox_for_all);
} else {
string = AndroidUtils.getString(m.vkim_popup_msg_delete_submit_checkbox_for_all);
}
var stringArr = new String[]{string, "Эта галочка ничего не делает (совсем.)"};
var boolArr = new boolean[]{z2, false};
var boolRef = new Ref$BooleanRef();
boolRef.element = z & z2;
var dialogBuilder = PopupUtils.a(context, i3, "", 0, "", m.vkim_popup_msg_delete_submit_yes, "", m.vkim_popup_msg_delete_submit_cancel, "", false, new DelegateMsgShowDeleteSubmit(functions2, boolRef), null, null, new DelegateMsg$showDeleteSubmit$builder$2(delegateMsg));
if (z) {
dialogBuilder.setMultiChoiceItems(stringArr, boolArr, new ClickListener(boolRef));
} else {
var quantityStrring = context.getResources().getQuantityString(l.vkim_popup_msg_delete_submit_desc, i, i);
dialogBuilder.setMessage(quantityStrring);
}
return dialogBuilder;
}
public static class ClickListener implements DialogInterface.OnMultiChoiceClickListener {
final Ref$BooleanRef checkForAll;
public ClickListener(Ref$BooleanRef checkForAll) {
this.checkForAll = checkForAll;
}
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
this.checkForAll.element = isChecked;
}
}
static class DelegateMsgShowDeleteSubmit extends Lambda<Unit> implements Functions<Unit> {
final Ref$BooleanRef checkForAll;
final Functions2<Boolean, Unit> onSubmitListener;
public DelegateMsgShowDeleteSubmit(Functions2<Boolean, Unit> onSubmitListener, Ref$BooleanRef checkForAll) {
super(0);
this.checkForAll = checkForAll;
this.onSubmitListener = onSubmitListener;
}
@Override
public Unit invoke() {
if (onSubmitListener != null) {
return onSubmitListener.invoke(checkForAll.element);
}
return Unit.a;
}
}
static class MsgListVcShowMsgDeleteSubmitDialog extends Lambda<Unit> implements Functions2<Boolean, Unit> {
final Functions<? extends Unit> $callback;
final Msg $msg;
final MsgListVc this$0;
public MsgListVcShowMsgDeleteSubmitDialog(MsgListVc msgListVc, Msg msg, Functions<? extends Unit> functions) {
super(1);
this.this$0 = msgListVc;
this.$msg = msg;
this.$callback = functions;
}
public final void a(boolean z) {
List<Msg> a;
MsgListVcCallback g = this.this$0.g();
if (g != null) {
a = Collections.singletonList($msg);
g.a(a, z);
}
Functions<? extends Unit> functions = this.$callback;
if (functions != null) {
Unit unit = functions.invoke();
}
}
@Override
public Unit invoke(Boolean bool) {
a(bool);
return Unit.a;
}
}
}
@@ -31,6 +31,7 @@ import java.util.regex.Pattern;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.net.Request;
import ru.vtosters.lite.utils.LifecycleUtils;
@@ -38,7 +39,7 @@ public class FoafBase {
private static final Pattern FOAF_REGEX = Pattern.compile("<ya:created dc:date=\"(.+?)\"");
private static final Pattern FOAF_REGEX_LAST_SEEN = Pattern.compile("<ya:lastLoggedIn dc:date=\"(.*)(((\\+|-)\\d\\d):(\\d\\d))\"");
private static final String API_VKNEXT = "https://api.vtosters.app/v1/getBypassedOnlineInfo?json=1&ids=";
private static final OkHttpClient client = new OkHttpClient();
private static final OkHttpClient client = VtOkHttpClient.getInstance();
public static long getLastSeen(long origtime, int id) throws ParseException {
var request = new okhttp3.Request.a()
@@ -148,8 +149,8 @@ public class FoafBase {
.setPositiveButton(android.R.string.ok, null)
.show());
} catch (Exception e) {
e.printStackTrace();
LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), context.getString(R.string.foaferr), LENGTH_SHORT).show());
e.printStackTrace();
}
}
@@ -22,6 +22,7 @@ import java.util.Collections;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.NewsFeedFiltersUtils;
import ru.vtosters.lite.utils.Preferences;
public class AdBlockHook {
public static Boolean discoverAdBlock(JSONObject jsonObject) {
@@ -231,10 +232,14 @@ public class AdBlockHook {
continue;
}
if (NewsFeedFiltersUtils.injectFiltersReposts(list) && !isWhitelistedFilters(list)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + list.optInt("post_id") + " from feed, Reason: repost ad");
continue;
try {
if (NewsFeedFiltersUtils.injectFiltersReposts(list) && !isWhitelistedFilters(list)) {
if (dev())
Log.d("NewsfeedAdBlockV2", "Removed post " + list.optInt("post_id") + " from feed, Reason: repost ad");
continue;
}
} catch (Exception ex) {
Log.d("RepostInj", ex.getMessage());
}
newItems.put(list);
@@ -248,6 +253,30 @@ public class AdBlockHook {
return items;
}
private static void parseStoriesItem(JSONObject item) throws JSONException {
var stories = item.optJSONArray("stories");
var newStories = new JSONArray();
if (stories == null) return;
for (int j = 0; j < stories.length(); j++) {
var story = stories.optJSONObject(j);
if (!story.optBoolean("is_ads") && !story.optBoolean("is_promo")) {
if (isStoryNotAd(story)) {
newStories.put(story);
}
} else {
if (dev()) Log.d("StoriesAds", "Fetched ad, owner id " + story.optString("owner_id") + ", caption " + story.optString("caption"));
}
}
item.put("stories", newStories);
}
private static Boolean isStoryNotAd(JSONObject json) {
return !getBoolValue("storiesGroupsAdBlock", false) || (json.has("link") ? isWhitelistedAdStories(json) : true);
}
public static JSONObject storiesads(JSONObject json, boolean isDeleteFix) throws JSONException {
if (!adsstories()) {
@@ -273,8 +302,6 @@ public class AdBlockHook {
}
}
if (!json.has("items")) return json;
var items = json.optJSONArray("items");
if (items != null) {
for (int i = 0; i < items.length(); i++) {
@@ -288,26 +315,6 @@ public class AdBlockHook {
return json;
}
private static void parseStoriesItem(JSONObject item) throws JSONException {
var stories = item.optJSONArray("stories");
var newStories = new JSONArray();
if (stories == null) return;
for (int j = 0; j < stories.length(); j++) {
var story = stories.optJSONObject(j);
if (!story.optBoolean("is_ads") && !story.optBoolean("is_promo")) {
newStories.put(story);
} else {
if (dev())
Log.d("StoriesAds", "Fetched ad, owner id " + story.optString("owner_id") + ", caption " + story.optString("caption"));
}
}
item.put("stories", newStories);
}
public static Boolean isAds(JSONObject list, String type) throws JSONException {
if (list == null || type == null || !ads()) return false;
@@ -345,7 +352,7 @@ public class AdBlockHook {
return true;
}
var whitelist = AndroidUtils.getDefaultPrefs().getStringSet(
var whitelist = Preferences.getPreferences().getStringSet(
"whitelisted_filters_groups",
Collections.emptySet()
);
@@ -360,7 +367,7 @@ public class AdBlockHook {
id = String.valueOf(list.optInt("source_id"));
}
var whitelist = AndroidUtils.getDefaultPrefs().getStringSet(
var whitelist = Preferences.getPreferences().getStringSet(
"whitelisted_ad_groups",
Collections.emptySet()
);
@@ -368,6 +375,17 @@ public class AdBlockHook {
return whitelist.contains(id);
}
public static Boolean isWhitelistedAdStories(JSONObject list) {
var id = String.valueOf(list.optInt("owner_id"));
var whitelist = Preferences.getPreferences().getStringSet(
"whitelisted_stories_ad",
Collections.emptySet()
);
return whitelist.contains(id);
}
public static Boolean hasMiniAppAds(JSONObject list) {
var attachments = list.optJSONArray("attachments");
@@ -0,0 +1,15 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import b.h.g.g.BuildInfo;
public class AppVerHook {
public static int appBuild() {
return getBoolValue("useOldAppVer", false) ? 6561 : BuildInfo.i.e();
}
public static String appVer() {
return getBoolValue("useOldAppVer", false) ? "6.18.1" : BuildInfo.i.d();
}
}
@@ -0,0 +1,9 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
public class BusinessNotifHook {
public static boolean hook(boolean i){
return !getBoolValue("businessDisabler", false) && i;
}
}
@@ -0,0 +1,67 @@
package ru.vtosters.lite.hooks;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.view.View;
import android.widget.Toast;
import com.vk.im.engine.models.dialogs.Dialog;
import com.vtosters.lite.api.ExtendedUserProfile;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import static android.widget.Toast.LENGTH_SHORT;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
public class CallsHook {
public static void forwardToVkOffApps(View view, ExtendedUserProfile p) {
call(view.getContext(), AccountManagerUtils.getUserID(p));
}
public static void forwardToVkOffApps(View view, Dialog dl) {
call(view.getContext(), dl.getId());
}
public static void forwardToVkOffApps(Context ctx, int userId) {
call(ctx, userId);
}
public static void call(Context ctx, int id) {
// FIXME: add com.vk.calls, right now it doesn't support intent
var vkim = checkPackage("com.vk.im");
var vk = checkPackage("com.vkontakte.android");
var callsAvailable = vkim || vk;
if (!callsAvailable) {
Toast.makeText(LifecycleUtils.getCurrentActivity(), AndroidUtils.getString("calls_not_available"), LENGTH_SHORT).show();
try {
var action = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.vkontakte.android"));
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(action);
} catch (android.content.ActivityNotFoundException e) {
var action = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.vkontakte.android"));
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(action);
}
return;
}
var link = "https://vk.com/call?id=" + id;
var startCall = new Intent(Intent.ACTION_VIEW, Uri.parse(link)).setPackage(vk ? "com.vkontakte.android" : "com.vk.im");
startCall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(startCall);
}
private static Boolean checkPackage(String packageName) {
try {
var pm = getGlobalContext().getPackageManager();
return pm.getApplicationInfo(packageName, 0).enabled;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}
@@ -59,11 +59,6 @@ public class CryptImHook {
IMProcessor finalEnabled = enabled;
Activity ctx = getCurrentActivity();
if (ctx == null) {
sendToast(AndroidUtils.getString(R.string.app_activities_error));
return;
}
new VkAlertDialog.Builder(ctx)
.setTitle(AndroidUtils.getString(R.string.encryption_sett))
.setSingleChoiceItems(titles, enabledPosition, (dialog, which) -> {
@@ -111,11 +106,6 @@ public class CryptImHook {
Context ctx = getCurrentActivity();
if (ctx == null) {
sendToast(ctx.getString(R.string.app_activities_error));
return;
}
LinearLayout linearLayout = new LinearLayout(ctx);
final EditText editText = new EditText(ctx);
@@ -1,6 +1,6 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.Preferences.getString;
import static ru.vtosters.lite.utils.AndroidUtils.getStringDate;
import android.annotation.SuppressLint;
@@ -15,18 +15,15 @@ import ru.vtosters.lite.utils.AndroidUtils;
public class DateHook {
public static boolean fulltime() {
return getPrefsValue("dateformat").equals("noyear") || getPrefsValue("dateformat").equals("full") || getPrefsValue("dateformat").equals("noseconds");
return getString("dateformat").equals("noyear") || getString("dateformat").equals("full") || getString("dateformat").equals("noseconds");
}
public static String getDateFormat() {
switch (getPrefsValue("dateformat")) {
case "noyear":
return AndroidUtils.getString(R.string.fulltime2);
case "full":
return AndroidUtils.getString(R.string.fulltime);
default:
return AndroidUtils.getString(R.string.fulltime3);
}
return switch (getString("dateformat")) {
case "noyear" -> AndroidUtils.getString(R.string.fulltime2);
case "full" -> AndroidUtils.getString(R.string.fulltime);
default -> AndroidUtils.getString(R.string.fulltime3);
};
}
@SuppressLint("SimpleDateFormat")
@@ -48,7 +45,7 @@ public class DateHook {
}
public static String getLocale() {
String string = getPrefsValue("lang_value");
String string = getString("lang_value");
if (string.equals("system")) {
return Locale.getDefault().getLanguage();
@@ -83,15 +83,14 @@ public class DockBarInjector {
}
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException |
NoSuchMethodException | InvocationTargetException e) {
NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
for (DockBarTab tab : sManager.getSelectedTabs()) {
MenuItem add = menu.add(0, tab.id, 0, tab.titleID);
Drawable drawable = getResources().getDrawable(tab.iconID);
add.setIcon(new RecoloredDrawable(drawable, getCSTDock()));
add.setIcon(new RecoloredDrawable(getResources().getDrawable(tab.iconID), getCSTDock()));
add.setTitle(getGlobalContext().getString(tab.titleID));
add.setCheckable(true);
}
@@ -102,16 +101,15 @@ public class DockBarInjector {
item.setStaticMode(true);
item.setIconsMode(false);
item.setShiftingMode(false);
item.setTextColor(getCSTDock());
}
}
}
private static void removeAllChildren(ViewGroup view) {
for (int i = 0; i < view.getChildCount(); i++) {
if (view instanceof ViewGroup) {
removeAllChildren((ViewGroup) view.getChildAt(i));
view.removeAllViews();
}
removeAllChildren((ViewGroup) view.getChildAt(i));
view.removeAllViews();
}
}
@@ -190,36 +188,24 @@ public class DockBarInjector {
int val = 0;
switch (tabId) {
case tab_messages:
val = MenuCountersState.j();
break;
case menu_groups:
val = MenuCountersState.h();
break;
case menu_fave:
val = MenuCountersState.c();
break;
case menu_games:
val = MenuCountersState.a();
break;
case menu_photos:
val = MenuCountersState.l();
break;
case menu_videos:
val = MenuCountersState.n();
break;
case tab_feedback:
case tab_messages -> val = MenuCountersState.j();
case menu_groups -> val = MenuCountersState.h();
case menu_fave -> val = MenuCountersState.c();
case menu_games -> val = MenuCountersState.a();
case menu_photos -> val = MenuCountersState.l();
case menu_videos -> val = MenuCountersState.n();
case tab_feedback -> {
if (milkshake())
val = MenuCountersState.k();
break;
case tab_news:
}
case tab_news -> {
if (milkshake() && getBoolValue("newsfeed_notif", true))
val = MenuCountersState.k();
break;
case menu_friends:
}
case menu_friends -> {
if (!milkshake())
val = MenuCountersState.k();
break;
}
}
//
// val = MenuCountersState.g(); // friends ?????? menu_friends
@@ -259,8 +245,8 @@ public class DockBarInjector {
}
try {
JSONArray jSONArray = new JSONArray();
for (int i = 0; i < arrayList.size(); i++) {
jSONArray.put(new JSONObject().put("name", arrayList.get(i)));
for (String s : arrayList) {
jSONArray.put(new JSONObject().put("name", s));
}
for (int i2 = 0; i2 < arr.length(); i2++) {
jSONArray.put(arr.getJSONObject(i2));
@@ -1,7 +1,7 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.ui.components.FragAnimationKit.setAnimations;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.Preferences.getString;
import androidx.fragment.app.FragmentTransaction;
@@ -9,7 +9,7 @@ public class FragAnimationHook {
public static boolean animateOpen(FragmentTransaction transaction) {
setAnimations(transaction);
return getPrefsValue("anim_rtrn_type").equals("noanim") || getPrefsValue("anim_rtrn_type").isEmpty();
return getString("anim_rtrn_type").equals("noanim") || getString("anim_rtrn_type").isEmpty();
}
public static void animateClose(FragmentTransaction transaction) {
@@ -1,6 +1,6 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.musicFixNew;
import static ru.vtosters.lite.utils.Preferences.isValidSignature;
import android.os.Build;
import android.util.Base64;
@@ -27,9 +27,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.HttpsURLConnection;
public class GcmHook {
private static final String agent = String.format("Android-GCM/1.5 (%s %s)", Build.MODEL, Build.MODEL);
private static KeyPair pair;
private static int rid = 0;
static {
@@ -37,14 +41,11 @@ public class GcmHook {
}
public static String requestTokenV2(String orig) {
if (musicFixNew()) return "null"; //not public
return orig;
return isValidSignature() ? "eyJhbGciOiAibm9uZSJ9.eyJub25jZSI6ICJ0ZXN0PT0ifQ." : "e251bGx9";
}
public static String requestToken(String orig) {
if (musicFixNew()) return "{null}"; //not public
if (orig.equals("")) return requestToken();
return orig;
return "yssp9o9p9pamz5t-nvmq8spgwtin3e0==";
}
public static String requestToken() {
@@ -60,6 +61,7 @@ public class GcmHook {
} catch (NoSuchAlgorithmException e) {
xappide = "";
}
AtomicBoolean wait = new AtomicBoolean(false);
StringBuilder sb = new StringBuilder();
List<String> params = new ArrayList<>();
@@ -71,17 +73,20 @@ public class GcmHook {
while (!wait.get()) {
Thread.sleep(100);
}
String token = sb.toString();
if (token.equals("REGISTRATION_ERROR")) {
return requestToken();
}
rid = 0;
String pub22 = genNewKey();
String sig2 = getSig(pub22);
params.clear();
fillParams(params, sig2, pub22, xappide, Long.parseLong(aid.split(" ")[1].split(":")[0]), true);
doRequest("https://android.clients.google.com/c2dm/register3", "POST", params, aid, GcmHook::lambda$requestToken$1);
return token;
return isValidSignature() ? token : null;
} catch (FileNotFoundException e2) {
return requestToken();
} catch (Exception e3) {
@@ -100,12 +105,14 @@ public class GcmHook {
private static void fillParams(List<String> params, String sig, String pub2, String xappid, long androidId, boolean del) {
rid++;
params.add("X-subtype=841415684880");
if (del) {
params.add("X-delete=1");
params.add("X-X-delete=1");
} else {
params.add("X-X-subscription=841415684880");
}
params.add("X-X-subtype=841415684880");
params.add("X-app_ver=" + "7.26");
params.add("X-kid=|ID|" + rid + "|");
@@ -115,19 +122,20 @@ public class GcmHook {
params.add("X-gmsv=11949480");
params.add("X-pub2=" + pub2);
params.add("X-X-kid=|ID|" + rid + "|");
StringBuilder sb = new StringBuilder();
sb.append("X-appid=");
sb.append(xappid);
params.add(sb.toString());
params.add("X-appid=" + xappid);
if (del) {
params.add("X-scope=GCM");
} else {
params.add("X-scope=*");
}
params.add("X-subscription=841415684880");
if (!del) {
params.add("X-gmp_app_id=1:841415684880:android:632f429381141121");
}
params.add("X-app_ver_name=" + "7.26");
params.add("app=com.vkontakte.android");
params.add("sender=841415684880");
@@ -139,9 +147,11 @@ public class GcmHook {
private static String join(String chr, Iterable<String> arr) {
String str = "";
for (String s : arr) {
str = str.isEmpty() ? s : str + chr + s;
}
return str;
}
@@ -168,8 +178,12 @@ public class GcmHook {
}
private static String getRandomAid() {
var arr1 = new String[]{"3974055026275073921", "4418584909973341826", "4585634953328772978"};
var arr2 = new String[]{"1932960345884890854", "6594645578425092292", "1792344590975444730"};
var arr1 = new String[]{
"3974055026275073921", "4418584909973341826", "4585634953328772978"
};
var arr2 = new String[]{
"1932960345884890854", "6594645578425092292", "1792344590975444730"
};
int r = new Random().nextInt(2);
return "AidLogin " + arr1[r] + ":" + arr2[r];
}
@@ -182,6 +196,7 @@ public class GcmHook {
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return Base64.encodeToString(pair.getPublic().getEncoded(), 0);
}
@@ -190,7 +205,9 @@ public class GcmHook {
PrivateKey key = pair.getPrivate();
Signature sign = Signature.getInstance(key instanceof RSAPrivateKey ? "SHA256withRSA" : "SHA256withECDSA");
sign.initSign(key);
sign.update(join("\n", new String[]{"com.vkontakte.android", pub2}).getBytes(StandardCharsets.UTF_8));
sign.update(join("\n", new String[]{
"com.vkontakte.android", pub2
}).getBytes(StandardCharsets.UTF_8));
return Base64.encodeToString(sign.sign(), 0);
} catch (Exception e) {
e.printStackTrace();
@@ -201,4 +218,4 @@ public class GcmHook {
public interface Callback {
void doCallback(String str);
}
}
}
@@ -1,206 +1,24 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.foaf.FoafBase.getBypassedOnlineInfo;
import static ru.vtosters.lite.hooks.DateHook.getLocale;
import static ru.vtosters.lite.hooks.OnlineFormatterHook.onlineHookProfiles;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.getCatalogHeader;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.getCatalogPlaylist;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.getCatalogSeparator;
import static ru.vtosters.lite.music.cache.helpers.PlaylistHelper.getPlaylist;
import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
import static ru.vtosters.lite.proxy.ProxyUtils.getStatic;
import static ru.vtosters.lite.utils.AndroidUtils.getDefaultPrefs;
import static ru.vtosters.lite.utils.Preferences.dev;
import static ru.vtosters.lite.utils.Preferences.friendsblock;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import static ru.vtosters.lite.utils.Preferences.hasVerification;
import static ru.vtosters.lite.utils.Preferences.podcastcatalog;
import static ru.vtosters.lite.utils.VTVerifications.isDeveloper;
import static ru.vtosters.lite.utils.VTVerifications.isPrometheus;
import static ru.vtosters.lite.utils.VTVerifications.isVerified;
import android.text.TextUtils;
import android.util.Log;
import com.vk.core.network.Network;
import com.vk.core.util.DeviceIdProvider;
import com.vk.core.util.LangUtils;
import com.vtosters.lite.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
import java.io.IOException;
import java.text.ParseException;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Random;
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import static ru.vtosters.lite.foaf.FoafBase.getBypassedOnlineInfo;
import static ru.vtosters.lite.hooks.OnlineFormatterHook.onlineHookProfiles;
import static ru.vtosters.lite.utils.Preferences.dev;
import static ru.vtosters.lite.utils.Preferences.friendsblock;
public class JsonInjectors {
private static final OkHttpClient mClient = new OkHttpClient();
public static JSONObject profileButton(JSONObject orig) throws JSONException {
var id = orig.getInt("id");
var newItem = new JSONArray();
if (orig.has("buttons")) {
newItem = orig.getJSONArray("buttons");
}
if (!haveDonateButton() && id == AccountManagerUtils.getUserId()) {
newItem.put(donateRecomm());
}
if (!orig.has("type") && AccountManagerUtils.isVKTester()) {
newItem.put(vktesters(id));
}
orig.put("buttons", newItem);
return orig;
}
public static JSONObject donateRecomm() throws JSONException {
var title = AndroidUtils.getString(R.string.donate_to_vtl);
var link = "https://vk.com/vtosters_official";
var text_color = "2D81E0";
var json = new JSONObject();
var icons = new JSONArray();
var action = new JSONObject();
action.put("target", "internal");
action.put("type", "open_url"); // may be open_url, open_internal_vkui, open_game, help_hint, show_full_post, open_vkapp, show_recommendations_for_post and groups_advertisement
action.put("url", link);
json.put("action", action);
json.put("title", title);
var icon1 = new JSONObject();
icon1.put("url", "https://sun2-10.userapi.com/NLd_rNpGuSaBnPV6O-j5mqCGZk8BK8drAMd2LQ/5R-DEF37PFs.png");
icon1.put("width", "20");
icon1.put("height", "20");
icons.put(icon1);
var icon2 = new JSONObject();
icon2.put("url", "https://sun2-12.userapi.com/N8y9pU1meJq_eug-wB1u-HUuGyMqDfdq7A025w/A4Aio-xuLY8.png");
icon2.put("width", "40");
icon2.put("height", "40");
icons.put(icon2);
var icon3 = new JSONObject();
icon3.put("url", "https://sun2-9.userapi.com/3Hx4hff63_2Lt6wjjthJMF_3QLUswNNlQKoAXQ/oyzEu1NL9T8.png");
icon3.put("width", "80");
icon3.put("height", "80");
icons.put(icon3);
json.put("icons", icons);
json.put("text_color", text_color);
return json;
}
public static JSONObject vktesters(int id) throws JSONException {
var title = AndroidUtils.getString(R.string.tester_profile);
var text_color = "2D81E0";
var link = "https://" + getStatic() + "/bugs?lang=" + LangUtils.a() + "#/reporter" + id;
var json = new JSONObject();
var icons = new JSONArray();
var action = new JSONObject();
action.put("target", "internal");
action.put("type", "open_internal_vkui");
action.put("url", link);
json.put("action", action);
json.put("title", title);
var icon1 = new JSONObject();
icon1.put("url", "https://sun2-10.userapi.com/NLd_rNpGuSaBnPV6O-j5mqCGZk8BK8drAMd2LQ/5R-DEF37PFs.png");
icon1.put("width", "20");
icon1.put("height", "20");
icons.put(icon1);
var icon2 = new JSONObject();
icon2.put("url", "https://sun2-12.userapi.com/N8y9pU1meJq_eug-wB1u-HUuGyMqDfdq7A025w/A4Aio-xuLY8.png");
icon2.put("width", "40");
icon2.put("height", "40");
icons.put(icon2);
var icon3 = new JSONObject();
icon3.put("url", "https://sun2-9.userapi.com/3Hx4hff63_2Lt6wjjthJMF_3QLUswNNlQKoAXQ/oyzEu1NL9T8.png");
icon3.put("width", "80");
icon3.put("height", "80");
icons.put(icon3);
json.put("icons", icons);
json.put("text_color", text_color);
return json;
}
public static JSONObject convBar(JSONObject orig) throws JSONException {
var peerid = Objects.requireNonNull(orig.optJSONObject("peer")).optInt("id");
var pic = "https://image.pngaaa.com/641/326641-middle.png"; // can be null
var text = AndroidUtils.getString(R.string.no_data_error);
var link = "https://vtosters.app"; // can be null
var linktitle = "Test button"; // can be null
var hasIcon = !pic.isEmpty();
var hasButton = !link.isEmpty();
var isPicture = pic.endsWith(".png") || pic.endsWith(".jpg") || pic.endsWith(".jpeg") || pic.endsWith(".webp");
if (isVerified(peerid)) text = AndroidUtils.getString(R.string.i_bought) + " VTosters Premium";
if (isPrometheus(peerid))
text = AndroidUtils.getString(R.string.i_bought) + " VTosters Premium Gold Prime Pro Plus";
if (isDeveloper(peerid)) text = AndroidUtils.getString(R.string.i_created_poop);
if (!isVerified(peerid) || text.equals("") || peerid == AccountManagerUtils.getUserId() || peerid == 0) {
if (getBoolValue("convBarRecomm", false)) {
return null;
} else {
return orig.optJSONObject("conversation_bar");
}
}
if (!dev()) return orig.optJSONObject("conversation_bar"); // for devs only
var json = new JSONObject();
var buttonsJson = new JSONObject();
buttonsJson.put("layout", "tertiary");
buttonsJson.put("text", linktitle);
buttonsJson.put("type", "link");
buttonsJson.put("link", link);
var buttonsjson = new JSONArray(); // max 3 buttons in array
if (hasButton) buttonsjson.put(buttonsJson);
json.put("name", "group_admin_welcome");
json.put("text", text);
json.put("buttons", buttonsjson);
if (hasIcon && isPicture) {
json.put("icon", pic);
}
return json;
}
public static JSONObject menu(JSONObject orig) throws JSONException {
var Special = orig.optJSONArray("special");
var Main = orig.getJSONArray("main");
@@ -214,7 +32,7 @@ public class JsonInjectors {
}
public static JSONObject superapp(JSONObject json) throws JSONException {
var superApps = AndroidUtils.getPreferences().getString("superapp_items",
var superApps = Preferences.getPreferences().getString("superapp_items",
"menu,miniapps,vkpay_slim,greeting,promo,holiday,weather,sport,games,informer,food,event,music,vk_run").split(",");
if (superApps.length == 0) return json;
@@ -242,19 +60,6 @@ public class JsonInjectors {
return json.putOpt("items", newItems);
}
public static JSONObject musiclink(JSONObject json) {
var links = json.optJSONArray("links");
if (links != null) {
if (links.optJSONObject(0).optString("url").contains("?section=recent")) {
json.remove("links");
if (dev()) Log.d("VKMusic", "Removed links buttons");
}
}
return json;
}
public static JSONObject setOnlineInfo(JSONObject json) throws JSONException {
var id = json.optInt("id");
@@ -334,8 +139,8 @@ public class JsonInjectors {
}
public static JSONArray newsfeedlist(JSONArray items) throws JSONException {
var selectedItems = getDefaultPrefs().getString("news_feed_selected_items", "");
var filtersSet = getDefaultPrefs().getStringSet("news_feed_items_set", null);
var selectedItems = Preferences.getPreferences().getString("news_feed_selected_items", "");
var filtersSet = Preferences.getPreferences().getStringSet("news_feed_items_set", null);
var mutableFiltersSet = new LinkedHashSet<String>();
if (filtersSet != null)
mutableFiltersSet.addAll(filtersSet);
@@ -362,165 +167,12 @@ public class JsonInjectors {
.put("is_unavailable", hide);
if (dev()) Log.d("NewsfeedListInj", "Unlocked " + id + " in newsfeed list");
}
getDefaultPrefs().edit().putStringSet("news_feed_items_set", mutableFiltersSet)
Preferences.getPreferences().edit().putStringSet("news_feed_items_set", mutableFiltersSet)
.apply();
return items;
}
public static JSONObject music(JSONObject json) throws JSONException {
var catalog = json.optJSONObject("catalog");
JSONArray oldItems = null;
if (catalog != null) {
oldItems = catalog.optJSONArray("sections");
}
if (oldItems != null && oldItems.optJSONObject(0).optString("url").contains("general")) {
json.optJSONArray("playlists").put(getPlaylist());
if (podcastcatalog()) {
var podcasts = fetchCatalogPodcast();
if (podcasts != null) {
var catalogarr = podcasts.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
var title = catalogarr.optString("title");
var id = catalogarr.optString("id");
var url = catalogarr.optString("url");
if (dev()) Log.d("VKMusic", "Added " + title + " in music sections");
oldItems.put(new JSONObject().put("id", id).put("title", title).put("url", url));
}
}
var playlists = fetchCatalogId("https://vk.com/audio?section=my_playlists");
if (playlists != null) {
var catalogarr = playlists.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
var title = catalogarr.optString("title");
var id = catalogarr.optString("id");
var url = catalogarr.optString("url");
if (dev()) Log.d("VKMusic", "Added " + title + " in music sections");
oldItems.put(new JSONObject().put("id", id).put("title", title).put("url", url));
}
var albums = fetchCatalogId("https://vk.com/audio?section=albums");
if (albums != null) {
var catalogarr = albums.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
var title = catalogarr.optString("title");
var id = catalogarr.optString("id");
var url = catalogarr.optString("url");
if (dev()) Log.d("VKMusic", "Added " + title + " in music sections");
oldItems.put(new JSONObject().put("id", id).put("title", title).put("url", url));
}
var recent = fetchCatalogId("https://vk.com/audio?section=recent");
if (recent != null) {
var catalogarr = recent.optJSONObject("catalog").optJSONArray("sections").optJSONObject(0);
var title = catalogarr.optString("title");
var id = catalogarr.optString("id");
var url = catalogarr.optString("url");
if (dev()) Log.d("VKMusic", "Added " + title + " in music sections");
oldItems.put(new JSONObject().put("id", id).put("title", title).put("url", url));
}
for (int i = 0; i < oldItems.length(); i++) {
var item = oldItems.optJSONObject(i);
var title = item.optString("title");
var id = item.optString("id");
var url = item.optString("url");
var value = AndroidUtils.getPrefsValue("musicdefcatalog");
Log.d("VKMusic", "title: " + title + " id: " + id + " url: " + url + " value: " + value);
if (url.contains(value) && !value.isEmpty()) {
catalog.put("default_section", id);
if (dev()) Log.d("VKMusic", "Added " + title + " as default music section");
}
}
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()){ // inj in playlist list
var blocks = oldItems.optJSONObject(0).optJSONArray("blocks");
var newBlocks = new JSONArray();
if (blocks != null){
newBlocks
.put(getCatalogHeader())
.put(getCatalogPlaylist())
.put(getCatalogSeparator());
Log.d("VKMusic", "added cache catalog playlist");
for (int i = 0; i < blocks.length(); i++) {
newBlocks.put(blocks.optJSONObject(i));
}
oldItems.optJSONObject(0).put("blocks", newBlocks);
}
}
}
return json;
}
public static JSONObject fetchCatalogId(String section) {
if (section.isEmpty()) return null;
var request = new Request.a()
.b("https://" + getApi() + "/method/catalog.getAudio"
+ "?v=5.119"
+ "&https=1"
+ "&need_blocks=1"
+ "&lang="
+ getLocale()
+ "&device_id="
+ DeviceIdProvider.d(AndroidUtils.getGlobalContext())
+ "&url="
+ section
+ "&access_token="
+ AccountManagerUtils.getUserToken())
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
return new JSONObject(mClient.a(request).execute().a().g()).getJSONObject("response");
} catch (JSONException | IOException e) {
Log.d("VTLMusic", "Error: " + e.getMessage());
}
return null;
}
public static JSONObject fetchCatalogPodcast() {
var request = new Request.a()
.b("https://" + getApi() + "/method/catalog.getPodcasts"
+ "?v=5.119"
+ "&https=1"
+ "&need_blocks=1"
+ "&lang="
+ getLocale()
+ "&device_id="
+ DeviceIdProvider.d(AndroidUtils.getGlobalContext())
+ "&access_token="
+ AccountManagerUtils.getUserToken())
.a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")).a();
try {
return new JSONObject(mClient.a(request).execute().a().g()).getJSONObject("response");
} catch (JSONException | IOException e) {
Log.d("VTLMusic", "Error: " + e.getMessage());
}
return null;
}
public static JSONObject friends(JSONObject json) throws JSONException, ParseException, IOException {
JSONObject catalog = onlineHookProfiles(json);
boolean sectionexecute = true;
@@ -586,8 +238,4 @@ public class JsonInjectors {
return json;
}
public static boolean haveDonateButton() {
return hasVerification() || new Random().nextInt(6) != 1;
}
}
@@ -1,33 +1,34 @@
package ru.vtosters.lite.hooks;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import b.h.g.k.VKProgressDialog;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vtosters.lite.R;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.ssfs.UsersList;
import ru.vtosters.lite.themes.ThemesManager;
import ru.vtosters.lite.ui.dialogs.DisableBattery;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.*;
import static ru.vtosters.lite.ui.dialogs.ServerDialog.sendRequest;
import static ru.vtosters.lite.utils.CacheUtils.getInstance;
import static ru.vtosters.lite.utils.NewsFeedFiltersUtils.setupFilters;
import static ru.vtosters.lite.utils.Preferences.checkupdates;
import static ru.vtosters.lite.utils.ThemesUtils.isDarkTheme;
import static ru.vtosters.lite.utils.ThemesUtils.setNeededTheme;
import android.app.Activity;
import android.os.Build;
import android.preference.PreferenceManager;
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
import ru.vtosters.lite.concurrent.VTExecutors;
import ru.vtosters.lite.ui.dialogs.DisableBattery;
import ru.vtosters.lite.ui.dialogs.InstallGMS;
import ru.vtosters.lite.ui.dialogs.OTADialog;
import ru.vtosters.lite.ui.dialogs.Start;
import ru.vtosters.lite.utils.DeletedMessagesHandler;
public class MainActivityInjector {
public static void inject(Activity activity) {
setNeededTheme(activity);
sendRequest();
UsersList.getUsersList();
if (checkupdates()) OTADialog.checkUpdates(activity);
VTExecutors.getIoScheduler().a(DeletedMessagesHandler::reloadMessagesList); // ioScheduler
Start.alert(activity);
InstallGMS.alert(activity);
DisableBattery.alert(activity);
VTExecutors.getSlowTasksScheduler().a(() -> {
getInstance().autoCleaningCache();
@@ -39,6 +40,45 @@ public class MainActivityInjector {
NotificationChannels.createChannels();
}
if(Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && ThemesManager.canApplyCustomAccent()) {
Preferences.updateBuildNumber();
updateBinsAndTmpArchive(activity);
}
VTExecutors.getIoScheduler().a(DeletedMessagesHandler::reloadMessagesList); // ioScheduler
if(activity.getIntent().getAction() != null && Intent.ACTION_APPLICATION_PREFERENCES.equals(activity.getIntent().getAction())) {
NavigatorUtils.switchToSettings(activity);
return;
}
Start.alert(activity);
InstallGMS.alert(activity);
DisableBattery.alert(activity);
// VKIDProtection.alert(activity);
}
private static void updateBinsAndTmpArchive(Activity activity) {
final VKProgressDialog dialog = new VKProgressDialog(activity);
dialog.setMessage("Применение акцента");
dialog.show();
VTExecutors.getIoExecutor().execute(() -> {
try {
ThemesManager.generateModApk(ThemesUtils.getReservedAccent());
activity.runOnUiThread(LifecycleUtils::restartApplication);
} catch(Throwable e) {
e.printStackTrace();
activity.runOnUiThread(() -> {
dialog.cancel();
new VkAlertDialog.Builder(activity)
.setTitle("Ошикба")
.setMessage("Ошибка при применении акцента:\n" + e)
.setPositiveButton(R.string.ok, null)
.show();
});
}
});
}
}
@@ -1,6 +1,5 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.ui.dialogs.MessageSettings.argDialog;
import static ru.vtosters.lite.ui.dialogs.MessageSettings.bombCount;
import static ru.vtosters.lite.ui.dialogs.MessageSettings.isSilentEnabled;
import static ru.vtosters.lite.utils.Preferences.autoalltranslate;
@@ -17,6 +16,7 @@ import java.util.ArrayList;
import java.util.regex.Pattern;
import ru.vtosters.lite.translators.BaseTranslator;
import ru.vtosters.lite.ui.dialogs.MessageSettings;
import ru.vtosters.lite.utils.LifecycleUtils;
public class MessagesHook {
@@ -70,7 +70,7 @@ public class MessagesHook {
public static void onLongClick(View view) {
view.setOnLongClickListener(v -> {
argDialog(v.getContext());
MessageSettings.argDialog(v.getContext());
return true;
});
}
@@ -99,19 +99,13 @@ public class MessagesHook {
}
private static int expireTime() {
switch (bombCount) {
case "15s":
return 15;
case "1m":
return 60;
case "5m":
return 300;
case "1h":
return 3600;
case "24h":
return 86400;
default:
return 0;
}
return switch (bombCount) {
case "15s" -> 15;
case "1m" -> 60;
case "5m" -> 300;
case "1h" -> 3600;
case "24h" -> 86400;
default -> 0;
};
}
}
@@ -1,11 +1,9 @@
package ru.vtosters.lite.hooks;
import static java.lang.Long.MAX_VALUE;
import static ru.vtosters.lite.utils.AndroidUtils.getDefaultPrefs;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getPrefsValue;
import static ru.vtosters.lite.utils.Preferences.getString;
import static ru.vtosters.lite.utils.Preferences.ads;
import static ru.vtosters.lite.utils.Preferences.adsslider;
import static ru.vtosters.lite.utils.Preferences.authorsrecomm;
import static ru.vtosters.lite.utils.Preferences.friendsrecomm;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
@@ -24,20 +22,19 @@ import java.util.LinkedHashSet;
import java.util.List;
import ru.vtosters.lite.utils.AccountManagerUtils;
import ru.vtosters.lite.utils.Preferences;
public class NewsfeedHook {
public static long getUpdateNewsfeed(boolean refresh_timeout) {
if (vkme()) {
return MAX_VALUE;
}
switch (getPrefsValue("newsupdate")) {
case "no_update":
return MAX_VALUE;
case "imd_update":
return 10000L;
default:
return Preference.b().getLong(refresh_timeout ? "refresh_timeout_top" : "refresh_timeout_recent", 600000L);
}
return switch (getString("newsupdate")) {
case "no_update" -> MAX_VALUE;
case "imd_update" -> 10000L;
default ->
Preference.b().getLong(refresh_timeout ? "refresh_timeout_top" : "refresh_timeout_recent", 600000L);
};
}
public static List hideElement(ArrayList list) {
@@ -70,22 +67,29 @@ public class NewsfeedHook {
}
public static boolean isWhitelistedFilter(ExtendedCommunityProfile eup) {
return getDefaultPrefs().getStringSet(
return Preferences.getPreferences().getStringSet(
"whitelisted_filters_groups", Collections.emptySet()
).contains(String.valueOf(AccountManagerUtils.getUserID(eup)));
}
public static boolean isWhitelistedAd(ExtendedCommunityProfile eup) {
return getDefaultPrefs().getStringSet(
return Preferences.getPreferences().getStringSet(
"whitelisted_ad_groups",
Collections.emptySet()
).contains(String.valueOf(AccountManagerUtils.getUserID(eup)));
}
public static boolean isWhitelistedAdStories(ExtendedCommunityProfile eup) {
return Preferences.getPreferences().getStringSet(
"whitelisted_stories_ad",
Collections.emptySet()
).contains(String.valueOf(AccountManagerUtils.getUserID(eup)));
}
public static void setWhitelistedFilter(ExtendedCommunityProfile eup, boolean needWhitelist) {
var id = String.valueOf(AccountManagerUtils.getUserID(eup));
var mutableAdsSet = new LinkedHashSet<>(getDefaultPrefs().getStringSet(
var mutableAdsSet = new LinkedHashSet<>(Preferences.getPreferences().getStringSet(
"whitelisted_filters_groups",
Collections.emptySet())
);
@@ -95,13 +99,13 @@ public class NewsfeedHook {
mutableAdsSet.remove(id);
}
getDefaultPrefs().edit().putStringSet("whitelisted_filters_groups", mutableAdsSet).apply();
Preferences.getPreferences().edit().putStringSet("whitelisted_filters_groups", mutableAdsSet).apply();
}
public static void setWhitelistedAd(ExtendedCommunityProfile eup, boolean needWhitelist) {
var id = String.valueOf(AccountManagerUtils.getUserID(eup));
var mutableAdsSet = new LinkedHashSet<>(getDefaultPrefs().getStringSet(
var mutableAdsSet = new LinkedHashSet<>(Preferences.getPreferences().getStringSet(
"whitelisted_ad_groups",
Collections.emptySet()));
@@ -111,18 +115,36 @@ public class NewsfeedHook {
mutableAdsSet.remove(id);
}
getDefaultPrefs().edit().putStringSet("whitelisted_ad_groups", mutableAdsSet).apply();
Preferences.getPreferences().edit().putStringSet("whitelisted_ad_groups", mutableAdsSet).apply();
}
public static void setWhitelistedAdStories(ExtendedCommunityProfile eup, boolean needWhitelist) {
var id = String.valueOf(AccountManagerUtils.getUserID(eup));
var mutableAdsSet = new LinkedHashSet<>(Preferences.getPreferences().getStringSet(
"whitelisted_stories_ad",
Collections.emptySet()));
if (needWhitelist) {
mutableAdsSet.add(id);
} else {
mutableAdsSet.remove(id);
}
Preferences.getPreferences().edit().putStringSet("whitelisted_stories_ad", mutableAdsSet).apply();
}
public static void adsParams(HashSet<String> hashSet) {
if (ads())
if (ads()) {
hashSet.add("ads_disabled");
} else {
hashSet.add("ads_app_slider");
hashSet.add("ads_site_slider");
}
hashSet.add("ads_app");
hashSet.add("ads_site");
hashSet.add("ads_post");
if (!adsslider())
hashSet.add("ads_app_slider");
hashSet.add("ads_site_slider");
hashSet.add("ads_app_video");
hashSet.add("ads_post_pretty_cards");
hashSet.add("ads_post_snippet_video");
@@ -29,117 +29,61 @@ public class OnlineFormatterHook {
public static String getAppName(int appid) { // thanks to egormetlitsky (vk mp3 mod) for helping with online ids
if (appid <= 3 || !getBoolValue("onlineformatter", true)) return null;
switch (appid) {
case 2274003:
return "Android";
case 6328039:
case 6328868:
case 6820516:
return "VK mp3 mod";
case 5910839:
return "DarkVK";
case 3140623:
return "iPhone";
case 3087106:
return "iPhone (Dev)";
case 3682744:
return "iPad";
case 4083558:
case 5316500:
return "VFeed (iPhone)";
case 4385266:
return "VFeed Pro (iPhone)";
case 3502557:
case 3502561:
return "Windows Phone";
case 3697615:
return "Windows";
case 2685278:
return "Kate Mobile";
case 3469984:
return "Lynt";
case 6121396:
return "VK Admin (Android)";
case 5776857:
return "VK Admin (iPhone)";
case 5027722:
return "VK Messenger (Desktop)";
case 6146827:
return AndroidUtils.getString(R.string.vk_messenger) + " (Android)"; // VK Мессенджер (Android)
case 6482950:
return AndroidUtils.getString(R.string.vk_messenger) + " (iPhone)"; // VK Мессенджер (iPhone)
case 6481715:
return AndroidUtils.getString(R.string.vk_messenger) + " Dev (iPhone)"; // VK Мессенджер Dev (iPhone)
case 7799655:
return AndroidUtils.getString(R.string.vk_mail_online); // VK Почта
case 7598572:
return AndroidUtils.getString(R.string.sferum_online) + " (Android)"; // Сферум (Android)
case 7571751:
return AndroidUtils.getString(R.string.sferum_online) + " (iPhone)"; // Сферум (iPhone)
case 7556576:
return AndroidUtils.getString(R.string.sferum_online); // Сферум
case 7497650:
return "VK ID";
case 8094476:
return AndroidUtils.getString(R.string.vk_calls_online) + " (Android)"; // VK Звонки (Android)
case 8093730:
return AndroidUtils.getString(R.string.vk_calls_online) + " (iPhone)"; // VK Звонки (iPhone)
case 7793118:
return AndroidUtils.getString(R.string.vk_calls_online) + " (Desktop)"; // VK Звонки (Desktop)
case 6767438:
return AndroidUtils.getString(R.string.vk_music_online) + " (Android)"; // VK Музыка (Android)
case 8113297:
return AndroidUtils.getString(R.string.vk_clips_online) + " (Android)"; // VK Клипы (Android)
case 8106428:
return AndroidUtils.getString(R.string.vk_clips_online) + " (iPhone)"; // VK Клипы (iPhone)
case 5044491:
return "Candy";
case 8114066:
return AndroidUtils.getString(R.string.vk_video_online); // VK Видео
case 4894723:
return "Phoenix Lite";
case 4994316:
return "Phoenix for VK";
case 3032107:
return "Vika";
case 3698024:
return "Instagram";
case 4580399:
return "Snapster (Android)";
case 4986954:
return "Snapster (iPhone)";
case 3900098:
case 5353544:
return "Домофон";
case 5462895:
return "Fast Messenger";
case 6964679:
return "Fast";
case 6030003:
return "Juno Messenger";
case 5955265:
return "VK Mobile";
case 6614620:
return "Laney";
case 5632485:
return "SpaceVK";
case 6287487:
return "vk.com";
case 4542624:
return "Black VK";
case 3917910:
return "Miranda NG (bridge)";
case 8043814:
return "Quise";
case 7221936:
return "Вечный Online";
case 6892935:
return "VK Helper";
case 6248661:
return "MemeVoices for VK";
default:
return getAppNameFromOnline(appid);
}
return switch (appid) {
case 2274003 -> "Android";
case 6328039, 6328868, 6820516 -> "VK mp3 mod";
case 5910839 -> "DarkVK";
case 3140623 -> "iPhone";
case 3087106 -> "iPhone (Dev)";
case 3682744 -> "iPad";
case 4083558, 5316500 -> "VFeed (iPhone)";
case 4385266 -> "VFeed Pro (iPhone)";
case 3502557, 3502561 -> "Windows Phone";
case 3697615 -> "Windows";
case 2685278 -> "Kate Mobile";
case 3469984 -> "Lynt";
case 6121396 -> "VK Admin (Android)";
case 5776857 -> "VK Admin (iPhone)";
case 5027722 -> "VK Messenger (Desktop)";
case 6146827 -> AndroidUtils.getString(R.string.vk_messenger) + " (Android)"; // VK Мессенджер (Android)
case 6482950 -> AndroidUtils.getString(R.string.vk_messenger) + " (iPhone)"; // VK Мессенджер (iPhone)
case 6481715 ->
AndroidUtils.getString(R.string.vk_messenger) + " Dev (iPhone)"; // VK Мессенджер Dev (iPhone)
case 7799655 -> AndroidUtils.getString(R.string.vk_mail_online); // VK Почта
case 7598572 -> AndroidUtils.getString(R.string.sferum_online) + " (Android)"; // Сферум (Android)
case 7571751 -> AndroidUtils.getString(R.string.sferum_online) + " (iPhone)"; // Сферум (iPhone)
case 7556576 -> AndroidUtils.getString(R.string.sferum_online); // Сферум
case 7497650 -> "VK ID";
case 8094476 -> AndroidUtils.getString(R.string.vk_calls_online) + " (Android)"; // VK Звонки (Android)
case 8093730 -> AndroidUtils.getString(R.string.vk_calls_online) + " (iPhone)"; // VK Звонки (iPhone)
case 7793118 -> AndroidUtils.getString(R.string.vk_calls_online) + " (Desktop)"; // VK Звонки (Desktop)
case 6767438 -> AndroidUtils.getString(R.string.vk_music_online) + " (Android)"; // VK Музыка (Android)
case 8113297 -> AndroidUtils.getString(R.string.vk_clips_online) + " (Android)"; // VK Клипы (Android)
case 8106428 -> AndroidUtils.getString(R.string.vk_clips_online) + " (iPhone)"; // VK Клипы (iPhone)
case 5044491 -> "Candy";
case 8114066 -> AndroidUtils.getString(R.string.vk_video_online); // VK Видео
case 4894723 -> "Phoenix Lite";
case 4994316 -> "Phoenix for VK";
case 3032107 -> "Vika";
case 3698024 -> "Instagram";
case 4580399 -> "Snapster (Android)";
case 4986954 -> "Snapster (iPhone)";
case 3900098, 5353544 -> "Домофон";
case 5462895 -> "Fast Messenger";
case 6964679 -> "Fast";
case 6030003 -> "Juno Messenger";
case 5955265 -> "VK Mobile";
case 6614620 -> "Laney";
case 5632485 -> "SpaceVK";
case 6287487 -> "vk.com";
case 4542624 -> "Black VK";
case 3917910 -> "Miranda NG (bridge)";
case 8043814 -> "Quise";
case 7221936 -> "Вечный Online";
case 6892935 -> "VK Helper";
case 6248661 -> "MemeVoices for VK";
default -> getAppNameFromOnline(appid);
};
}
@@ -1,57 +1,51 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.vk.core.dialogs.actionspopup.ActionsPopup;
import com.vk.core.drawable.RecoloredDrawable;
import com.vk.core.dialogs.alert.VkAlertDialog;
import com.vk.core.ui.themes.VKThemeHelper;
import com.vk.core.util.ContextExtKt;
import com.vk.core.util.ToastUtils;
import com.vk.dto.common.AttachmentWithMedia;
import com.vk.dto.common.ImageSize;
import com.vtosters.lite.R;
import com.vtosters.lite.attachments.DocumentAttachment;
import com.vtosters.lite.attachments.PhotoAttachment;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.LifecycleUtils;
import ru.vtosters.lite.utils.Preferences;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.ThemesUtils;
public class PhotoViewer {
static OkHttpClient client = new OkHttpClient();
static OkHttpClient client = VtOkHttpClient.getInstance();
public static boolean interceptClick(AttachmentWithMedia attachment, MenuItem item, View view) {
if (item.getItemId() == R.id.search_photo) {
if (attachment instanceof PhotoAttachment) {
searchPhoto(getUrlFromPhotoAttachment((PhotoAttachment) attachment));
searchDialog(LifecycleUtils.getCurrentActivity(), getUrlFromPhotoAttachment((PhotoAttachment) attachment));
} else if (attachment instanceof DocumentAttachment) {
var documentAttachment = (DocumentAttachment) attachment;
if (documentAttachment.J != null)
try {
searchPhoto(getUrlFromDocumentAttachment(documentAttachment));
searchDialog(LifecycleUtils.getCurrentActivity(), getUrlFromDocumentAttachment(documentAttachment));
} catch (Exception e) {
ToastUtils.a(R.string.photo_get_error);
}
@@ -96,18 +90,16 @@ public class PhotoViewer {
@SuppressLint("UseCompatLoadingForDrawables")
public static void addMenuItems(AttachmentWithMedia attachment, ActionsPopup.b actionPopup, int i, boolean z) {
actionPopup.a(R.string.search_photo_content,
ThemesUtils.isDarkTheme()
? AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_menu_search_outline_28)
: new RecoloredDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_menu_search_outline_28), ThemesUtils.getAccentColor()),
ContextExtKt.b(AndroidUtils.getGlobalContext(), R.drawable.ic_menu_search_outline_28, VKThemeHelper.g(R.attr.header_tint)),
false,
() -> {
if (attachment instanceof PhotoAttachment) {
searchPhoto(getUrlFromPhotoAttachment((PhotoAttachment) attachment));
searchDialog(LifecycleUtils.getCurrentActivity(), getUrlFromPhotoAttachment((PhotoAttachment) attachment));
} else if (attachment instanceof DocumentAttachment) {
var documentAttachment = (DocumentAttachment) attachment;
if (documentAttachment.J != null)
try {
searchPhoto(getUrlFromDocumentAttachment(documentAttachment));
searchDialog(LifecycleUtils.getCurrentActivity(), getUrlFromDocumentAttachment(documentAttachment));
} catch (Exception e) {
ToastUtils.a(R.string.photo_get_error);
}
@@ -118,9 +110,7 @@ public class PhotoViewer {
}
);
actionPopup.a(R.string.copy_photo_url_content,
!ThemesUtils.isDarkTheme()
? new RecoloredDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_copy_outline_28), ThemesUtils.getAccentColor())
: new RecoloredDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_copy_outline_28), Color.WHITE),
ContextExtKt.b(AndroidUtils.getGlobalContext(), R.drawable.ic_copy_outline_28, VKThemeHelper.g(R.attr.header_tint)),
false,
() -> {
if (attachment instanceof PhotoAttachment) {
@@ -140,10 +130,7 @@ public class PhotoViewer {
}
);
actionPopup.a(R.string.open_original_photo_content,
ThemesUtils.isDarkTheme()
? AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_link_outline_28)
: new RecoloredDrawable(AndroidUtils.getGlobalContext().getDrawable(R.drawable.ic_link_outline_28),
ThemesUtils.getAccentColor()),
ContextExtKt.b(AndroidUtils.getGlobalContext(), R.drawable.ic_link_outline_28, VKThemeHelper.g(R.attr.header_tint)),
false,
() -> {
if (attachment instanceof PhotoAttachment) {
@@ -191,21 +178,46 @@ public class PhotoViewer {
return max.url;
}
private static void searchPhoto(String url) {
if (url == null || url.isEmpty()) return;
switch (getPreferences().getInt("search_engine", 0)) {
case 0:
searchWithYandex(url);
break;
case 1:
searchWithGoogle(url);
break;
case 2:
searchWithBing(url);
break;
private static void searchDialog(Context context, String url) {
if (selectedEngine() == 0) {
var items = new String[]{
"Yandex", "Google", "Bing", "TraceMoe", "Ascii2d", "Saucenao"
};
new VkAlertDialog.Builder(context)
.setItems(items, (dl, i) -> {
searchPhoto(i + 1, url);
})
.show();
} else {
searchPhoto(selectedEngine(), url);
}
}
private static int selectedEngine() {
return Preferences.getPreferences().getInt("search_engine", 0);
}
private static void searchPhoto(int i, String url) {
if (url == null || url.isEmpty()) {
Log.d("PhotoViewer", "url is null or empty");
return;
}
switch (i) {
case 1 -> searchWithYandex(url);
case 2 -> searchWithGoogle(url);
case 3 -> searchWithBing(url);
case 4 -> searchWithTraceMoe(url);
case 5 -> searchWithAscii2d(url);
case 6 -> searchWithSaucenao(url);
}
}
private static void searchPhoto(String url) {
searchPhoto(selectedEngine(), url);
}
private static void searchWithYandex(String url) {
var request = new Request.a()
.b(String.format("https://yandex.ru/images-apphost/image-download?url=%s&images_avatars_size=preview&images_avatars_namespace=images-cbir",
@@ -233,8 +245,19 @@ public class PhotoViewer {
}
private static void searchWithGoogle(String url) {
openUrl("https://www.google.ru/searchbyimage?image_url=" +
URLEncoder.encode(url));
openUrl("https://www.google.com/searchbyimage?image_url=" + URLEncoder.encode(url) + "&client=app");
}
private static void searchWithTraceMoe(String url) {
openUrl("https://trace.moe/?url=" + URLEncoder.encode(url));
}
private static void searchWithAscii2d(String url) {
openUrl("https://ascii2d.net/search/url/" + URLEncoder.encode(url));
}
private static void searchWithSaucenao(String url) {
openUrl("https://saucenao.com/search.php?url=" + URLEncoder.encode(url));
}
private static void searchWithBing(String url) {
@@ -1,20 +1,12 @@
package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.preferences;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.*;
import com.facebook.drawee.generic.RoundingParams;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.Preferences;
public class PicRoundingHook {
private static final int pref = preferences.getInt("pic_rounding", 0);
private static final int pref = Preferences.getPreferences().getInt("pic_rounding", 0);
public static RoundingParams inject() {
if (pref > 0) {
@@ -3,7 +3,7 @@ package ru.vtosters.lite.hooks;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
public class PromoStickersHook {
public static String hook(){
public static String hook() {
return getBoolValue("promotedstickers", true) ? "purchased,active" : "purchased,active,promoted";
}
}

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