diff --git a/.github/features.md b/.github/features.md index debb6c6db0..cd3d6a7b57 100644 --- a/.github/features.md +++ b/.github/features.md @@ -1,49 +1,22 @@ -### Все функции модификации +### Весь функционал модификации -* Лента - * Блокировка рекламы в ленте - * Блокировка постов через фильтры - * Возможность добавлять в вайтлист фильтров группы, чтобы в них не удалялись посты +# Реклама + * Блокировка рекламы в ленте + + * Блокировка историй с возможной рекламой + + * Блокировка постов: + * С источником + * С MiniApp + * Через фильтры групп, слов + * Возможность добавлять в вайтлист фильтров группы, чтобы в них не удалялись посты+ * Возможность отключить рекомендации от ВК -* Медиаконтент + * Отключение отправки метрики + +# Интерфейс и внешний вид * Округление дат * 1 января в 4:20 * 1 января в 4:20:00 * 1 января 2000 в 4:20:00 - * Округление чисел - * Сокращение постов **(Показать полностью)** - * Результаты опросов без участия - * Возможность указывать папку для загрузки фото/видео/музыки/всего - * Интеграция с LAST.FM (а также возможность скробблинга) - * Возможность **кешировать музыку / автокешировать ее при прослушивании** - * Возможность выбирать раздел музыки, открываемый по умолчанию - * Возможность скачивать видео, очищать историю просмотра видео - * Возможность найти фотографию через поисковые системы - * Интеграция с VK X - * Возможность кешировать треки в приложении -* Сообщения - * Обои в сообщениях - * Возможность выключения кнопки записи голосовых сообщений - * Возможность скрыть блок рекомендаций в сообщениях - * Режим **VK Me** - * Возможность вернуть вкладку уведомлений для режима VK Me - * Встроенный переводчик текста для сообщений - * Возможность выбрать язык перевода - * Автоматический перевод ваших сообщений на выбранный язык - * Альтернативный дизайн эмодзи меню - * Возможность использовать системные эмодзи - * Возможнсть использовать Enter с аппаратной клавиатуры - * Панель ввода из приложения VK для iOS -* Telegram Стикеры - * Возможность загружать свои стикерпаки из Telegram во ВКонтакте -* Внешний вид - * Возможность отключать новый дизайн версий VK (Milkshake) - * Название заголовков в докбаре - * Возможность использовать акцент для иконок - * Возможность отключить счётчики в докбаре - * Смена иконки приложения (содержит много уникальных иконок для донатеров) - * Возможность покрасить панель навигации, веб-страницы -* Интерфейс + * Округление чисел * Возможность отключить блок историй наверху ленты * Возможность отключить боковую камеру в разделе «Новости» * Возможность отключить блок «Что у вас нового?» в разделе «Новости» @@ -56,23 +29,58 @@ * Возможность отключить сокращение названий разделов в меню * Возможность скрыть блок рекомендаций друзей в разделе «Друзья» * Возможность скрыть подписи в настройках -* Редактор докбара -* Редактор элементов Superapp -* Активность + * Анимированный экран входа для Android 12+ + * Редактор докбара + * Редактор элементов Superapp + * Возможность отключать новый дизайн версий VK (Milkshake) + * Название заголовков в докбаре + * Возможность использовать свой акцент для иконок + * Возможность отключить счётчики в докбаре + * Смена иконки приложения (содержит много уникальных иконок для донатеров) + * Возможность покрасить панель навигации, веб-страницы + * Обои в сообщениях + * Возможность выключения кнопки записи голосовых сообщений + * Возможность скрыть блок рекомендаций в сообщениях + * Режим **VK Me** + * Возможность вернуть вкладку уведомлений для режима VK Me + * Встроенный переводчик текста для сообщений + * Альтернативный дизайн эмодзи меню + * Возможность использовать системные эмодзи + * Панель ввода из приложения VK для iOS + * Сокращение постов **(Показать полностью)** + * Результаты опросов без участия + + +# Медиаконтент + * Интеграция с LAST.FM (а также возможность скробблинга) + * Возможность **кешировать музыку / автокешировать ее при прослушивании** + * Возможность выбирать раздел музыки, открываемый по умолчанию + * Возможность скачивать видео, очищать историю просмотра видео + * Возможность найти фотографию через поисковые системы ? + * Интеграция с VK X + * Поддержка новых текстов в музыке (и с Genius в том числе) + +# Сообщения + * Шифрование сообщений * Возможность видеть удаленные сообщения - * Функционал, позволяющий оставаться в «оффлайне» (принудительный оффлайн/не отправлять статус онлайна и т.п.) * Нечиталка/неписалка для ЛС/бесед/ботов/групп + * Возможность загружать свои стикерпаки из Telegram во ВКонтакте + * Возможнсть использовать Enter с аппаратной клавиатуры + +# Активность + * Возможность сохранять удалённые сообщения + * Функционал, позволяющий оставаться в «оффлайне» (принудительный оффлайн/не отправлять статус онлайна и т.п.) * Возможность отключить информацию о том, что вы просмотрели историю/прослушали голосовое сообщение * Возможность видеть онлайн пользователей, если у вас скрыт онлайн через VK Messenger * Возможность сохранять настройки бесшумных сообщений/сообщений-бомбочек * Отключить уведомление о том, что вы сделали скриншот в беседе * Отключение редиректа через away.php - * Отключение отправки метрики -* Прокси + * Отключение бизнес-уведомлений + +# Прокси * Поддержка сторонних прокси, таких как: Zaborona, VikaMobile, рандомные прокси и прочие - * Возможность использовать **HTTP/HTTPS/SOCKS** прокси, менять домены **API/OAuth/Static** -* Другое - * Синхронизация контактов с VK - * Очистка кеша приложения (**всё/стикеры/изображения/видео/сообщения/WebView**) + * Возможность использовать **HTTP/HTTPS/SOCKS** прокси, менять домены **API/OAuth/Static** + +# Другое * Автоочистка кеша при достижении указанного вами значения (**100/500 MB, 1/2/5 GB**) - * Функционал для **дебага приложения/бэкап возможности/верификации VTosters** + * Возможность создания резервной копии (бекапа) настроек и аккаунтов diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6314f0d355..7766ceab23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up JDK 14 + - name: Set up JDK uses: actions/setup-java@v3 with: - java-version: '14' + java-version: '17' distribution: 'adopt' - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 101a180a0d..208f81ddb7 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ * Стабильные сборки находятся в [Releases](https://github.com/vtosters/lite/releases) -Зеркало на нашем [ Git](https://git.maki.su/gdlbo/lite) +Зеркало на нашем [ Git](https://git.maki.su/vtosters/lite) ### Быстрая навигация @@ -129,6 +129,15 @@ Для сборки необходимо вызвать одну из следующих gradle tasks: -+ `./gradlew buildVTL` - соберет и подпишет APK -+ `./gradlew buildAndInstallVTL` - соберет, подпишет и попытается установить через ADB -+ [FOR DEVS] `./gradlew prepareDexForMerge` - соберет все необходимое для сборки в `smali/` ++ `./gradlew buildVTL + ` - соберет и подпишет APK ++ `./gradlew buildAndInstallVTL + ` - соберет, подпишет и попытается установить через ADB ++ `./gradlew buildAndLaunchVTL + ` - соберет, подпишет, попытается установить через ADB и запустит на устройстве ++ [FOR DEVS] `./gradlew prepareDexForMerge + ` - соберет все необходимое для сборки в `smali/` ++ [FOR DEVS] `./gradlew exportDex + ` - экспортирование дексов из собранного приложения для переноса в `smali/` +``` +Build types: + +Dev +Beta +Release +``` \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 93e0be16f2..4012912910 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,6 @@ import org.apache.tools.ant.taskdefs.condition.Os -import java.util.zip.ZipEntry import java.util.zip.ZipFile -import java.util.zip.ZipInputStream plugins { id 'com.android.application' @@ -21,6 +19,10 @@ cargo { def smaliFolder = projectDir.toPath().resolve("../smali").normalize() def scriptsFolder = projectDir.toPath().resolve("../scripts").normalize() +//you can add own folders with decompiled smali code +def dexes=smaliFolder.toFile() + .listFiles( { f -> f.isDirectory() && f.name.startsWith("smali") } as FileFilter) + .size() versionFile { file = projectDir.toPath().resolve("src/main/assets/version.properties").toFile() @@ -50,15 +52,22 @@ android { } buildTypes { - dev { - initWith release + release { + minifyEnabled true buildConfigField 'String', 'APPCENTER_KEY', '""' + proguardFile 'proguard-rules.pro' } - beta { - initWith release + rc { + minifyEnabled true buildConfigField 'String', 'APPCENTER_KEY', '""' + proguardFile 'proguard-rules.pro' } - release { + dev { + minifyEnabled false + buildConfigField 'String', 'APPCENTER_KEY', '""' + } + beta { + minifyEnabled false buildConfigField 'String', 'APPCENTER_KEY', '""' } } @@ -70,77 +79,60 @@ android { } namespace 'ru.vtosters.lite' ndkVersion '25.2.9519653' + buildFeatures { + aidl true + } +} + +task testClasses { + doLast { + println 'This is a dummy testClasses task' + } } dependencies { - implementation "com.microsoft.appcenter:appcenter-analytics:5.0.0" - implementation "com.microsoft.appcenter:appcenter-crashes:5.0.0" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.1' + implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.1' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4' + implementation 'com.google.guava:guava:23.3-android' + implementation fileTree(dir: 'libs', include: ['*.aar']) compileOnly fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.google.guava:guava:23.3-android' } task generateBuildTasks() { android.applicationVariants.all { variant -> variant.outputs.each { output -> def buildType = output.name.toString().capitalize() - def lowerBuildType = buildType.toLowerCase() + if(buildType=="Base")return task "exportDex$buildType"() { dependsOn('assemble' + buildType) doLast { try { - var z = "" - if (lowerBuildType != "debug") { - z = "-unsigned" - } - - File moduleDir = projectDir - ZipInputStream inputStream = new ZipInputStream(new FileInputStream(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk")) - ZipFile file = new ZipFile(moduleDir.getAbsolutePath() + "/build/outputs/apk/$lowerBuildType/app-$lowerBuildType${z}.apk"); - ZipEntry entry - while ((entry = inputStream.nextEntry) != null) { - if (entry.getName().equals("classes.dex")) { - print('classes.dex found!') - - FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes6.dex")) - - byte[] buffer = new byte[1024] - int len = 0 - while ((len = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, len) - } - outputStream.close() - } - if (entry.getName().equals("classes2.dex")) { - print('\nclasses2.dex found!') - - FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "classes7.dex")) - - byte[] buffer = new byte[1024] - int len = 0 - while ((len = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, len) - } - outputStream.close() - } - } - - for (ZipEntry e : Collections.list(file.entries())) { - if (e.getName().endsWith("assets/version.properties")) { - var is = file.getInputStream(e) - FileOutputStream outputStream = new FileOutputStream(new File(moduleDir.getAbsoluteFile(), "version.properties")) - - byte[] buffer = new byte[1024] - int len = 0 - while ((len = is.read(buffer)) > 0) { - outputStream.write(buffer, 0, len) - } - outputStream.close() - - print('\nversion.properties found!') - } + def suffix=buildType.toLowerCase() + String apkPath=projectDir.getAbsolutePath()+"/build/outputs/apk/$suffix/app-$suffix" + def apk=new File(apkPath+"-unsigned.apk") + if(!apk.exists())apk=new File(apkPath+".apk") + def zip=new ZipFile(apk) + def entries=zip.entries() + def dexCounter=dexes + while(entries.hasMoreElements()) + { + def entry=entries.nextElement() + String filename + if(entry.name.matches("^classes(?:(?=\\d)2|)\\.dex\$"))filename="classes${++dexCounter}.dex" + else if(entry.name=="assets/version.properties")filename="version.properties" + else continue + println("Exporting "+entry.name) + def tmpFile=new File(projectDir,filename) + def is=zip.getInputStream(entry) + def fos=new FileOutputStream(tmpFile) + def buff=new byte[0x800] + int len + while((len=is.read(buff))>0)fos.write(buff,0,len) + is.close() + fos.close() } } catch (Exception e) { e.printStackTrace() @@ -150,17 +142,16 @@ task generateBuildTasks() { task "prepareDexForMerge$buildType"(type: Copy) { dependsOn('exportDex' + buildType) // Remove comment for native lib build - dependsOn('cargoBuild') + //dependsOn('cargoBuild') into smaliFolder.toString() from(projectDir) { - include 'classes6.dex' - include 'classes7.dex' + into '../smali' + include "classes*.dex" } from(projectDir) { include 'version.properties' into 'assets' } - // Remove comment for native lib build from('../native-effects/target/aarch64-linux-android/release'){ include 'libnative_effects.so' into 'lib/arm64-v8a' @@ -169,15 +160,16 @@ task generateBuildTasks() { include 'libnative_effects.so' into 'lib/armeabi-v7a' } - doLast { - new File(projectDir, "classes6.dex").delete() - new File(projectDir, "classes7.dex").delete() - new File(projectDir, "version.properties").delete() - } + } + task "cleanTemporaryFiles$buildType"(type:Delete) + { + delete fileTree(dir:projectDir,include:["*.dex","version.properties"]) + delete fileTree(dir:smaliFolder,include:['*.dex',"assets/version.properties"]) } task "buildVTL$buildType"(type: Exec) { dependsOn('prepareDexForMerge' + buildType) workingDir(projectDir.parentFile) + finalizedBy "cleanTemporaryFiles$buildType" if (Os.isFamily("unix") || Os.isFamily("mac")) { commandLine 'bash', '-c', './scripts/build.sh' } else { @@ -205,7 +197,6 @@ task generateBuildTasks() { } } - task generateRjava(type: Exec) { workingDir(projectDir.parentFile) if (Os.isFamily("unix") || Os.isFamily("mac")) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e4650c9d65..7e882ce4fb 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,29 @@ ## If you keep the line number information, uncomment this to ## hide the original source file name. ##-renamesourcefileattribute SourceFile -#-keepclassmembers class fq \ No newline at end of file +#-keepclassmembers class fq + +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int i(...); + public static int w(...); + public static int d(...); + public static int e(...); +} + +-repackageclasses +-allowaccessmodification +-optimizations +-optimizationpasses 5 +-keepattributes SourceFile, LineNumberTable + +-keep,allowoptimization class ru.vtosters.hooks.**, ru.vtosters.lite.utils.AndroidUtils, ru.vtosters.lite.utils.NetworkUtils, ru.vtosters.lite.utils.CrashReporter, ru.vtosters.lite.ui.activities.**, ru.vtosters.lite.services.**, ru.vtosters.lite.ui.fragments.**, ru.vtosters.lite.themes.**, ru.vtosters.lite.music.hook.**, ru.vtosters.lite.music.cache.injectors.**, ru.vtosters.lite.ui.wallpapers.NativeEffects, com.aefyr.tsg.g2.TelegramStickersService { + public ; + public ; + public (...); +} + +-keep class com.vtosters.lite.** { *; } +-keep class com.vk.** { *; } +-keep class bruhcollective.itaysonlab.libvkx.client.** { *; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cf6f003d52..b60c735692 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,15 +1,15 @@ - - - - + + + + + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true"> \ No newline at end of file diff --git a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java index 9f5dbf63ba..2232550d11 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java +++ b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClient.java @@ -1,20 +1,18 @@ package bruhcollective.itaysonlab.libvkx.client; -import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; -import static ru.vtosters.lite.utils.Preferences.isValidSignature; - import android.annotation.SuppressLint; import android.content.Context; import android.os.RemoteException; - +import bruhcollective.itaysonlab.libvkx.ILibVkxService; import com.vk.dto.music.MusicTrack; import com.vk.music.common.MusicPlaybackLaunchContext; import java.util.ArrayList; import java.util.List; -import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.isValidSignature; public class LibVKXClient { @SuppressLint("StaticFieldLeak") @@ -78,7 +76,7 @@ public static void lambdaplay(List list, MusicTrack musicTrack, ILib } } - public static void lambdaplay(List list, MusicTrack musicTrack, ILibVkxService iLibVkxService){ + public static void lambdaplay(List list, MusicTrack musicTrack, ILibVkxService iLibVkxService) { lambdaplay(list, musicTrack, iLibVkxService, null); } diff --git a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java index 11b8628560..79f15ff4d8 100644 --- a/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java +++ b/app/src/main/java/bruhcollective/itaysonlab/libvkx/client/LibVKXClientImpl.java @@ -1,7 +1,5 @@ package bruhcollective.itaysonlab.libvkx.client; -import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -10,10 +8,11 @@ import android.content.pm.ResolveInfo; import android.os.IBinder; import android.os.RemoteException; +import bruhcollective.itaysonlab.libvkx.ILibVkxService; import java.util.List; -import bruhcollective.itaysonlab.libvkx.ILibVkxService; +import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; public class LibVKXClientImpl { private ILibVkxService serviceInstance; diff --git a/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java b/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java index 2d0bbb359b..75d0ab0284 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java +++ b/app/src/main/java/com/aefyr/tsg/g2/NotificationsHelper.java @@ -5,24 +5,21 @@ import android.app.NotificationManager; import android.content.Context; import android.os.Build; - import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; - import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber; import com.vtosters.lite.R; +import ru.vtosters.lite.utils.AndroidUtils; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import ru.vtosters.lite.utils.AndroidUtils; - /** * Created by Aefyr on 20.05.2018. */ public class NotificationsHelper { -// private static final int NOTIFICATION_ID_BASE = 3921337; + // private static final int NOTIFICATION_ID_BASE = 3921337; private static final String NOTIFICATION_CHANNEL_ID = "tgss_chan_v5"; private static final int MAX_UPDATE_NOTIFICATIONS_PER_SECOND = 3; private static final int MIN_TIME_BETWEEN_UPDATE_NOTIFICATIONS = 1000 / MAX_UPDATE_NOTIFICATIONS_PER_SECOND; diff --git a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java index 4b35f7f6d6..83b1cf2d12 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java +++ b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersPack.java @@ -3,7 +3,6 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -37,10 +36,8 @@ public class TelegramStickersPack extends CustomStickersPack { public HashMap> emojis = new HashMap<>(); public TelegramStickersPack(String id) { - final var idLowerCase = id.toLowerCase(); - this.id = idLowerCase; - this.hash = idLowerCase.hashCode(); - title = id; + this.id=this.title=id; + this.hash=id.hashCode(); } public JSONObject encodeEmojis() throws JSONException { @@ -104,7 +101,7 @@ private Bitmap getScaledBitmap(File bitmapFile, int targetMaxSideSize) { @Override public boolean equals(Object obj) { - return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equalsIgnoreCase(id); + return obj instanceof TelegramStickersPack && ((TelegramStickersPack) obj).id.equals(id); } @Override diff --git a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersService.java b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersService.java index ded64f57c3..ba7c6281c1 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersService.java +++ b/app/src/main/java/com/aefyr/tsg/g2/TelegramStickersService.java @@ -8,6 +8,7 @@ import com.aefyr.tsg.g2.sql.TelegramStickersDbHelper; import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersGrabber; import com.aefyr.tsg.g2.stickersgrabber.TelegramStickersPackInfo; +import ru.vtosters.lite.ui.fragments.VTSettings; import ru.vtosters.lite.ui.fragments.tgstickers.StickersFragment; import java.io.File; @@ -152,6 +153,7 @@ private void notifyPackDownloadError(TelegramStickersPack pack, Exception error) private void notifyActivePacksListChanged() { getGlobalContext().sendBroadcast(new Intent(StickersFragment.ACTION_RELOAD)); + getGlobalContext().sendBroadcast(new Intent(VTSettings.ACTION_INVALIDATE_TGS_COUNT)); if (listeners.isEmpty()) return; @@ -222,7 +224,7 @@ public boolean requestPackDownload(final String id, File packFolder) { final TelegramStickersPack newPack = pack; final boolean isUpdate = update; - currentlyDownloading.add(id.toLowerCase()); + currentlyDownloading.add(id); notificationsHelper.packStartedDownloading(newPack); diff --git a/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java b/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java index 8decf3eac5..2972d4f6f4 100755 --- a/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java +++ b/app/src/main/java/com/aefyr/tsg/g2/sql/TelegramStickersDbHelper.java @@ -8,9 +8,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.os.AsyncTask; import android.util.Log; - import com.aefyr.tsg.g2.TelegramStickersPack; - import org.json.JSONException; import org.json.JSONObject; diff --git a/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java b/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java index dd1cc967b8..6289d37aa7 100644 --- a/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java +++ b/app/src/main/java/com/aefyr/tsg/g2/stickersgrabber/TelegramStickersGrabber.java @@ -5,12 +5,11 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; - import com.aefyr.tsg.g2.stickersgrabber.util.Flag; import com.aefyr.tsg.g2.stickersgrabber.util.GoalCounter; - import org.json.JSONArray; import org.json.JSONObject; +import ru.vtosters.lite.net.*; import java.io.File; import java.io.FileOutputStream; @@ -27,12 +26,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import ru.vtosters.lite.net.NetCall; -import ru.vtosters.lite.net.NetCallback; -import ru.vtosters.lite.net.NetClient; -import ru.vtosters.lite.net.NetRequest; -import ru.vtosters.lite.net.NetResponse; - /** * Created by Aefyr on 11.05.2018. */ @@ -67,7 +60,7 @@ public TelegramStickersGrabber(String botApiKey) { try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { - Log.wtf(TAG, "No support for SHA-256"); + Log.d(TAG, "No support for SHA-256"); e.printStackTrace(); throw new RuntimeException(e); } diff --git a/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java b/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java index 02d2b62aed..cdd3afa484 100755 --- a/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java +++ b/app/src/main/java/com/arthenica/ffmpegkit/NativeLoader.java @@ -2,7 +2,6 @@ import android.os.Build; import android.util.Log; - import com.arthenica.smartexception.java.Exceptions; import java.util.Collections; diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java index d6423103e5..df22f8e98e 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ArscBlamer.java @@ -35,243 +35,293 @@ */ public class ArscBlamer { - /** Maps package key pool indices to blamed resources. */ - private final Map[]> keyToBlame = new HashMap<>(); - - /** Maps types to blamed resources. */ - private final Map[]> typeToBlame = new HashMap<>(); - - /** Maps package to blamed resources. */ - private final Multimap packageToBlame = HashMultimap.create(); - - /** Maps string indices to blamed resources. */ - private final List[] stringToBlame; - - /** Maps type chunk entries to blamed resources. */ - private final Multimap typeEntryToBlame = HashMultimap.create(); - - /** Maps resources to the type chunk entries they reference. */ - private Multimap resourceEntries; - - /** Maps resources which have no base config to the type chunk entries they reference. */ - private Multimap baselessKeys; - - /** Contains all of the type chunks in {@link #resourceTable}. */ - private List 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 entries = getResourceEntries(); - for (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); + /** + * Maps package key pool indices to blamed resources. + */ + private final Map[]> keyToBlame = new HashMap<>(); + + /** + * Maps types to blamed resources. + */ + private final Map[]> typeToBlame = new HashMap<>(); + + /** + * Maps package to blamed resources. + */ + private final Multimap packageToBlame = HashMultimap.create(); + + /** + * Maps string indices to blamed resources. + */ + private final List[] stringToBlame; + + /** + * Maps type chunk entries to blamed resources. + */ + private final Multimap typeEntryToBlame = HashMultimap.create(); + + /** + * Maps resources to the type chunk entries they reference. + */ + private Multimap resourceEntries; + + /** + * Maps resources which have no base config to the type chunk entries they reference. + */ + private Multimap baselessKeys; + + /** + * Contains all of the type chunks in {@link #resourceTable}. + */ + private List 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()); } - Multimaps.invertFrom(entries, typeEntryToBlame); - for (TypeChunk.Entry entry : typeEntryToBlame.keySet()) { - blameFromTypeChunkEntry(entry); - } - } - private void blameKeyOrType(Map[]> keyOrType, - PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) { - if (!keyOrType.containsKey(packageChunk)) { - keyOrType.put(packageChunk, createEntryListArray(entryCount)); + /** + * Generates blame mappings. + */ + public void blame() { + Multimap entries = getResourceEntries(); + for (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); + } } - keyOrType.get(packageChunk)[keyIndex].add(entry); - } - - private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) { - packageToBlame.put(packageChunk, entry); - } - - private void blameFromTypeChunkEntry(TypeChunk.Entry chunkEntry) { - for (BinaryResourceValue value : getAllResourceValues(chunkEntry)) { - for (ResourceEntry entry : typeEntryToBlame.get(chunkEntry)) { - switch (value.type()) { - case STRING: - blameString(value.data(), entry); - break; - default: - break; + + private void blameKeyOrType(Map[]> keyOrType, + PackageChunk packageChunk, int keyIndex, ResourceEntry entry, int entryCount) { + if (!keyOrType.containsKey(packageChunk)) { + keyOrType.put(packageChunk, createEntryListArray(entryCount)); } - } + keyOrType.get(packageChunk)[keyIndex].add(entry); } - } - - /** Returns all {@link BinaryResourceValue} for a single {@code entry}. */ - private Collection getAllResourceValues(TypeChunk.Entry entry) { - Set values = new HashSet(); - BinaryResourceValue binaryResourceValue = entry.value(); - if (binaryResourceValue != null) { - values.add(binaryResourceValue); + + private void blamePackage(PackageChunk packageChunk, ResourceEntry entry) { + packageToBlame.put(packageChunk, entry); } - for (BinaryResourceValue value : entry.values().values()) { - values.add(value); + + 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; + } + } + } } - return values; - } - - private void blameString(int stringIndex, ResourceEntry entry) { - stringToBlame[stringIndex].add(entry); - } - - /** Must first call {@link #blame}. */ - public Map[]> getKeyToBlamedResources() { - return Collections.unmodifiableMap(keyToBlame); - } - - /** Must first call {@link #blame}. */ - public Map[]> getTypeToBlamedResources() { - return Collections.unmodifiableMap(typeToBlame); - } - - /** Must first call {@link #blame}. */ - public Multimap getPackageToBlamedResources() { - return Multimaps.unmodifiableMultimap(packageToBlame); - } - - /** Must first call {@link #blame}. */ - public List[] getStringToBlamedResources() { - return stringToBlame; - } - - /** Must first call {@link #blame}. */ - public Multimap getTypeEntryToBlamedResources() { - return Multimaps.unmodifiableMultimap(typeEntryToBlame); - } - - /** Returns a multimap of keys for which there is no default resource. */ - public Multimap getBaselessKeys() { - if (baselessKeys != null) { - return baselessKeys; + + /** + * Returns all {@link BinaryResourceValue} for a single {@code entry}. + */ + private Collection getAllResourceValues(TypeChunk.Entry entry) { + Set values = new HashSet(); + BinaryResourceValue binaryResourceValue = entry.value(); + if (binaryResourceValue != null) { + values.add(binaryResourceValue); + } + for (BinaryResourceValue value : entry.values().values()) { + values.add(value); + } + return values; } - Multimap result = HashMultimap.create(); - for (Entry> entry - : getResourceEntries().asMap().entrySet()) { - Collection chunkEntries = entry.getValue(); - if (!hasBaseConfiguration(chunkEntries)) { - result.putAll(entry.getKey(), chunkEntries); - } + + private void blameString(int stringIndex, ResourceEntry entry) { + stringToBlame[stringIndex].add(entry); } - baselessKeys = result; - return result; - } - - /** Returns a multimap of resource entries to the chunk entries they reference in this APK. */ - public Multimap getResourceEntries() { - if (resourceEntries != null) { - return resourceEntries; + + /** + * Must first call {@link #blame}. + */ + public Map[]> getKeyToBlamedResources() { + return Collections.unmodifiableMap(keyToBlame); } - Multimap result = HashMultimap.create(); - for (TypeChunk typeChunk : getTypeChunks()) { - for (TypeChunk.Entry entry : typeChunk.getEntries().values()) { - result.put(ResourceEntry.create(entry), entry); - } + + /** + * Must first call {@link #blame}. + */ + public Map[]> getTypeToBlamedResources() { + return Collections.unmodifiableMap(typeToBlame); } - resourceEntries = result; - return result; - } - - /** Returns all {@link TypeChunk} in resources.arsc. */ - public List getTypeChunks() { - if (typeChunks != null) { - return typeChunks; + + /** + * Must first call {@link #blame}. + */ + public Multimap getPackageToBlamedResources() { + return Multimaps.unmodifiableMultimap(packageToBlame); } - List result = new ArrayList<>(); - for (PackageChunk packageChunk : resourceTable.getPackages()) { - for (TypeChunk typeChunk : packageChunk.getTypeChunks()) { - result.add(typeChunk); - } + + /** + * Must first call {@link #blame}. + */ + public List[] getStringToBlamedResources() { + return stringToBlame; } - typeChunks = result; - return result; - } - - private boolean hasBaseConfiguration(Collection entries) { - for (TypeChunk.Entry entry : entries) { - if (entry.parent().getConfiguration().isDefault()) { - return true; - } + + /** + * Must first call {@link #blame}. + */ + public Multimap getTypeEntryToBlamedResources() { + return Multimaps.unmodifiableMultimap(typeEntryToBlame); } - return false; - } - private static List[] createEntryListArray(int size) { - ArrayListResourceEntry[] result = new ArrayListResourceEntry[size]; - for (int i = 0; i < size; ++i) { - result[i] = new ArrayListResourceEntry(); + /** + * Returns a multimap of keys for which there is no default resource. + */ + public Multimap getBaselessKeys() { + if (baselessKeys != null) { + return baselessKeys; + } + Multimap result = HashMultimap.create(); + for (Entry> entry + : getResourceEntries().asMap().entrySet()) { + Collection chunkEntries = entry.getValue(); + if (!hasBaseConfiguration(chunkEntries)) { + result.putAll(entry.getKey(), chunkEntries); + } + } + baselessKeys = result; + return result; } - return result; - } - /** Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}. */ - private static class ArrayListResourceEntry extends ArrayList { + /** + * Returns a multimap of resource entries to the chunk entries they reference in this APK. + */ + public Multimap getResourceEntries() { + if (resourceEntries != null) { + return resourceEntries; + } + Multimap result = HashMultimap.create(); + for (TypeChunk typeChunk : getTypeChunks()) { + for (TypeChunk.Entry entry : typeChunk.getEntries().values()) { + result.put(ResourceEntry.create(entry), entry); + } + } + resourceEntries = result; + return result; + } - private ArrayListResourceEntry() { - super(2); // ~90-95% of these lists end up with only 1 or 2 elements. + /** + * Returns all {@link TypeChunk} in resources.arsc. + */ + public List getTypeChunks() { + if (typeChunks != null) { + return typeChunks; + } + List result = new ArrayList<>(); + for (PackageChunk packageChunk : resourceTable.getPackages()) { + for (TypeChunk typeChunk : packageChunk.getTypeChunks()) { + result.add(typeChunk); + } + } + typeChunks = result; + return result; } - } - - /** 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 boolean hasBaseConfiguration(Collection entries) { + for (TypeChunk.Entry entry : entries) { + if (entry.parent().getConfiguration().isDefault()) { + return true; + } + } + return false; } - private ResourceEntry(String packageName, String typeName, String entryName) { - this.packageName = packageName; - this.typeName = typeName; - this.entryName = entryName; + private static List[] createEntryListArray(int size) { + ArrayListResourceEntry[] result = new ArrayListResourceEntry[size]; + for (int i = 0; i < size; ++i) { + result[i] = new ArrayListResourceEntry(); + } + return result; } - 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); + /** + * Allows creation of concrete parameterized type arr {@link ArscBlamer#createEntryListArray}. + */ + private static class ArrayListResourceEntry extends ArrayList { + + private ArrayListResourceEntry() { + super(2); // ~90-95% of these lists end up with only 1 or 2 elements. + } } - @Override - public int hashCode() { - return Objects.hash(packageName, typeName, entryName); + /** + * 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); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java index 96f98784f1..2da6a2d4f1 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java @@ -27,48 +27,54 @@ import java.util.Collections; import java.util.List; -/** Given an arsc file, maps the contents of the file. */ +/** + * Given an arsc file, maps the contents of the file. + */ public final class BinaryResourceFile implements SerializableResource { - /** The chunks contained in this resource file. */ - private final List chunks = new ArrayList<>(); + /** + * The chunks contained in this resource file. + */ + private final List chunks = new ArrayList<>(); - public BinaryResourceFile(byte[] buf) { - ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); - while (buffer.remaining() > 0) { - chunks.add(Chunk.newInstance(buffer)); + public BinaryResourceFile(byte[] buf) { + ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + while (buffer.remaining() > 0) { + chunks.add(Chunk.newInstance(buffer)); + } } - } - /** - * Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile} - * representing the contents of the stream. - * - * @param is The input stream to read from. - * @return BinaryResourceFile represented by the @{link InputStream}. - * @throws IOException - */ - public static BinaryResourceFile fromInputStream(InputStream is) throws IOException { - byte[] buf = ByteStreams.toByteArray(is); - return new BinaryResourceFile(buf); - } + /** + * Given an input stream, reads the stream until the end and returns a {@link BinaryResourceFile} + * representing the contents of the stream. + * + * @param is The input stream to read from. + * @return BinaryResourceFile represented by the @{link InputStream}. + * @throws IOException + */ + public static BinaryResourceFile fromInputStream(InputStream is) throws IOException { + byte[] buf = ByteStreams.toByteArray(is); + return new BinaryResourceFile(buf); + } - /** Returns the chunks in this resource file. */ - public List getChunks() { - return Collections.unmodifiableList(chunks); - } + /** + * Returns the chunks in this resource file. + */ + public List getChunks() { + return Collections.unmodifiableList(chunks); + } - @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); - } + @Override + public byte[] toByteArray() throws IOException { + return toByteArray(false); + } - @Override - public byte[] toByteArray(boolean shrink) throws IOException { - ByteArrayDataOutput output = ByteStreams.newDataOutput(); - for (Chunk chunk : chunks) { - output.write(chunk.toByteArray(shrink)); + @Override + public byte[] toByteArray(boolean shrink) throws IOException { + ByteArrayDataOutput output = ByteStreams.newDataOutput(); + for (Chunk chunk : chunks) { + output.write(chunk.toByteArray(shrink)); + } + return output.toByteArray(); } - return output.toByteArray(); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java index a3b80ab77a..32a61cf605 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java @@ -27,73 +27,97 @@ */ 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); - } + /** + * 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); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java index 4da4e2f51f..3fe721a60d 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceString.java @@ -26,139 +26,144 @@ import static java.nio.charset.StandardCharsets.UTF_16LE; import static java.nio.charset.StandardCharsets.UTF_8; -/** Provides utilities to decode/encode a String packed in an arsc resource file. */ +/** + * Provides utilities to decode/encode a String packed in an arsc resource file. + */ public final class BinaryResourceString { - /** Type of {@link BinaryResourceString} to encode / decode. */ - public enum Type { - UTF8(UTF_8), - UTF16(UTF_16LE); + /** + * Type of {@link BinaryResourceString} to encode / decode. + */ + public enum Type { + UTF8(UTF_8), + UTF16(UTF_16LE); - private final Charset charset; + private final Charset charset; - Type(Charset charset) { - this.charset = charset; - } + Type(Charset charset) { + this.charset = charset; + } - public Charset charset() { - return 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. - * - *

Here's an example UTF-8-encoded string of ab©: - *

-   * 03 04 61 62 C2 A9 00
-   * ^ Offset should be here
-   * 
- * - * @param buffer The buffer containing the string to decode. - * @param offset Offset into the buffer where the string resides. - * @param type The encoding type that the {@link BinaryResourceString} is encoded in. - * @return The decoded string. - */ - public static String decodeString(ByteBuffer buffer, int offset, Type type) { - int length; - int characterCount = decodeLength(buffer, offset, type); - offset += computeLengthOffset(characterCount, type); - // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length. - // UTF-16 strings, however, only have 1 length: the number of characters. - if (type == Type.UTF8) { - length = decodeLength(buffer, offset, type); - offset += computeLengthOffset(length, type); - } else { - length = characterCount * 2; + + private 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. + * + *

Here's an example UTF-8-encoded string of ab©: + *

+     * 03 04 61 62 C2 A9 00
+     * ^ Offset should be here
+     * 
+ * + * @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()); } - 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). - * - *

Here's an example UTF-8-encoded string of ab©: - *

03 04 61 62 C2 A9 00
- * - * @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); + + /** + * 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). + * + *

Here's an example UTF-8-encoded string of ab©: + *

03 04 61 62 C2 A9 00
+ * + * @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(); } - output.write(bytes); - // NULL-terminate the string - if (type == Type.UTF8) { - output.write(0); - } else { - output.writeShort(0); + + private static void encodeLength(ByteArrayDataOutput output, int length, Type type) { + if (length < 0) { + output.write(0); + return; + } + if (type == Type.UTF8) { + if (length > 0x7F) { + output.write(((length & 0x7F00) >> 8) | 0x80); + } + output.write(length & 0xFF); + } else { // UTF-16 + // TODO(acornwall): Replace output with a little-endian output. + if (length > 0x7FFF) { + int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000; + output.write(highBytes & 0xFF); + output.write((highBytes & 0xFF00) >> 8); + } + int lowBytes = length & 0xFFFF; + output.write(lowBytes & 0xFF); + output.write((lowBytes & 0xFF00) >> 8); + } } - return output.toByteArray(); - } - private static void encodeLength(ByteArrayDataOutput output, int length, Type type) { - if (length < 0) { - output.write(0); - return; + private static int computeLengthOffset(int length, Type type) { + return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1); } - 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 decodeLength(ByteBuffer buffer, int offset, Type type) { + return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset); } - } - - private static int computeLengthOffset(int length, Type type) { - return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1); - } - - private static int decodeLength(ByteBuffer buffer, int offset, Type type) { - return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset); - } - - private static int decodeLengthUTF8(ByteBuffer buffer, int offset) { - // UTF-8 strings use a clever variant of the 7-bit integer for packing the string length. - // If the first byte is >= 0x80, then a second byte follows. For these values, the length - // is WORD-length in big-endian & 0x7FFF. - int length = UnsignedBytes.toInt(buffer.get(offset)); - if ((length & 0x80) != 0) { - length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1)); + + private static int 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; } - 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); + + private static int decodeLengthUTF16(ByteBuffer buffer, int offset) { + // UTF-16 strings use a clever variant of the 7-bit integer for packing the string length. + // If the first word is >= 0x8000, then a second word follows. For these values, the length + // is DWORD-length in big-endian & 0x7FFFFFFF. + int length = (buffer.getShort(offset) & 0xFFFF); + if ((length & 0x8000) != 0) { + length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF); + } + return length; } - return length; - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java index b66a531571..50e8d9e447 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java @@ -26,126 +26,176 @@ import java.util.Map; import java.util.Objects; -/** Represents a single typed resource value. */ +/** + * Represents a single typed resource value. + */ public class BinaryResourceValue implements SerializableResource { - /** Resource type codes. */ - public enum Type { - /** {@code data} is either 0 (undefined) or 1 (empty). */ - NULL(0x00), - /** {@code data} holds a {@link ResourceTableChunk} entry reference. */ - REFERENCE(0x01), - /** {@code data} holds an attribute resource identifier. */ - ATTRIBUTE(0x02), - /** {@code data} holds an index into the containing resource table's string pool. */ - STRING(0x03), - /** {@code data} holds a single-precision floating point number. */ - FLOAT(0x04), - /** {@code data} holds a complex number encoding a dimension value, such as "100in". */ - DIMENSION(0x05), - /** {@code data} holds a complex number encoding a fraction of a container. */ - FRACTION(0x06), - /** {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. */ - DYNAMIC_REFERENCE(0x07), - /** {@code data} holds a dynamic attribute resource identifier. */ - DYNAMIC_ATTRIBUTE(0x08), - /** {@code data} is a raw integer value of the form n..n. */ - INT_DEC(0x10), - /** {@code data} is a raw integer value of the form 0xn..n. */ - INT_HEX(0x11), - /** {@code data} is either 0 (false) or 1 (true). */ - INT_BOOLEAN(0x12), - /** {@code data} is a raw integer value of the form #aarrggbb. */ - INT_COLOR_ARGB8(0x1c), - /** {@code data} is a raw integer value of the form #rrggbb. */ - INT_COLOR_RGB8(0x1d), - /** {@code data} is a raw integer value of the form #argb. */ - INT_COLOR_ARGB4(0x1e), - /** {@code data} is a raw integer value of the form #rgb. */ - INT_COLOR_RGB4(0x1f); - - private final byte code; - - private static final Map FROM_BYTE; - - static { - Builder builder = ImmutableMap.builder(); - for (Type type : values()) { - builder.put(type.code(), type); - } - FROM_BYTE = builder.build(); + /** + * 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 FROM_BYTE; + + static { + Builder 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; + + public int size; + public Type type; + public 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); } - Type(int code) { - this.code = UnsignedBytes.checkedCast(code); + @Override + public byte[] toByteArray(boolean shrink) { + ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort((short) size()); + buffer.put((byte) 0); // Unused + buffer.put(type().code()); + buffer.putInt(data()); + return buffer.array(); } - public byte code() { - return code; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BinaryResourceValue that = (BinaryResourceValue) o; + return size == that.size && + data == that.data && + type == that.type; } - public static Type fromCode(byte code) { - return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code); + @Override + public int hashCode() { + return Objects.hash(size, type, data); } - } - - /** 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); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java index 0d26614865..665b8cd06b 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/Chunk.java @@ -30,272 +30,296 @@ import java.nio.ByteOrder; import java.util.Map; -/** Represents a generic chunk. */ +/** + * Represents a generic chunk. + */ public abstract class Chunk implements SerializableResource { - /** Types of chunks that can exist. */ - public enum Type { - NULL(0x0000), - STRING_POOL(0x0001), - TABLE(0x0002), - XML(0x0003), - XML_START_NAMESPACE(0x0100), - XML_END_NAMESPACE(0x0101), - XML_START_ELEMENT(0x0102), - XML_END_ELEMENT(0x0103), - XML_CDATA(0x0104), - XML_RESOURCE_MAP(0x0180), - TABLE_PACKAGE(0x0200), - TABLE_TYPE(0x0201), - TABLE_TYPE_SPEC(0x0202), - TABLE_LIBRARY(0x0203); - - private final short code; - - private static final Map FROM_SHORT; - - static { - Builder builder = ImmutableMap.builder(); - for (Type type : values()) { - builder.put(type.code(), type); - } - FROM_SHORT = builder.build(); + /** + * 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 FROM_SHORT; + + static { + Builder 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; } - Type(int code) { - this.code = Shorts.checkedCast(code); + /** + * 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). + * + *

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; } - public short code() { - return code; + /** + * 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); } - public static Type fromCode(short code) { - return Preconditions.checkNotNull(FROM_SHORT.get(code), "Unknown chunk type: %s", code); + /** + * 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()); } - } - - /** 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). - * - *

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; + + /** + * 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; } - 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); + + @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); } - 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); + /** + * 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; } - result.init(buffer); - result.seekToEndOfChunk(buffer); - return result; - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java index 11ba3a41ff..8a311a2b76 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java @@ -24,50 +24,52 @@ import java.util.LinkedHashMap; import java.util.Map; -/** Represents a chunk whose payload is a list of sub-chunks. */ +/** + * Represents a chunk whose payload is a list of sub-chunks. + */ public abstract class ChunkWithChunks extends Chunk { - private final Map chunks = new LinkedHashMap<>(); + private final Map chunks = new LinkedHashMap<>(); - protected ChunkWithChunks(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + 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); + @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(); - } + while (offset < end) { + Chunk chunk = Chunk.newInstance(buffer, this); + chunks.put(offset, chunk); + offset += chunk.getOriginalChunkSize(); + } - buffer.position(position); - } + buffer.position(position); + } - /** - * Retrieves the @{code chunks} contained in this chunk. - * - * @return map of buffer offset -> chunk contained in this chunk. - */ - public final Map getChunks() { - return chunks; - } + /** + * Retrieves the @{code chunks} contained in this chunk. + * + * @return map of buffer offset -> chunk contained in this chunk. + */ + public final Map 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); + @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); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java index 38236de8b0..4a31546f4c 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/LibraryChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -32,105 +33,121 @@ */ 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 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)); - } + /** + * The number of resources of this type at creation time. + */ + private final int entryCount; - private List enumerateEntries(ByteBuffer buffer) { - List result = new ArrayList<>(entryCount); - int offset = this.offset + getHeaderSize(); - int endOffset = offset + Entry.SIZE * entryCount; + /** + * The libraries used in this chunk (package id + name). + */ + private final List entries = new ArrayList<>(); - while (offset < endOffset) { - result.add(Entry.create(buffer, offset)); - offset += Entry.SIZE; + protected LibraryChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + entryCount = buffer.getInt(); } - 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; + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + entries.addAll(enumerateEntries(buffer)); } - /** The id assigned to the shared library at build time. */ - public int packageId() { return packageId; } + private List enumerateEntries(ByteBuffer buffer) { + List result = new ArrayList<>(entryCount); + int offset = this.offset + getHeaderSize(); + int endOffset = offset + Entry.SIZE * entryCount; - /** The package name of the shared library. */ - public String packageName() { return packageName; } + while (offset < endOffset) { + result.add(Entry.create(buffer, offset)); + offset += Entry.SIZE; + } + return result; + } @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); + protected Type getType() { + return Type.TABLE_LIBRARY; } @Override - public byte[] toByteArray(boolean shrink) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(packageId()); - PackageUtils.writePackageName(buffer, packageName()); - return buffer.array(); + protected void writeHeader(ByteBuffer output) { + super.writeHeader(output); + output.putInt(entries.size()); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Entry entry = (Entry)o; - return packageId == entry.packageId && - Objects.equals(packageName, entry.packageName); + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + for (Entry entry : entries) { + output.write(entry.toByteArray(shrink)); + } } - @Override - public int hashCode() { - return Objects.hash(packageId, packageName); + /** + * 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); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java index cc8c2e32fc..9ede602663 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -29,211 +29,251 @@ import java.util.Map; import java.util.Optional; -/** A package chunk is a collection of resource data types within a package. */ +/** + * A package chunk is a collection of resource data types within a package. + */ public final class PackageChunk extends ChunkWithChunks { - /** Offset in bytes, from the start of the chunk, where {@code typeStringsOffset} can be found. */ - private static final int TYPE_OFFSET_OFFSET = 268; - - /** Offset in bytes, from the start of the chunk, where {@code 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 typeSpecs = new HashMap<>(); - - /** Contains a mapping of a type index to all of the {@link TypeChunk} with that index. */ - private final Multimap types = ArrayListMultimap.create(); - - /** May contain a library chunk for mapping dynamic references to resolved references. */ - private Optional 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 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 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 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 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); - } + /** + * 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 typeSpecs = new HashMap<>(); + + /** + * Contains a mapping of a type index to all of the {@link TypeChunk} with that index. + */ + private final Multimap types = ArrayListMultimap.create(); + + /** + * May contain a library chunk for mapping dynamic references to resolved references. + */ + private Optional 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 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 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 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 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); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java index 5077ca6f04..6c5215762f 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/PackageUtils.java @@ -20,48 +20,53 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -/** Provides utility methods for package names. */ +/** + * Provides utility methods for package names. + */ public final class PackageUtils { - public static final int PACKAGE_NAME_SIZE = 256; + public static final int PACKAGE_NAME_SIZE = 256; - private PackageUtils() {} // Prevent instantiation + private PackageUtils() { + } // Prevent instantiation - /** - * Reads the package name from the buffer and repositions the buffer to point directly after - * the package name. - * @param buffer The buffer containing the package name. - * @param offset The offset in the buffer to read from. - * @return The package name. - */ - public static String readPackageName(ByteBuffer buffer, int offset) { - byte[] data = buffer.array(); - int length = 0; - // Look for the null terminator for the string instead of using the entire buffer. - // It's UTF-16 so check 2 bytes at a time to see if its double 0. - for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) { - if (data[i] == 0 && data[i + 1] == 0) { - length = i - offset; - break; - } + /** + * Reads the package name from the buffer and repositions the buffer to point directly after + * the package name. + * + * @param buffer The buffer containing the package name. + * @param offset The offset in the buffer to read from. + * @return The package name. + */ + public static String readPackageName(ByteBuffer buffer, int offset) { + byte[] data = buffer.array(); + int length = 0; + // Look for the null terminator for the string instead of using the entire buffer. + // It's UTF-16 so check 2 bytes at a time to see if its double 0. + for (int i = offset; i < data.length && i < PACKAGE_NAME_SIZE + offset; i += 2) { + if (data[i] == 0 && data[i + 1] == 0) { + length = i - offset; + break; + } + } + Charset utf16 = StandardCharsets.UTF_16LE; + String str = new String(data, offset, length, utf16); + buffer.position(offset + PACKAGE_NAME_SIZE); + return str; } - Charset utf16 = StandardCharsets.UTF_16LE; - String str = new String(data, offset, length, utf16); - buffer.position(offset + PACKAGE_NAME_SIZE); - return str; - } - /** - * Writes the provided package name to the buffer in UTF-16. - * @param buffer The buffer that will be written to. - * @param packageName The package name that will be written to the buffer. - */ - public static void writePackageName(ByteBuffer buffer, String packageName) { - byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE); - buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE)); - if (nameBytes.length < PACKAGE_NAME_SIZE) { - // pad out the remaining space with an empty array. - buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]); + /** + * Writes the provided package name to the buffer in UTF-16. + * + * @param buffer The buffer that will be written to. + * @param packageName The package name that will be written to the buffer. + */ + public static void writePackageName(ByteBuffer buffer, String packageName) { + byte[] nameBytes = packageName.getBytes(StandardCharsets.UTF_16LE); + buffer.put(nameBytes, 0, Math.min(nameBytes.length, PACKAGE_NAME_SIZE)); + if (nameBytes.length < PACKAGE_NAME_SIZE) { + // pad out the remaining space with an empty array. + buffer.put(new byte[PACKAGE_NAME_SIZE - nameBytes.length]); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java index 5891f05675..e7d3895ef6 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceEntryStatsCollector.java @@ -26,255 +26,276 @@ /** * 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 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 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[]> entry - : blamer.getTypeToBlamedResources().entrySet()) { - computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue()); + /** + * 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 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; } - } - private void computeKeyPoolSizes() throws IOException { - for (Entry[]> entry - : blamer.getKeyToBlamedResources().entrySet()) { - computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue()); + public void compute() throws IOException { + Preconditions.checkState(stats.isEmpty(), "Must only call #compute once."); + blamer.blame(); + computeStringPoolSizes(); + computePackageSizes(); } - } - private void computeTypeSpecSizes() { - for (Entry[]> entry - : blamer.getTypeToBlamedResources().entrySet()) { - computeTypeSpecSizes(entry.getKey(), entry.getValue()); - } - } - - private void computeTypeChunkSizes() { - for (Entry> 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); + /** + * Returns entries for which there are computed stats. Must first call {@link #compute}. + */ + public Map getStats() { + Preconditions.checkState(!stats.isEmpty(), "Must call #compute() first."); + return Collections.unmodifiableMap(stats); } - } - private void computePackageChunkSizes() { - for (Entry> entry - : blamer.getPackageToBlamedResources().asMap().entrySet()) { - int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD; - addSizes(entry.getValue(), overhead, 0, 1); + /** + * 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 computePoolSizes(StringPoolChunk stringPool, - List[] usages) throws IOException { - int overhead = stringPool.getHeaderSize(); - if (stringPool.getStyleCount() > 0) { - overhead += STYLE_OVERHEAD; + private void computeStringPoolSizes() throws IOException { + computePoolSizes(resourceTable.getStringPool(), blamer.getStringToBlamedResources()); } - // 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; - } + private void computePackageSizes() throws IOException { + computeTypePoolSizes(); + computeKeyPoolSizes(); + computeTypeSpecSizes(); + computeTypeChunkSizes(); + computePackageChunkSizes(); } - // 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[] 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); + private void computeTypePoolSizes() throws IOException { + for (Entry[]> entry + : blamer.getTypeToBlamedResources().entrySet()) { + computePoolSizes(entry.getKey().getTypeStringPool(), entry.getValue()); + } } - } - - /** - * 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 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); + + private void computeKeyPoolSizes() throws IOException { + for (Entry[]> entry + : blamer.getKeyToBlamedResources().entrySet()) { + computePoolSizes(entry.getKey().getKeyStringPool(), entry.getValue()); + } } - } - /** Stats for an individual {@link ResourceEntry}. */ - public static class ResourceStatistics { + private void computeTypeSpecSizes() { + for (Entry[]> entry + : blamer.getTypeToBlamedResources().entrySet()) { + computeTypeSpecSizes(entry.getKey(), entry.getValue()); + } + } - /** The empty, immutable instance of ResourceStatistics which contains 0 for all values. */ - public static final ResourceStatistics EMPTY = new ResourceStatistics(); + private void computeTypeChunkSizes() { + for (Entry> 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 int privateSize = 0; - private int sharedSize = 0; - private double proportionalSize = 0; + private void computePackageChunkSizes() { + for (Entry> entry + : blamer.getPackageToBlamedResources().asMap().entrySet()) { + int overhead = entry.getKey().getHeaderSize() + PACKAGE_CHUNK_OVERHEAD; + addSizes(entry.getValue(), overhead, 0, 1); + } + } - private ResourceStatistics() {} + private void computePoolSizes(StringPoolChunk stringPool, + List[] 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); + } + } - /** The number of bytes that would be freed if this resource was removed. */ - public int getPrivateSize() { - return privateSize; + private void computeTypeSpecSizes(PackageChunk packageChunk, + List[] 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); + } } - /** The number of bytes taken up by this resource that are also shared with other resources. */ - public int getSharedSize() { - return sharedSize; + /** + * 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); } - /** The total size this resource is responsible for. */ - public double getProportionalSize() { - return proportionalSize; + /** + * 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; } - private void addPrivateSize(int privateSize) { - this.privateSize += privateSize; + /** + * 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; } - private void addSharedSize(int sharedSize) { - this.sharedSize += sharedSize; + /** + * 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 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); + } } - private void addProportionalSize(int numerator, int denominator) { - this.proportionalSize += 1.0 * numerator / denominator; + /** + * 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; + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java index 8752b90549..b6dffd6609 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceTableChunk.java @@ -16,9 +16,9 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; -import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; @@ -36,57 +36,67 @@ */ public final class ResourceTableChunk extends ChunkWithChunks { - /** A string pool containing all string resource values in the entire resource table. */ - private StringPoolChunk stringPool; + /** + * 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 packages = new HashMap<>(); + /** + * The packages contained in this resource table. + */ + private final Map 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."); - } + 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; - } + @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."); } - 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 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 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 getPackages() { - return Collections.unmodifiableCollection(packages.values()); - } + /** + * Returns the packages contained in this resource table. + */ + public Collection getPackages() { + return Collections.unmodifiableCollection(packages.values()); + } - @Override - protected Type getType() { - return Type.TABLE; - } + @Override + protected Type getType() { + return Type.TABLE; + } - @Override - protected void writeHeader(ByteBuffer output) { - super.writeHeader(output); - output.putInt(packages.size()); - } + @Override + protected void writeHeader(ByteBuffer output) { + super.writeHeader(output); + output.putInt(packages.size()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java index b9c62a1d5f..d099defe4c 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/SerializableResource.java @@ -22,20 +22,22 @@ * A resource, typically a @{link Chunk}, that can be converted to an array of bytes. */ public interface SerializableResource { - - /** - * Converts this resource into an array of bytes representation. - * @return An array of bytes representing this resource. - * @throws IOException - */ - byte[] toByteArray() throws IOException; - /** - * Converts this resource into an array of bytes representation. - * @param shrink True if, when converting to a byte array, this resource can modify the returned - * bytes in an effort to reduce the size. - * @return An array of bytes representing this resource. - * @throws IOException - */ - byte[] toByteArray(boolean shrink) throws IOException; + /** + * Converts this resource into an array of bytes representation. + * + * @return An array of bytes representing this resource. + * @throws IOException + */ + byte[] toByteArray() throws IOException; + + /** + * Converts this resource into an array of bytes representation. + * + * @param shrink True if, when converting to a byte array, this resource can modify the returned + * bytes in an effort to reduce the size. + * @return An array of bytes representing this resource. + * @throws IOException + */ + byte[] toByteArray(boolean shrink) throws IOException; } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java index c0c73efaa1..28b7f59074 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/StringPoolChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.io.LittleEndianDataOutputStream; -import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.IOException; @@ -28,396 +28,416 @@ import java.nio.ByteOrder; import java.util.*; -/** Represents a string pool structure. */ +/** + * Represents a string pool structure. + */ public final class StringPoolChunk extends Chunk { - // These are the defined flags for the "flags" field of ResourceStringPoolHeader - private static final int SORTED_FLAG = 1 << 0; - private static final int UTF8_FLAG = 1 << 8; - - /** 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 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 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 readStrings(ByteBuffer buffer, int offset, int count) { - List 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 readStyles(ByteBuffer buffer, int offset, int count) { - List 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)); + // 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 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 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(); } - return result; - } - - private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink) - throws IOException { - int stringOffset = 0; - Map 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; - } + + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + strings.addAll(readStrings(buffer, offset + stringsStart, stringCount)); + styles.addAll(readStyles(buffer, offset + stylesStart, styleCount)); } - // ARSC files pad to a 4-byte boundary. We should do so too. - stringOffset = writePad(payload, stringOffset); - return stringOffset; - } + /** + * 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); + } - private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink) - throws IOException { - int styleOffset = 0; - if (!styles.isEmpty()) { - Map 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); + /** + * 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); } - 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); + + public void updateString(int index, String value) { + strings.set(index, value); } - output.write(offsets.array()); - output.write(baos.toByteArray()); - if (!styles.isEmpty()) { - header.putInt(STYLE_START_OFFSET, getHeaderSize() + getOffsetSize() + stringOffset); + /** + * Returns the number of strings in this pool. + */ + public int getStringCount() { + return strings.size(); } - } - - /** - * 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 spans; - - static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) { - Builder 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()); + + /** + * 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); } - private StringPoolStyle(List spans) { - this.spans = spans; + /** + * Returns the number of styles in this pool. + */ + public int getStyleCount() { + return styles.size(); } - @Override - public byte[] toByteArray() throws IOException { - return toByteArray(false); + /** + * Returns the type of strings in this pool. + */ + public BinaryResourceString.Type getStringType() { + return isUTF8() ? BinaryResourceString.Type.UTF8 : BinaryResourceString.Type.UTF16; } @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(); + protected Type getType() { + return Type.STRING_POOL; } - @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); + /** + * Returns the number of bytes needed for offsets based on {@code strings} and {@code styles}. + */ + private int getOffsetSize() { + return (strings.size() + styles.size()) * 4; } - @Override - public int hashCode() { - return Objects.hash(spans); + /** + * 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; } /** - * 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: + * True if this string pool contains already-sorted strings. * - *

"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"
+ * @return true if @{code strings} are sorted. */ - @Override - public String toString() { - return String.format("StringPoolStyle{spans=%s}", spans); + public boolean isSorted() { + return (flags & SORTED_FLAG) != 0; } - } - - /** 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 List readStrings(ByteBuffer buffer, int offset, int count) { + List 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 StringPoolSpan(int nameIndex, int start, int stop, StringPoolChunk parent) { - this.nameIndex = nameIndex; - this.start = start; - this.stop = stop; - this.parent = parent; + private List readStyles(ByteBuffer buffer, int offset, int count) { + List 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; } - @Override - public final byte[] toByteArray() { - return toByteArray(false); + private int writeStrings(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int stringOffset = 0; + Map 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; } - @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(); + private int writeStyles(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int styleOffset = 0; + if (!styles.isEmpty()) { + Map 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 - 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); + protected void writeHeader(ByteBuffer output) { + int stringsStart = getHeaderSize() + getOffsetSize(); + output.putInt(strings.size()); + output.putInt(styles.size()); + output.putInt(flags); + output.putInt(strings.isEmpty() ? 0 : stringsStart); + output.putInt(0); // Placeholder. The styles starting offset cannot be computed at this point. } @Override - public int hashCode() { - return Objects.hash(nameIndex, start, stop, parent); + 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); + } } /** - * Returns a brief description of this span. The representation of this information is subject - * to change, but below is a typical example: - * - *
"StringPoolSpan{foo, start=0, stop=5}"
+ * Represents all of the styles for a particular string. The string is determined by its index + * in {@link StringPoolChunk}. */ - @Override - public String toString() { - return String.format("StringPoolSpan{%s, start=%d, stop=%d}", - parent.getString(nameIndex), start, stop); + 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 spans; + + static StringPoolStyle create(ByteBuffer buffer, int offset, StringPoolChunk parent) { + Builder 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 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: + * + *
"StringPoolStyle{spans=[StringPoolSpan{foo, start=0, stop=5}, ...]}"
+ */ + @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: + * + *
"StringPoolSpan{foo, start=0, stop=5}"
+ */ + @Override + public String toString() { + return String.format("StringPoolSpan{%s, start=%d, stop=%d}", + parent.getString(nameIndex), start, stop); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java index 9a98ed54c8..1030df262b 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeChunk.java @@ -16,11 +16,11 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.io.LittleEndianDataOutputStream; import com.google.common.primitives.UnsignedBytes; -import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.IOException; @@ -38,412 +38,485 @@ */ 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 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 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 entries) { - for (Map.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 values; - private final int parentEntry; - private final TypeChunk parent; - - private Entry(int headerSize, - int flags, - int keyIndex, - BinaryResourceValue value, - Map 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; } + /** + * The type identifier of the resource type this chunk is holding. + */ + private final int id; - /** Resource entry flags. */ - public int flags() { return flags; } + /** + * The number of resources of this type at creation time. + */ + private final int entryCount; - /** Index into {@link PackageChunk#getKeyStringPool} identifying this entry. */ - public int keyIndex() { return keyIndex; } + /** + * The offset (from {@code offset}) in the original buffer where {@code entries} start. + */ + private final int entriesStart; - /** The value of this resource entry, if this is not a complex entry. Else, null. */ - @Nullable - public BinaryResourceValue value() { return value; } + /** + * The resource configuration that these resource entries correspond to. + */ + private BinaryResourceConfiguration configuration; /** - * Update the value of this resource entry - * @param value + * A sparse list of resource entries defined by this chunk. */ - public void updateValue(@Nullable BinaryResourceValue value){ - this.value = value; + private final Map 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); } - /** The extra values in this resource entry if this {@link #isComplex}. */ - public Map values() { return values; } - - /** Update the value in this resource entry if this {@link #isComplex}. */ - public void updateValue(int index, BinaryResourceValue value){ - if (index >= 0) { - if (value != null) { - values.put(index, value); - } else { - values.remove(index); + @Override + protected void init(ByteBuffer buffer) { + int offset = this.offset + entriesStart; + for (int i = 0; i < entryCount; ++i) { + Entry entry = Entry.create(buffer, offset, this); + if (entry != null) { + entries.put(i, entry); + } } - } } /** - * Entry into {@link PackageChunk} that is the parent {@link Entry} to this entry. - * This value only makes sense when this is complex ({@link #isComplex} returns true). + * Returns the (1-based) type id of the resource types that this {@link TypeChunk} is holding. */ - public int parentEntry() { return parentEntry; } + public int getId() { + return id; + } - /** 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 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 name of the type this chunk represents (e.g. string, attr, id). */ - public final String typeName() { - return parent().getTypeName(); + /** + * Returns the resource configuration that these resource entries correspond to. + */ + public BinaryResourceConfiguration getConfiguration() { + return configuration; } - /** The total number of bytes that this {@link Entry} takes up. */ - public final int size() { - return headerSize() + (isComplex() ? values().size() * MAPPING_SIZE : BinaryResourceValue.SIZE); + /** + * 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 key name identifying this resource entry. */ - public final String key() { - return parent().getKeyName(keyIndex()); + /** + * Returns the total number of entries for this type + configuration, including null entries. + */ + public int getTotalEntryCount() { + return entryCount; } - /** Update the key name in this resource entry. */ - public final void updateKey(int keyIndex, String value) { - parent().updateKey(keyIndex, value); + /** + * Returns a sparse list of 0-based indices to resource entries defined by this chunk. + */ + public Map getEntries() { + return Collections.unmodifiableMap(entries); } - /** Returns true if this is a complex resource. */ - public final boolean isComplex() { - return (flags() & FLAG_COMPLEX) != 0; + /** + * 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()); } /** - * 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. + * 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. * - *

Otherwise, this position is parsed and returned as an {@link Entry}. + * @param entries A sparse list containing index:entry pairs to override. + */ + public void overrideEntries(Map entries) { + for (Map.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 buffer A buffer positioned at an offset to an {@link Entry}. - * @param baseOffset Offset that must be added to the value at {@code buffer}'s position. - * @param parent The {@link TypeChunk} that this resource entry belongs to. - * @return New {@link Entry} or null if there is no resource at this location. + * @param 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 - 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; + 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 - 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 values = new LinkedHashMap<>(); - int parentEntry = 0; - if ((flags & FLAG_COMPLEX) != 0) { - parentEntry = buffer.getInt(); - int valueCount = buffer.getInt(); - for (int i = 0; i < valueCount; ++i) { - values.put(buffer.getInt(), BinaryResourceValue.create(buffer)); + public PackageChunk getPackageChunk() { + Chunk chunk = getParent(); + while (chunk != null && !(chunk instanceof PackageChunk)) { + chunk = chunk.getParent(); } - } else { - value = BinaryResourceValue.create(buffer); - } - return new Entry(headerSize, flags, keyIndex, value, values, parentEntry, parent); + return chunk != null ? (PackageChunk) chunk : null; } @Override - public final byte[] toByteArray() { - return toByteArray(false); + protected Type getType() { + return Type.TABLE_TYPE; } - @Override - public final byte[] toByteArray(boolean shrink) { - ByteBuffer buffer = ByteBuffer.allocate(size()); - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.putShort((short) headerSize()); - buffer.putShort((short) flags()); - buffer.putInt(keyIndex()); - if (isComplex()) { - buffer.putInt(parentEntry()); - buffer.putInt(values().size()); - for (Map.Entry entry : values().entrySet()) { - buffer.putInt(entry.getKey()); - buffer.put(entry.getValue().toByteArray(shrink)); + /** + * Returns the number of bytes needed for offsets based on {@code entries}. + */ + private int getOffsetSize() { + return entryCount * 4; + } + + private int writeEntries(DataOutput payload, ByteBuffer offsets, boolean shrink) + throws IOException { + int entryOffset = 0; + for (int i = 0; i < entryCount; ++i) { + Entry entry = entries.get(i); + if (entry == null) { + offsets.putInt(Entry.NO_ENTRY); + } else { + byte[] encodedEntry = entry.toByteArray(shrink); + payload.write(encodedEntry); + offsets.putInt(entryOffset); + entryOffset += encodedEntry.length; + } } - } else { - BinaryResourceValue value = value(); - Preconditions.checkNotNull(value, "A non-complex TypeChunk entry must have a value."); - buffer.put(value.toByteArray()); - } - return buffer.array(); + entryOffset = writePad(payload, entryOffset); + return entryOffset; } @Override - public final String toString() { - return String.format("Entry{key=%s}", key()); + protected void writeHeader(ByteBuffer output) { + int entriesStart = getHeaderSize() + getOffsetSize(); + output.putInt(id); // Write an unsigned byte with 3 bytes padding + output.putInt(entryCount); + output.putInt(entriesStart); + output.put(configuration.toByteArray(false)); } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Entry entry = (Entry)o; - return headerSize == entry.headerSize && - flags == entry.flags && - keyIndex == entry.keyIndex && - parentEntry == entry.parentEntry && - Objects.equals(value, entry.value) && - Objects.equals(values, entry.values) && - Objects.equals(parent, entry.parent); + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ByteBuffer offsets = ByteBuffer.allocate(getOffsetSize()).order(ByteOrder.LITTLE_ENDIAN); + try (LittleEndianDataOutputStream payload = new LittleEndianDataOutputStream(baos)) { + writeEntries(payload, offsets, shrink); + } + output.write(offsets.array()); + output.write(baos.toByteArray()); } - @Override - public int hashCode() { - return Objects.hash(headerSize, flags, keyIndex, value, values, parentEntry, parent); + /** + * An {@link Entry} in a {@link TypeChunk}. Contains one or more {@link BinaryResourceValue}. + */ + public static class Entry implements SerializableResource { + + /** + * An entry offset that indicates that a given resource is not present. + */ + public static final int NO_ENTRY = 0xFFFFFFFF; + + /** + * Set if this is a complex resource. Otherwise, it's a simple resource. + */ + private static final int FLAG_COMPLEX = 0x0001; + + /** + * Size of a single resource id + value mapping entry. + */ + private static final int MAPPING_SIZE = 4 + BinaryResourceValue.SIZE; + + private final int headerSize; + private final int flags; + private final int keyIndex; + private BinaryResourceValue value; + private Map values; + private final int parentEntry; + private final TypeChunk parent; + + private Entry(int headerSize, + int flags, + int keyIndex, + BinaryResourceValue value, + Map 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 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. + * + *

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 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 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); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java index 691d9a38af..c12922b8d2 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java @@ -16,86 +16,98 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.primitives.UnsignedBytes; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** A chunk that contains a collection of resource entries for a particular resource data type. */ +/** + * A chunk that contains a collection of resource entries for a particular resource data type. + */ public final class TypeSpecChunk extends Chunk { - /** The id of the resource type that this type spec refers to. */ - private final int id; + /** + * The id of the resource type that this type spec refers to. + */ + private final int id; - /** Resource configuration masks. */ - private final int[] resources; + /** + * Resource configuration masks. + */ + private final int[] resources; - protected TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - id = UnsignedBytes.toInt(buffer.get()); - buffer.position(buffer.position() + 3); // Skip 3 bytes for packing - int resourceCount = buffer.getInt(); - resources = new int[resourceCount]; + 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(); + 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 (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; - } + /** + * 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; - } + @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 name of the type this chunk represents (e.g. string, attr, id). + */ + public String getTypeName() { + PackageChunk packageChunk = getPackageChunk(); + Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass()); + StringPoolChunk typePool = packageChunk.getTypeStringPool(); + Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass()); + return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index + } - /** Returns the package enclosing this chunk, if any. Else, returns null. */ - @Nullable - private PackageChunk getPackageChunk() { - Chunk chunk = getParent(); - while (chunk != null && !(chunk instanceof PackageChunk)) { - chunk = chunk.getParent(); + /** + * Returns the 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; } - 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 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); + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + for (int resource : resources) { + output.writeInt(resource); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java index 7117e63f7b..0d07c31993 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/UnknownChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -27,35 +28,35 @@ */ public final class UnknownChunk extends Chunk { - private final Type type; + private final Type type; - private final byte[] header; + private final byte[] header; - private final byte[] payload; + private final byte[] payload; - protected UnknownChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); + 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); - } + type = Type.fromCode(buffer.getShort(offset)); + header = new byte[headerSize - Chunk.METADATA_SIZE]; + payload = new byte[chunkSize - headerSize]; + buffer.get(header); + buffer.get(payload); + } - @Override - protected void writeHeader(ByteBuffer output) { - output.put(header); - } + @Override + protected void writeHeader(ByteBuffer output) { + output.put(header); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - output.write(payload); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + output.write(payload); + } - @Override - protected Type getType() { - return type; - } + @Override + protected Type getType() { + return type; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java index 1cb2977b3f..f3d696834e 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlAttribute.java @@ -20,129 +20,149 @@ import java.nio.ByteOrder; import java.util.Objects; -/** Represents an XML attribute and value. */ +/** + * Represents an XML attribute and value. + */ public class XmlAttribute implements SerializableResource { - /** The serialized size in bytes of an {@link XmlAttribute}. */ - public static final int SIZE = 12 + BinaryResourceValue.SIZE; - - 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: - * - *

"XmlAttribute{namespace=foo, name=bar, value=1234}"
- */ - @Override - public String toString() { - return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}", - namespace(), name(), rawValue()); - } + /** + * 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: + * + *
"XmlAttribute{namespace=foo, name=bar, value=1234}"
+ */ + @Override + public String toString() { + return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}", + namespace(), name(), rawValue()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java index 44bc66befe..2f961cb354 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java @@ -17,57 +17,68 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents an XML cdata node. */ +/** + * Represents an XML cdata node. + */ public final class XmlCdataChunk extends XmlNodeChunk { - /** A string reference to a string containing the raw character data. */ - private final int rawValue; + /** + * A string reference to a string containing the raw character data. + */ + private final int rawValue; - /** A {@link BinaryResourceValue} instance containing the parsed value. */ - private final BinaryResourceValue binaryResourceValue; + /** + * A {@link BinaryResourceValue} instance containing the parsed value. + */ + private final BinaryResourceValue binaryResourceValue; - protected XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - rawValue = buffer.getInt(); - binaryResourceValue = BinaryResourceValue.create(buffer); - } + 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 string containing the raw character data of this chunk. + */ + public String getRawValue() { + return getString(rawValue); + } - /** Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. */ - public BinaryResourceValue getResourceValue() { - return binaryResourceValue; - } + /** + * Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. + */ + public BinaryResourceValue getResourceValue() { + return binaryResourceValue; + } - @Override - protected Type getType() { - return Type.XML_CDATA; - } + @Override + protected Type getType() { + return Type.XML_CDATA; + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(rawValue); - output.write(binaryResourceValue.toByteArray()); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(rawValue); + output.write(binaryResourceValue.toByteArray()); + } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"
- */ - @Override - public String toString() { - return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}", - getLineNumber(), getComment(), getRawValue()); - } + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
"XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"
+ */ + @Override + public String toString() { + return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}", + getLineNumber(), getComment(), getRawValue()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java index 08aba6d3a7..9c55bd42ab 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; /** @@ -27,22 +28,24 @@ */ public final class XmlChunk extends ChunkWithChunks { - protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML; - } + @Override + protected Type getType() { + return Type.XML; + } - /** Returns a string at the provided (0-based) index if the index exists in the string pool. */ - public String getString(int index) { - for (Chunk chunk : getChunks().values()) { - if (chunk instanceof StringPoolChunk) { - return ((StringPoolChunk) chunk).getString(index); - } + /** + * Returns a string at the provided (0-based) index if the index exists in the string pool. + */ + public String getString(int index) { + for (Chunk chunk : getChunks().values()) { + if (chunk instanceof StringPoolChunk) { + return ((StringPoolChunk) chunk).getString(index); + } + } + throw new IllegalStateException("XmlChunk did not contain a string pool."); } - throw new IllegalStateException("XmlChunk did not contain a string pool."); - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java index c336c68b9e..f3020eeb8a 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlEndElementChunk.java @@ -17,59 +17,70 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents the end of an XML node. */ +/** + * Represents the end of an XML node. + */ public final class XmlEndElementChunk extends XmlNodeChunk { - /** A string reference to the namespace URI, or -1 if not present. */ - private final int namespace; + /** + * A string reference to the namespace URI, or -1 if not present. + */ + private final int namespace; - /** A string reference to the attribute name. */ - private final int name; + /** + * A string reference to the attribute name. + */ + private final int name; - protected XmlEndElementChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - namespace = buffer.getInt(); - name = buffer.getInt(); - } + 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 namespace URI, or the empty string if no namespace is present. + */ + public String getNamespace() { + return getString(namespace); + } - /** Returns the attribute name. */ - public String getName() { - return getString(name); - } + /** + * Returns the attribute name. + */ + public String getName() { + return getString(name); + } - @Override - protected Type getType() { - return Type.XML_END_ELEMENT; - } + @Override + protected Type getType() { + return Type.XML_END_ELEMENT; + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(namespace); - output.writeInt(name); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(namespace); + output.writeInt(name); + } - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
-   * 
- */ - @Override - public String toString() { - return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}", - getLineNumber(), getComment(), getNamespace(), getName()); - } + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
+     * "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
+     * 
+ */ + @Override + public String toString() { + return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}", + getLineNumber(), getComment(), getNamespace(), getName()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java index dce571456e..d90955eb1f 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceChunk.java @@ -17,54 +17,65 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; -/** Represents the start/end of a namespace in an XML document. */ +/** + * Represents the start/end of a namespace in an XML document. + */ public abstract class XmlNamespaceChunk extends XmlNodeChunk { - /** A string reference to the namespace prefix. */ - private final int prefix; + /** + * A string reference to the namespace prefix. + */ + private final int prefix; - /** A string reference to the namespace URI. */ - private final int uri; + /** + * A string reference to the namespace URI. + */ + private final int uri; - protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - prefix = buffer.getInt(); - uri = buffer.getInt(); - } + protected XmlNamespaceChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + prefix = buffer.getInt(); + uri = buffer.getInt(); + } - /** Returns the namespace prefix. */ - public String getPrefix() { - return getString(prefix); - } + /** + * Returns the namespace prefix. + */ + public String getPrefix() { + return getString(prefix); + } - /** Returns the namespace URI. */ - public String getUri() { - return getString(uri); - } + /** + * Returns the namespace URI. + */ + public String getUri() { + return getString(uri); + } - @Override - protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) - throws IOException { - super.writePayload(output, header, shrink); - output.writeInt(prefix); - output.writeInt(uri); - } + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + output.writeInt(prefix); + output.writeInt(uri); + } - /** - * Returns a brief description of this namespace chunk. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
-   * 
- */ - @Override - public String toString() { - return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}", - getLineNumber(), getComment(), getPrefix(), getUri()); - } + /** + * Returns a brief description of this namespace chunk. The representation of this information is + * subject to change, but below is a typical example: + * + *
+     * "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
+     * 
+ */ + @Override + public String toString() { + return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}", + getLineNumber(), getComment(), getPrefix(), getUri()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java index 6cce6e9fdf..49f70957db 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java @@ -17,17 +17,20 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** Represents the ending tag of a namespace in an XML document. */ +/** + * Represents the ending tag of a namespace in an XML document. + */ public final class XmlNamespaceEndChunk extends XmlNamespaceChunk { - protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML_END_NAMESPACE; - } + @Override + protected Type getType() { + return Type.XML_END_NAMESPACE; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java index 3cb3daff9f..1fbd23383c 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java @@ -17,17 +17,20 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** Represents the starting tag of a namespace in an XML document. */ +/** + * Represents the starting tag of a namespace in an XML document. + */ public final class XmlNamespaceStartChunk extends XmlNamespaceChunk { - protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected Type getType() { - return Type.XML_START_NAMESPACE; - } + @Override + protected Type getType() { + return Type.XML_START_NAMESPACE; + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java index 3f293c0a9d..2dddc35780 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNodeChunk.java @@ -17,80 +17,93 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.nio.ByteBuffer; -/** The common superclass for the various types of XML nodes. */ +/** + * The common superclass for the various types of XML nodes. + */ public abstract class XmlNodeChunk extends Chunk { - /** The line number in the original source at which this node appeared. */ - private final int lineNumber; - - /** A string reference of this node's comment. If this is -1, then there is no comment. */ - private final int comment; + /** + * The line number in the original source at which this node appeared. + */ + private final int lineNumber; - protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - lineNumber = buffer.getInt(); - comment = buffer.getInt(); - } + /** + * A string reference of this node's comment. If this is -1, then there is no comment. + */ + private final int comment; - /** Returns true if this XML node contains a comment. Else, returns false. */ - public boolean hasComment() { - return comment != -1; - } + protected XmlNodeChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + lineNumber = buffer.getInt(); + comment = buffer.getInt(); + } - /** Returns the line number in the original source at which this node appeared. */ - public int getLineNumber() { - return lineNumber; - } + /** + * Returns true if this XML node contains a comment. Else, returns false. + */ + public boolean hasComment() { + return comment != -1; + } - /** Returns the comment associated with this node, if any. Else, returns the empty string. */ - public String getComment() { - return getString(comment); - } + /** + * Returns the line number in the original source at which this node appeared. + */ + public int getLineNumber() { + return lineNumber; + } - /** - * 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 ""; + /** + * Returns the comment associated with this node, if any. Else, returns the empty string. + */ + public String getComment() { + return getString(comment); } - Chunk parent = getParent(); - while (parent != null) { - if (parent instanceof XmlChunk) { - return ((XmlChunk) parent).getString(index); - } - parent = parent.getParent(); + + /** + * 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."); } - 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); - } + /** + * 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: - * - *
"XmlNodeChunk{line=1234, comment=My awesome comment.}"
- */ - @Override - public String toString() { - return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment()); - } + /** + * Returns a brief description of this XML node. The representation of this information is + * subject to change, but below is a typical example: + * + *
"XmlNodeChunk{line=1234, comment=My awesome comment.}"
+ */ + @Override + public String toString() { + return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment()); + } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java index 85d389f0ec..4c81d5c595 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlResourceMapChunk.java @@ -17,6 +17,7 @@ package com.google.devrel.gmscore.tools.apk.arsc; import androidx.annotation.Nullable; + import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -31,56 +32,60 @@ */ public class XmlResourceMapChunk extends Chunk { - /** The size of a resource reference for {@code resources} in bytes. */ - private static final int RESOURCE_SIZE = 4; + /** + * The size of a resource reference for {@code resources} in bytes. + */ + private static final int RESOURCE_SIZE = 4; - /** - * Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the - * resourceID returned by {@code resources.get(2)}. - */ - private final List resources = new ArrayList<>(); + /** + * Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the + * resourceID returned by {@code resources.get(2)}. + */ + private final List resources = new ArrayList<>(); - protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) { - super(buffer, parent); - } + protected XmlResourceMapChunk(ByteBuffer buffer, @Nullable Chunk parent) { + super(buffer, parent); + } - @Override - protected void init(ByteBuffer buffer) { - super.init(buffer); - resources.addAll(enumerateResources(buffer)); - } + @Override + protected void init(ByteBuffer buffer) { + super.init(buffer); + resources.addAll(enumerateResources(buffer)); + } - private List enumerateResources(ByteBuffer buffer) { - int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE; - List result = new ArrayList<>(resourceCount); - int offset = this.offset + getHeaderSize(); - buffer.mark(); - buffer.position(offset); + private List enumerateResources(ByteBuffer buffer) { + int resourceCount = (getOriginalChunkSize() - getHeaderSize()) / RESOURCE_SIZE; + List 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()); - } + for (int i = 0; i < resourceCount; ++i) { + result.add(buffer.getInt()); + } - buffer.reset(); - return result; - } + 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)); - } + /** + * 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 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); + @Override + protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink) + throws IOException { + super.writePayload(output, header, shrink); + for (Integer resource : resources) { + output.writeInt(resource); + } } - } } diff --git a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java index 523dcaf1a4..d219dec230 100644 --- a/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java +++ b/app/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlStartElementChunk.java @@ -16,9 +16,9 @@ package com.google.devrel.gmscore.tools.apk.arsc; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; -import androidx.annotation.Nullable; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; @@ -26,120 +26,144 @@ import java.util.Collections; import java.util.List; -/** Represents the beginning of an XML node. */ +/** + * Represents the beginning of an XML node. + */ public final class XmlStartElementChunk extends XmlNodeChunk { - /** A string reference to the namespace URI, or -1 if not present. */ - private final int namespace; - - /** A string reference to the 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 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 enumerateAttributes(ByteBuffer buffer) { - List 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; + /** + * 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 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 enumerateAttributes(ByteBuffer buffer) { + List 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 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)); + } } - 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 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: + * + *
+     * "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
+     * 
+ */ + @Override + public String toString() { + return String.format( + "XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}", + getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString()); } - } - - /** - * Returns a brief description of this XML node. The representation of this information is - * subject to change, but below is a typical example: - * - *
-   * "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
-   * 
- */ - @Override - public String toString() { - return String.format( - "XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}", - getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString()); - } } diff --git a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java index a023f48e1d..b6a1d8d6f1 100644 --- a/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java +++ b/app/src/main/java/com/vk/api/audio/AudioGetLyrics.java @@ -1,20 +1,16 @@ package com.vk.api.audio; import android.text.TextUtils; - import androidx.annotation.NonNull; - import com.vk.api.base.ApiRequest; import com.vk.dto.music.MusicTrack; import com.vk.log.L; - import org.json.JSONObject; - -import java.util.ArrayList; - import ru.vtosters.lite.music.Genius; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.Preferences; +import ru.vtosters.hooks.other.Preferences; + +import java.util.ArrayList; public class AudioGetLyrics extends ApiRequest { private final MusicTrack musicTrack; diff --git a/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java b/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java index 0111560cf9..dfa535f886 100644 --- a/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java +++ b/app/src/main/java/com/vk/api/gifts/GiftGetByStickerId.java @@ -1,10 +1,8 @@ package com.vk.api.gifts; import android.content.Context; - import com.vk.api.base.ApiRequest; import com.vk.dto.gift.CatalogedGift; - import org.json.JSONException; import org.json.JSONObject; @@ -29,7 +27,7 @@ public GiftGetByStickerId(Context context, int i) { @Override // com.vk.api.sdk.o.VKRequest public GiftGetByStickerId.a a(JSONObject jSONObject) throws JSONException { JSONObject jSONObject2 = jSONObject.getJSONObject("response"); - return new a(jSONObject2.optInt("balance"), + return new a(jSONObject2.optInt("balance"), new CatalogedGift(jSONObject2.getJSONObject("gift"))); } } \ No newline at end of file diff --git a/app/src/main/java/com/vk/core/extensions/TextViewExt.java b/app/src/main/java/com/vk/core/extensions/TextViewExt.java index 07e5293313..f9f023e08b 100644 --- a/app/src/main/java/com/vk/core/extensions/TextViewExt.java +++ b/app/src/main/java/com/vk/core/extensions/TextViewExt.java @@ -14,7 +14,7 @@ import b.h.v.TextViewTextChangeEvent; import b.h.v.TextViewTextChangeEventObservable; import com.vk.core.util.ContextExtKt; -import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.hooks.other.ThemesUtils; public final class TextViewExt { public static void a(TextView textView, Drawable drawable) { diff --git a/app/src/main/java/com/vk/core/util/DownloadUtils.java b/app/src/main/java/com/vk/core/util/DownloadUtils.java index 23ba639753..8ddceb31ec 100644 --- a/app/src/main/java/com/vk/core/util/DownloadUtils.java +++ b/app/src/main/java/com/vk/core/util/DownloadUtils.java @@ -11,13 +11,15 @@ import android.os.Environment; import android.text.TextUtils; import android.util.Log; - +import b.h.g.m.CameraUtils; +import b.h.g.m.FileUtils; import com.facebook.common.i.MediaUtils; import com.facebook.x.g.EncodedImage; import com.vk.imageloader.VKImageLoader; import com.vk.log.L; import com.vk.metrics.eventtracking.VkTracker; import com.vtosters.lite.R; +import ru.vtosters.lite.concurrent.VTExecutors; import java.io.File; import java.io.FileOutputStream; @@ -26,10 +28,6 @@ import java.net.URI; import java.util.HashSet; -import b.h.g.m.CameraUtils; -import b.h.g.m.FileUtils; -import ru.vtosters.lite.concurrent.VTExecutors; - public class DownloadUtils { public static final HashSet a = new HashSet<>(); @@ -49,7 +47,7 @@ private static void a(Context context, String str, String str2, boolean z) { ToastUtils.a(context.getString(R.string.error) + " [" + e2.getMessage() + "]"); } } - + private static void b(final Context context, String str, String uri, boolean z) { if (TextUtils.isEmpty(uri)) { return; diff --git a/app/src/main/java/com/vk/core/utils/VerifyInfoHelper.java b/app/src/main/java/com/vk/core/utils/VerifyInfoHelper.java index 6ddb223830..f32c281dfe 100644 --- a/app/src/main/java/com/vk/core/utils/VerifyInfoHelper.java +++ b/app/src/main/java/com/vk/core/utils/VerifyInfoHelper.java @@ -14,7 +14,7 @@ import com.vk.dto.common.VerifyInfo; import com.vk.extensions.ViewExtKt; import com.vtosters.lite.R; -import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.hooks.other.ThemesUtils; public final class VerifyInfoHelper { diff --git a/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java b/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java index 251e4a7571..644415d33b 100644 --- a/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java +++ b/app/src/main/java/com/vk/im/engine/commands/dialogs/DialogGetMentionSuggestionCmd.java @@ -186,8 +186,7 @@ public Boolean invoke(Member member) { } }), a(b2))); EntityValue entityValue = new EntityValue(z2 ? null : l, z3); - switch (this.d) - { + switch (this.d) { case CACHE: source = Source.CACHE; break; diff --git a/app/src/main/java/com/vk/im/engine/models/MemberType.java b/app/src/main/java/com/vk/im/engine/models/MemberType.java index d816fe0855..4a4e2157ab 100644 --- a/app/src/main/java/com/vk/im/engine/models/MemberType.java +++ b/app/src/main/java/com/vk/im/engine/models/MemberType.java @@ -16,12 +16,18 @@ public enum MemberType { public static MemberType a(int i) { switch (i) { - case 0: return UNKNOWN; - case 1: return USER; - case 2: return GROUP; - case 3: return EMAIL; - case 4: return CONTACT; - case 5: return CUSTOM_BOT; + case 0: + return UNKNOWN; + case 1: + return USER; + case 2: + return GROUP; + case 3: + return EMAIL; + case 4: + return CONTACT; + case 5: + return CUSTOM_BOT; } throw new IllegalArgumentException("Unknown member type value: " + i); } diff --git a/app/src/main/java/com/vk/im/ui/components/common/DialogActionsHelper.java b/app/src/main/java/com/vk/im/ui/components/common/DialogActionsHelper.java index facb5c17d7..ab52f76555 100644 --- a/app/src/main/java/com/vk/im/ui/components/common/DialogActionsHelper.java +++ b/app/src/main/java/com/vk/im/ui/components/common/DialogActionsHelper.java @@ -11,12 +11,11 @@ import com.vk.im.engine.models.groups.Group; import com.vk.im.engine.utils.DialogPermissionHelper; import com.vk.im.ui.p.ImBridge7; +import ru.vtosters.hooks.DialogMenuInjectors; import java.util.ArrayList; import java.util.List; -import ru.vtosters.lite.dnr.DNRInjector; - public final class DialogActionsHelper { public static final DialogActionsHelper a = new DialogActionsHelper(); @@ -28,7 +27,7 @@ public final List a(ImConfig imConfig, Dialog dialog, ProfilesSimp boolean I1 = z1 != null && z1.I1(); ArrayList arrayList = new ArrayList<>(); - DNRInjector.inject(dialog, arrayList); + DialogMenuInjectors.inject(dialog, arrayList); arrayList.add(DialogAction.SEARCH); @@ -97,7 +96,7 @@ public final List a(Dialog dialog, ProfilesSimpleInfo profilesSimp ChatSettings z1 = dialog.z1(); ArrayList arrayList = new ArrayList<>(); - DNRInjector.inject(dialog, arrayList); + DialogMenuInjectors.inject(dialog, arrayList); // if (dialog.hasUnread()) { arrayList.add(DialogAction.MARK_AS_READ); diff --git a/app/src/main/java/com/vk/im/ui/components/common/MsgAction.java b/app/src/main/java/com/vk/im/ui/components/common/MsgAction.java index fe03de846a..8bad9caf44 100644 --- a/app/src/main/java/com/vk/im/ui/components/common/MsgAction.java +++ b/app/src/main/java/com/vk/im/ui/components/common/MsgAction.java @@ -11,5 +11,6 @@ public enum MsgAction { PIN, UNPIN, TRANSLATE, - READTO + READTO, + KICK } \ No newline at end of file diff --git a/app/src/main/java/com/vk/im/ui/components/common/MsgActionHelper.java b/app/src/main/java/com/vk/im/ui/components/common/MsgActionHelper.java index 50207886c5..438d192a42 100644 --- a/app/src/main/java/com/vk/im/ui/components/common/MsgActionHelper.java +++ b/app/src/main/java/com/vk/im/ui/components/common/MsgActionHelper.java @@ -3,9 +3,11 @@ import com.vk.core.extensions.CollectionExt; import com.vk.im.engine.ImConfig; import com.vk.im.engine.ImEngine; +import com.vk.im.engine.models.Member; import com.vk.im.engine.models.dialogs.Dialog; import com.vk.im.engine.models.messages.Msg; import com.vk.im.engine.utils.MsgPermissionHelper; +import ru.vtosters.lite.utils.AccountManagerUtils; import java.util.ArrayList; import java.util.Collection; @@ -35,6 +37,24 @@ public final List a(ImConfig imConfig, Dialog dialog, Collection this.D == this.b.c.getLocalId(); case VK_ID -> this.D == this.b.c.C1(); diff --git a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartCallHolder.java b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartCallHolder.java index f36ed453b0..28e6faff4f 100644 --- a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartCallHolder.java +++ b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartCallHolder.java @@ -21,7 +21,7 @@ import com.vtosters.lite.R; import kotlin.TypeCastException; import kotlin.jvm.internal.Intrinsics; -import ru.vtosters.lite.hooks.CallsHook; +import ru.vtosters.hooks.CallsHook; public final class MsgPartCallHolder extends MsgPartHolderBase { public static final a I = new a(); diff --git a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java index 6cda32bab7..23d3e3bb4e 100644 --- a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java +++ b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/msgparts/MsgPartTextHolder.java @@ -23,7 +23,7 @@ protected View b(LayoutInflater inflater, ViewGroup parent) { MsgPartTextHolder.this.f.a(MsgPartTextHolder.this.g.getLocalId()); }); mTextPart.setOnLongClickListener(view -> { - if(!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId()); + if (!mTextPart.isTextSelectable()) MsgPartTextHolder.this.f.b(MsgPartTextHolder.this.g.getLocalId()); return false; }); diff --git a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java index f372dd6687..808a9d02db 100644 --- a/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java +++ b/app/src/main/java/com/vk/im/ui/components/viewcontrollers/msg_list/adapter/vh/VhMsg.java @@ -28,7 +28,6 @@ import com.vk.im.engine.models.dialogs.BubbleColors; import com.vk.im.engine.models.dialogs.Dialog; import com.vk.im.engine.models.messages.Msg; -import com.vk.im.engine.models.messages.MsgSyncState; import com.vk.im.engine.utils.MsgPermissionHelper; import com.vk.im.ui.components.viewcontrollers.msg_list.BombView; import com.vk.im.ui.components.viewcontrollers.msg_list.SwipeToReplyItemTouchCallback; @@ -144,7 +143,7 @@ public VhMsg(View itemView, MsgPartHolderBase vhBase) { this.X = context.getString(m.vkim_accessibility_msg_unread); MsgBubbleView var5 = this.e; var5.setContentView(this.B.a(var4, var5)); - ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams)this.e.getLayoutParams(); + ViewGroup.MarginLayoutParams var7 = (ViewGroup.MarginLayoutParams) this.e.getLayoutParams(); Rect var6 = this.G; var6.left = var7.leftMargin; var6.right = var7.rightMargin; @@ -337,7 +336,8 @@ private void a(VhBindArgs args, Rect rect) { if (args.w()) { var12 = var11; if (args.k()) { - label30: { + label30: + { if (args.G()) { var12 = Screen.a(4); } else { @@ -671,7 +671,7 @@ private void e(VhBindArgs args) { private void f(VhBindArgs args) { Msg var2 = args.b.c; if (var2 != null) { - this.P.setStateListener((BombView.d)null); + this.P.setStateListener((BombView.d) null); if (var2.M1() && !var2.E1()) { this.P.setVisibility(View.GONE); } else { @@ -832,7 +832,8 @@ private void j(VhBindArgs args) { MsgBubbleView var8 = this.e; boolean var6 = var4; if (var5) { - label57: { + label57: + { if (!args.C()) { var6 = var4; if (!args.z()) { @@ -877,7 +878,7 @@ private void k(VhBindArgs args) { if (args.s()) { this.itemView.setBackground(this.H); } else { - this.itemView.setBackground((Drawable)null); + this.itemView.setBackground((Drawable) null); } } @@ -985,7 +986,7 @@ public void a(Profile var1) { public void a(@NonNull Msg var1, int var2) { if (this.f0()) { - ((MsgPartCarouselHolder)this.B).a(var1, var2); + ((MsgPartCarouselHolder) this.B).a(var1, var2); } } @@ -1110,7 +1111,7 @@ private h() { public Unit invoke() { if (VhMsg.this.e.getMeasuredWidth() < VhMsg.h0) { - ((ViewGroup.MarginLayoutParams)VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a; + ((ViewGroup.MarginLayoutParams) VhMsg.this.e.getLayoutParams()).leftMargin = VhMsg.h0 - VhMsg.this.e.getMeasuredWidth() + this.a; VhMsg.this.e.invalidate(); VhMsg.this.e.requestLayout(); } else { diff --git a/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java b/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java index 4ef6a7831e..007fadc4c4 100644 --- a/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java +++ b/app/src/main/java/com/vk/im/ui/q/h/e/MentionsController.java @@ -6,7 +6,6 @@ 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; @@ -55,7 +54,7 @@ public MentionsController(ImEngine imEngine, int i, View view, b bVar) { @Override public boolean a() { - if(MentionsController.this.state) + if (MentionsController.this.state) return ViewExtKt.i(MentionsController.this.mCoordinatorLayout); return false; } @@ -105,7 +104,7 @@ private final void h() { 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.mCoordinatorLayout = inflate.findViewById(R.id.mentions_cl); this.mBottomSheetBehavior = (VkBottomSheetBehavior) ((CoordinatorLayout.LayoutParams) viewGroup.getLayoutParams()).getBehavior(); this.mBottomSheetBehavior.a(new VkBottomSheetBehavior.b() { @Override diff --git a/app/src/main/java/com/vk/im/ui/views/Corners.java b/app/src/main/java/com/vk/im/ui/views/Corners.java index 6d09b50101..d6faee429c 100644 --- a/app/src/main/java/com/vk/im/ui/views/Corners.java +++ b/app/src/main/java/com/vk/im/ui/views/Corners.java @@ -2,13 +2,13 @@ import androidx.annotation.NonNull; -public class Corners{ +public class Corners { private int topLeft; private int topRight; private int bottomLeft; private int bottomRight; - public Corners(int i, int i2, int i3, int i4){ + public Corners(int i, int i2, int i3, int i4) { this.topLeft = i; this.topRight = i2; this.bottomLeft = i3; @@ -23,7 +23,7 @@ public Corners() { this(0, 0, 0, 0, 15); } - public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj){ + public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, Object obj) { if ((i5 & 1) != 0) { i = corners.topLeft; } @@ -40,30 +40,30 @@ public static Corners a(Corners corners, int i, int i2, int i3, int i4, int i5, return corners; } - public int a(){ + public int a() { return this.bottomLeft; } - public int b(){ + public int b() { return this.bottomRight; } - public int c(){ + public int c() { return this.topLeft; } - public int d(){ + public int d() { return this.topRight; } - public boolean e(){ + public boolean e() { int i; int i2 = this.topLeft; int i3 = this.topRight; return i2 == i3 && i3 == (i = this.bottomRight) && this.bottomLeft == i; } - public boolean equals(Object obj){ + public boolean equals(Object obj) { if (this != obj) { if (!(obj instanceof Corners)) { return false; @@ -74,24 +74,24 @@ public boolean equals(Object obj){ return true; } - public boolean f(){ + public boolean f() { return this.topLeft == 0 && e(); } - public void g(){ + public void g() { a(0, 0); } - public int hashCode(){ + public int hashCode() { return (((((this.topLeft * 31) + this.topRight) * 31) + this.bottomLeft) * 31) + this.bottomRight; } @NonNull - public String toString(){ + public String toString() { return "Corners(topLeft=" + this.topLeft + ", topRight=" + this.topRight + ", bottomLeft=" + this.bottomLeft + ", bottomRight=" + this.bottomRight + ")"; } - public Corners a(int i, int i2){ + public Corners a(int i, int i2) { if ((i2 & 1) > 0) { this.topLeft = i; } @@ -113,7 +113,7 @@ public Corners a(int i, int i2){ return this; } - public Corners a(int i, int i2, int i3, int i4){ + public Corners a(int i, int i2, int i3, int i4) { this.topLeft = i; this.topRight = i2; this.bottomLeft = i3; @@ -121,7 +121,7 @@ public Corners a(int i, int i2, int i3, int i4){ return this; } - public Corners a(Corners corners){ + public Corners a(Corners corners) { this.topLeft = corners.topLeft; this.topRight = corners.topRight; this.bottomLeft = corners.bottomLeft; diff --git a/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java b/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java index 5ada597055..27ceffa88a 100644 --- a/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java +++ b/app/src/main/java/com/vk/im/ui/views/avatars/StoryBorderView.java @@ -9,7 +9,6 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; - import com.vk.im.ui.o; public class StoryBorderView extends View { diff --git a/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java b/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java index fcea16294c..a2fef7dfb4 100644 --- a/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java +++ b/app/src/main/java/com/vk/im/ui/views/dialogs/DialogItemView.java @@ -1,8 +1,5 @@ package com.vk.im.ui.views.dialogs; -import static com.vk.im.ui.d.vkim_administration_title; -import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; - import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -17,10 +14,9 @@ import android.view.ViewPropertyAnimator; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.ColorInt; import androidx.appcompat.widget.AppCompatImageView; - +import b.h.g.r.MathExt; import com.vk.core.extensions.AnimationExtKt; import com.vk.core.util.ContextExtKt; import com.vk.core.util.Screen; @@ -42,9 +38,10 @@ import com.vk.im.ui.views.avatars.AvatarView; import com.vk.im.ui.views.avatars.StoryBorderView; -import b.h.g.r.MathExt; +import static com.vk.im.ui.d.vkim_administration_title; +import static ru.vtosters.hooks.other.ThemesUtils.getAccentColor; -public class DialogItemView extends ViewGroup{ +public class DialogItemView extends ViewGroup { private final int B; private final int C; private final int D; @@ -80,15 +77,15 @@ public class DialogItemView extends ViewGroup{ private boolean g0; private boolean h0; - public DialogItemView(Context context){ + public DialogItemView(Context context) { this(context, null, 0, 6); } - public DialogItemView(Context context, AttributeSet attributeSet){ + public DialogItemView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0, 4); } - public DialogItemView(Context context, AttributeSet attributeSet, int i){ + public DialogItemView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); Resources resources = getResources(); this.a = resources.getDisplayMetrics(); @@ -255,11 +252,11 @@ public DialogItemView(Context context, AttributeSet attributeSet, int i){ addView(this.f0); } - public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2){ + public DialogItemView(Context context, AttributeSet attributeSet, int i, int i2) { this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i); } - public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){ + public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) { if ((i & 1) != 0) { f = 0.0f; } @@ -279,7 +276,7 @@ public static ViewPropertyAnimator b(View view, float f, long j, long j2, Runnab return AnimationExtKt.b(view, f, j, j2, runnable); } - public static void a(View view, float f, float f2, int i, Object obj){ + public static void a(View view, float f, float f2, int i, Object obj) { if ((i & 1) != 0) { f = 1.0f; } @@ -291,7 +288,7 @@ public static void a(View view, float f, float f2, int i, Object obj){ AnimationExtKt.a(view, f, f2); } - public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj){ + public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnable runnable, int i, Object obj) { if ((i & 1) != 0) { f = 1.0f; } @@ -311,14 +308,14 @@ public static ViewPropertyAnimator a(View view, float f, long j, long j2, Runnab return AnimationExtKt.a(view, f, j, j2, runnable); } - static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj){ + static void a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, Object obj) { if ((i4 & 4) != 0) { i3 = 0; } dialogItemView.a(i, i2, i3); } - static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj){ + static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int i2, int i3, int i4, int i5, int i6, int i7, Object obj) { if ((i7 & 1) != 0) { i = -2; } @@ -340,19 +337,19 @@ static ViewGroup.MarginLayoutParams a(DialogItemView dialogItemView, int i, int return dialogItemView.a(i, i2, i3, i4, i5, i6); } - private Drawable getDrOnlineMobile(){ + private Drawable getDrOnlineMobile() { return ContextExtKt.c(getContext(), f.ic_online_mobile_vkapp_composite_16); } - private Drawable getDrOnlineVkMe(){ + private Drawable getDrOnlineVkMe() { return ContextExtKt.c(getContext(), f.ic_online_mobile_vkme_composite_16); } - private Drawable getDrOnlineWeb(){ + private Drawable getDrOnlineWeb() { return ContextExtKt.c(getContext(), f.ic_online_web_composite_16); } - private int getTimeMargin(){ + private int getTimeMargin() { Layout layout = this.T.getLayout(); if (layout == null) { @@ -374,11 +371,11 @@ private int getTimeMargin(){ return 0; } - private int getTitleColorHighlight(){ + private int getTitleColorHighlight() { return ContextExtKt.a(getContext(), vkim_administration_title); } - private void h(int i, int i2, int i3, int i4){ + private void h(int i, int i2, int i3, int i4) { int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int measuredWidth = getMeasuredWidth() - getPaddingRight(); @@ -412,51 +409,51 @@ private void h(int i, int i2, int i3, int i4){ j(measuredWidth - r, max6, measuredWidth, q + max6); } - private int i(){ + private int i() { return g(this.K); } - private int j(){ + private int j() { return f(this.T) + Math.max(f(this.V), f(this.U)); } - private int k(){ + private int k() { return Math.max(g(this.T), g(this.V) + g(this.U)); } - private int l(){ + private int l() { return Math.max(Math.max(f(this.N), f(this.R)), Math.max(f(this.O), f(this.P))); } - private int m(){ + private int m() { return View.MeasureSpec.makeMeasureSpec(0, 0); } - private boolean n(){ + private boolean n() { return e(this.S); } - private int o(){ + private int o() { return f(this.S); } - private int p(){ + private int p() { return g(this.S); } - private int q(){ + private int q() { return MathExt.a(f(this.b0), f(this.c0), f(this.d0), f(this.e0), f(this.f0)); } - private int r(){ + private int r() { return g(this.b0) + MathExt.a(g(this.c0), g(this.d0), g(this.e0), g(this.f0)); } - private int s(){ + private int s() { return Math.max(f(this.a0), f(this.W)); } - private void setAttach(CharSequence charSequence){ + private void setAttach(CharSequence charSequence) { int i = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -467,57 +464,57 @@ private void setAttach(CharSequence charSequence){ this.U.setText(charSequence); } - private void setBodyLinesCount(int i){ + private void setBodyLinesCount(int i) { boolean z = i == 1; this.T.setSingleLine(z); this.T.setMaxLines(i); } - private int t(){ + private int t() { return g(this.a0) + g(this.W); } - private void u(){ + private void u() { ViewExtKt.b(this.b0, this.g0 | this.h0); } - public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo){ + public void a(Dialog dialog, ProfilesSimpleInfo profilesSimpleInfo) { this.K.a(dialog, profilesSimpleInfo); } - public void b(){ + public void b() { a(null, (ComposingType) null); } - public void c(){ + public void c() { a(getDrOnlineMobile()); } - public void d(){ + public void d() { a(getDrOnlineVkMe()); } - public void e(){ + public void e() { a(getDrOnlineWeb()); } - public void f(){ + public void f() { this.S.setVisibility(GONE); this.S.e(); } - public void g(){ + public void g() { b(this.L, 0.0f, 0L, 0L, null, 15, null); } @Override // android.view.ViewGroup, android.view.View - protected void onDetachedFromWindow(){ + protected void onDetachedFromWindow() { super.onDetachedFromWindow(); a(this.L, 0.0f, 0.0f, 3, null); } @Override // android.view.ViewGroup, android.view.View - protected void onLayout(boolean z, int i, int i2, int i3, int i4){ + protected void onLayout(boolean z, int i, int i2, int i3, int i4) { if (this.E) { g(i, i2, i3, i4); } else { @@ -526,7 +523,7 @@ protected void onLayout(boolean z, int i, int i2, int i3, int i4){ } @Override // android.view.View - protected void onMeasure(int i, int i2){ + protected void onMeasure(int i, int i2) { if (this.E) { c(i); } else { @@ -534,11 +531,11 @@ protected void onMeasure(int i, int i2){ } } - public void setAvatar(Profile profile){ + public void setAvatar(Profile profile) { this.K.a(profile); } - public void setBombVisible(boolean z){ + public void setBombVisible(boolean z) { if (z) { this.b0.setImageResource(f.ic_bomb_composite_24); } @@ -548,23 +545,23 @@ public void setBombVisible(boolean z){ u(); } - public void setCasperIconColor(@ColorInt int i){ + public void setCasperIconColor(@ColorInt int i) { ViewExtKt.a(this.Q, i); } - public void setCasperIconVisible(boolean z){ + public void setCasperIconVisible(boolean z) { this.Q.setVisibility(z ? VISIBLE : GONE); } - public void setErrorVisible(boolean z){ + public void setErrorVisible(boolean z) { this.f0.setVisibility(z ? VISIBLE : GONE); } - public void setGiftVisible(boolean z){ + public void setGiftVisible(boolean z) { this.V.setVisibility(z ? VISIBLE : GONE); } - public void setHasStories(boolean z){ + public void setHasStories(boolean z) { if (this.I == z) { return; } @@ -590,7 +587,7 @@ public void setHasStories(boolean z){ setClipToPadding(!z); } - public void setMentionVisible(boolean z){ + public void setMentionVisible(boolean z) { if (z) { this.b0.setImageResource(f.ic_mention_composite_24); } @@ -600,15 +597,15 @@ public void setMentionVisible(boolean z){ u(); } - public void setMutedVisible(boolean z){ + public void setMutedVisible(boolean z) { this.P.setVisibility(z ? VISIBLE : GONE); } - public void setOnStoryClickListener(View.OnClickListener onClickListener){ + public void setOnStoryClickListener(View.OnClickListener onClickListener) { ViewExtKt.b(this.M, onClickListener); } - public void setSender(Profile profile){ + public void setSender(Profile profile) { if (profile == null) { f(); return; @@ -617,29 +614,29 @@ public void setSender(Profile profile){ this.S.a(profile); } - public void setSendingVisible(boolean z){ + public void setSendingVisible(boolean z) { this.e0.setVisibility(z ? VISIBLE : GONE); } - public void setTime(CharSequence charSequence){ + public void setTime(CharSequence charSequence) { this.R.setText(charSequence); } - public void setUnreadOutVisible(boolean z){ + public void setUnreadOutVisible(boolean z) { this.d0.setVisibility(z ? VISIBLE : GONE); } - public void setVerifiedVisible(boolean z){ + public void setVerifiedVisible(boolean z) { this.O.setVisibility(z ? VISIBLE : GONE); } - private void a(Drawable drawable){ + private void a(Drawable drawable) { ViewExtKt.c(this.L, a(16), a(20)); this.L.setImageDrawable(drawable); a(this.L, 0.0f, 0L, 0L, null, 15, null); } - private void c(int i){ + private void c(int i) { int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight(); d(b(size - this.K.getViewSize()), m()); int r = r(); @@ -655,7 +652,7 @@ private void c(int i){ setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q())))); } - private void d(int i){ + private void d(int i) { int size = (View.MeasureSpec.getSize(i) - getPaddingLeft()) - getPaddingRight(); d(b(size - this.K.getViewSize()), m()); int r = r(); @@ -672,7 +669,7 @@ private void d(int i){ setMeasuredDimension(View.MeasureSpec.getSize(i), getPaddingTop() + getPaddingBottom() + Math.max(f(this.K), l() + Math.max(Math.max(o(), j()), Math.max(s(), q())))); } - private void e(int i, int i2, int i3, int i4){ + private void e(int i, int i2, int i3, int i4) { if (e(this.T) && e(this.U)) { c(i, i2, i3, i4); } else if (e(this.T)) { @@ -683,7 +680,7 @@ private void e(int i, int i2, int i3, int i4){ } } - private void g(int i, int i2, int i3, int i4){ + private void g(int i, int i2, int i3, int i4) { int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int measuredWidth = getMeasuredWidth() - getPaddingRight(); @@ -706,12 +703,12 @@ private void g(int i, int i2, int i3, int i4){ j(measuredWidth - r, i8, measuredWidth, q + i8); } - private void i(int i, int i2, int i3, int i4){ + private void i(int i, int i2, int i3, int i4) { AvatarView avatarView = this.S; b(avatarView, i + b(avatarView), ((i2 + ((i4 - i2) / 2)) - (this.S.getMeasuredHeight() / 2)) + d(this.S)); } - private void j(int i, int i2, int i3, int i4){ + private void j(int i, int i2, int i3, int i4) { int i5 = i2 + ((i4 - i2) / 2); AppCompatImageView appCompatImageView = this.b0; b(appCompatImageView, b(appCompatImageView) + i, (i5 - (this.b0.getMeasuredHeight() / 2)) + d(this.b0)); @@ -728,7 +725,7 @@ private void j(int i, int i2, int i3, int i4){ b(appCompatImageView4, i + b(appCompatImageView4), (i5 - (this.f0.getMeasuredHeight() / 2)) + d(this.f0)); } - private void k(int i, int i2, int i3, int i4){ + private void k(int i, int i2, int i3, int i4) { int i5 = i2 + ((i4 - i2) / 2); TextView textView = this.W; b(textView, b(textView) + i, (i5 - (this.W.getMeasuredHeight() / 2)) + d(this.W)); @@ -741,7 +738,7 @@ private void k(int i, int i2, int i3, int i4){ b(appCompatImageView, i + b(appCompatImageView), (i5 - (this.a0.getMeasuredHeight() / 2)) + d(this.a0)); } - public void b(CharSequence charSequence, boolean z){ + public void b(CharSequence charSequence, boolean z) { int i = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -755,7 +752,7 @@ public void b(CharSequence charSequence, boolean z){ this.g.a(z ? this.B : this.h); } - private void f(int i, int i2, int i3, int i4){ + private void f(int i, int i2, int i3, int i4) { TextView textView = this.N; b(textView, i + b(textView), d(this.N) + i2); int right = this.N.getRight() + c(this.N); @@ -783,7 +780,7 @@ private void f(int i, int i2, int i3, int i4){ } } - private void l(int i, int i2, int i3, int i4){ + private void l(int i, int i2, int i3, int i4) { TextView textView = this.W; b(textView, i + b(textView), i2); int right = this.W.getRight() + c(this.W); @@ -791,33 +788,33 @@ private void l(int i, int i2, int i3, int i4){ b(appCompatImageView, right + b(appCompatImageView), (((this.W.getBottom() + this.W.getTop()) / 2) - (this.a0.getMeasuredHeight() / 2)) + d(this.a0)); } - private void e(int i, int i2){ + private void e(int i, int i2) { b(this.a0, i, 0, i2, 0); b(this.W, i, g(this.a0), i2, 0); } - public void a(CharSequence charSequence, boolean z){ + public void a(CharSequence charSequence, boolean z) { this.N.setText(charSequence); this.N.setTextColor(z ? getTitleColorHighlight() : this.f15761e); } - private void b(int i, int i2){ + private void b(int i, int i2) { b(this.O, i, 0, i2, 0); b(this.Q, i, 0, i2, 0); b(this.P, i, 0, i2, 0); b(this.R, i, 0, i2, 0); } - public void a(){ + public void a() { a(null, 1); setAttach(null); } - private boolean e(View view){ + private boolean e(View view) { return view.getVisibility() == VISIBLE; } - public void a(CharSequence charSequence, int i, CharSequence charSequence2){ + public void a(CharSequence charSequence, int i, CharSequence charSequence2) { boolean z = false; if (!(charSequence == null || charSequence.length() == 0)) { @@ -840,11 +837,11 @@ public void a(CharSequence charSequence, int i, CharSequence charSequence2){ } } - private void b(int i, int i2, int i3){ + private void b(int i, int i2, int i3) { a(this.N, i, i3, i2, 0); } - private void b(int i, int i2, int i3, int i4){ + private void b(int i, int i2, int i3, int i4) { int g = e(this.V) ? g(this.V) + i : i; TextView textView = this.U; a(textView, g + b(textView), i2 + d(this.U)); @@ -852,7 +849,7 @@ private void b(int i, int i2, int i3, int i4){ b(appCompatImageView, i + b(appCompatImageView), (this.U.getTop() + (this.U.getMeasuredHeight() / 2)) - (this.V.getMeasuredHeight() / 2)); } - private void a(CharSequence charSequence, int i){ + private void a(CharSequence charSequence, int i) { int i2 = VISIBLE; if (charSequence == null || charSequence.length() == 0) { @@ -864,13 +861,13 @@ private void a(CharSequence charSequence, int i){ setBodyLinesCount(i); } - private void b(View view, int i, int i2, int i3, int i4){ + private void b(View view, int i, int i2, int i3, int i4) { if (view.getVisibility() != GONE) { a(view, i, i2, i3, i4); } } - public void a(CharSequence charSequence, ComposingType composingType){ + public void a(CharSequence charSequence, ComposingType composingType) { Drawable drawable = composingType == ComposingType.AUDIO ? this.H : this.G; boolean z = false; drawable.setVisible(!(charSequence == null || charSequence.length() == 0), false); @@ -885,13 +882,13 @@ public void a(CharSequence charSequence, ComposingType composingType){ this.W.setText(charSequence); } - private void b(View view, int i, int i2){ + private void b(View view, int i, int i2) { if (view.getVisibility() == VISIBLE) { a(view, i, i2); } } - private int b(View view){ + private int b(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -901,11 +898,11 @@ private int b(View view){ return 0; } - private int b(int i){ + private int b(int i) { return View.MeasureSpec.makeMeasureSpec(i, MeasureSpec.AT_MOST); } - private int g(View view){ + private int g(View view) { if (view.getVisibility() == GONE) { return 0; } @@ -915,13 +912,13 @@ private int g(View view){ return marginLayoutParams != null ? measuredWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin : measuredWidth; } - private void a(int i, int i2){ + private void a(int i, int i2) { a(this.K, i, 0, i2, 0); b(this.M, i, 0, i2, 0); b(this.L, i, 0, i2, 0); } - private int f(View view){ + private int f(View view) { if (view.getVisibility() == GONE) { return 0; } @@ -934,32 +931,32 @@ private int f(View view){ return measuredHeight; } - private void a(int i, int i2, int i3, int i4){ + private void a(int i, int i2, int i3, int i4) { AvatarView avatarView = this.K; a(avatarView, i + b(avatarView), i2 + d(this.K)); a(this.M, this.K.getLeft() - this.K.getPaddingLeft(), this.K.getTop() - this.K.getPaddingTop()); b(this.L, this.K.getRight() - this.L.getMeasuredWidth(), this.K.getBottom() - this.L.getMeasuredHeight()); } - private void c(int i, int i2){ + private void c(int i, int i2) { b(this.S, i, 0, i2, 0); } - private void c(int i, int i2, int i3, int i4){ + private void c(int i, int i2, int i3, int i4) { d(i, i2, i3, f(this.T) + i2); b(i, i2 + f(this.T), i3, i4); } - private int h(){ + private int h() { return f(this.K); } - private void d(int i, int i2, int i3, int i4){ + private void d(int i, int i2, int i3, int i4) { TextView textView = this.T; a(textView, i + b(textView), i2 + d(this.T)); } - private int c(View view){ + private int c(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -969,7 +966,7 @@ private int c(View view){ return 0; } - private void d(int i, int i2){ + private void d(int i, int i2) { b(this.b0, i, 0, i2, 0); b(this.c0, i, 0, i2, 0); b(this.d0, i, 0, i2, 0); @@ -977,13 +974,13 @@ private void d(int i, int i2){ b(this.f0, i, 0, i2, 0); } - private void a(int i, int i2, int i3){ + private void a(int i, int i2, int i3) { b(this.T, i, i3, i2, 0); b(this.V, i, i3, i2, 0); b(this.U, i, i3 + g(this.V), i2, 0); } - private int d(View view){ + private int d(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { @@ -993,7 +990,7 @@ private int d(View view){ return 0; } - private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6){ + private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, int i6) { ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams(i, i2); marginLayoutParams.leftMargin = i3; marginLayoutParams.topMargin = i4; @@ -1002,23 +999,23 @@ private ViewGroup.MarginLayoutParams a(int i, int i2, int i3, int i4, int i5, in return marginLayoutParams; } - public int a(int i){ + public int a(int i) { return (int) (i * (this.a.densityDpi / 160.0f)); } - public int a(float f2){ + public int a(float f2) { return (int) (f2 * (this.a.densityDpi / 160.0f)); } - private void a(View view, int i, int i2, int i3, int i4){ + private void a(View view, int i, int i2, int i3, int i4) { view.measure(ViewGroup.getChildMeasureSpec(i, i2 + b(view) + c(view), view.getLayoutParams().width), ViewGroup.getChildMeasureSpec(i3, i4 + d(view) + a(view), view.getLayoutParams().height)); } - private void a(View view, int i, int i2){ + private void a(View view, int i, int i2) { view.layout(i, i2, view.getMeasuredWidth() + i, view.getMeasuredHeight() + i2); } - private int a(View view){ + private int a(View view) { ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); if (marginLayoutParams != null) { diff --git a/app/src/main/java/com/vk/medianative/MediaImageEncoder.java b/app/src/main/java/com/vk/medianative/MediaImageEncoder.java new file mode 100644 index 0000000000..e4c263f2f2 --- /dev/null +++ b/app/src/main/java/com/vk/medianative/MediaImageEncoder.java @@ -0,0 +1,93 @@ +package com.vk.medianative; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.os.Build; +import android.util.Log; +import ru.vtosters.hooks.other.Preferences; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class MediaImageEncoder { + private static boolean compressBitmap(Bitmap bitmap, File file, CompressFormat format, int quality) { + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + return bitmap.compress(format, quality, fileOutputStream); + } catch (IOException e) { + Log.e(MediaImageEncoder.class.getSimpleName(), "Bitmap compression error: " + e); + return false; + } + } + + private static boolean compressBitmapJpeg(Bitmap bitmap, File file, int quality) { + return compressBitmap(bitmap, file, CompressFormat.JPEG, quality); + } + + private static boolean compressBitmapLossless(Bitmap bitmap, File file) { + return compressBitmap(bitmap, file, CompressFormat.PNG, 100); + } + + private static boolean compressBitmapNative(Bitmap bitmap, File file, int quality) { + if (MediaNative.isX86() || MediaNative.isAsus() || Build.VERSION.SDK_INT < 24) { + Log.e(MediaImageEncoder.class.getSimpleName(), "JPEG turbo not supported on this device!"); + return compressBitmapJpeg(bitmap, file, quality); + } + + byte[] jpegData = MediaNative.compressBitmapJpeg(bitmap, quality); + + if (jpegData == null) { + return false; + } + + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + fileOutputStream.write(jpegData); + return true; + } catch (IOException e) { + Log.e(MediaImageEncoder.class.getSimpleName(), "JPEG compression error: " + e); + return false; + } + } + + public static boolean encodePicture(Bitmap bitmap, File file, int quality) { + if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) { + switch (Preferences.getPreferences().getString("compression", "jpeg")) { + case "skip" -> { + return compressBitmapLossless(bitmap, file); + } + case "jpeg" -> { + return compressBitmapJpeg(bitmap, file, quality); + } + case "native" -> { + return compressBitmapNative(bitmap, file, quality); + } + } + } + + return false; + } + + public static boolean needToSkipCompression() { + return Preferences.getPreferences().getString("compression", "jpeg").equals("skip"); + } + + public static boolean needToCompress() { + return !needToSkipCompression(); + } + + public static boolean encodeJpeg(Bitmap bitmap, File file) { + return encodePicture(bitmap, file, 90); + } + + public static boolean encodeJpegWithoutCompression(Bitmap bitmap, File file) { + return encodePicture(bitmap, file, 95); + } + + public static boolean encodeJpeg(Bitmap bitmap, File file, int quality) { + if (bitmap != null && !bitmap.isRecycled() && bitmap.getWidth() * bitmap.getHeight() != 0) { + return needToCompress() ? compressBitmapNative(bitmap, file, quality) : compressBitmapJpeg(bitmap, file, quality); + } else { + return false; + } + } +} diff --git a/app/src/main/java/com/vk/medianative/MediaNative.java b/app/src/main/java/com/vk/medianative/MediaNative.java new file mode 100644 index 0000000000..9821748e86 --- /dev/null +++ b/app/src/main/java/com/vk/medianative/MediaNative.java @@ -0,0 +1,502 @@ +package com.vk.medianative; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Process; +import android.os.*; +import android.util.Log; +import androidx.annotation.Keep; +import androidx.annotation.Nullable; +import com.facebook.soloader.SoLoader; + +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.Scanner; + +public final class MediaNative { + private static final String a = NativeMediaEncoder.class.getSimpleName(); + private static final Object f17443d = new Object(); + @SuppressLint("StaticFieldLeak") + @Keep + public static volatile Context context; + private static boolean isMediaLoaded = false; + private static boolean isMediaMasksLoaded = false; + + public static int HSLToIntRGB(float f2, float f3, float f4) { + int[] iArr = new int[3]; + waitForContext(); + nativeHSLToRGB(f2, f3, f4, iArr); + return Color.argb(255, iArr[0], iArr[1], iArr[2]); + } + + public static void HSLToRGB(float f2, float f3, float f4, int[] iArr) { + waitForContext(); + nativeHSLToRGB(f2, f3, f4, iArr); + } + + public static int HSVToIntRGB(float f2, float f3, float f4) { + int[] iArr = new int[3]; + waitForContext(); + nativeHSVToRGB(f2, f3, f4, iArr); + return Color.argb(255, iArr[0], iArr[1], iArr[2]); + } + + public static void HSVToRGB(float f2, float f3, float f4, int[] iArr) { + waitForContext(); + nativeHSVToRGB(f2, f3, f4, iArr); + } + + public static int LabToIntRGB(float f2, float f3, float f4) { + int[] iArr = new int[3]; + waitForContext(); + nativeLabToRGB(f2, f3, f4, iArr); + return Color.argb(255, iArr[0], iArr[1], iArr[2]); + } + + public static void LabToRGB(float f2, float f3, float f4, int[] iArr) { + waitForContext(); + nativeLabToRGB(f2, f3, f4, iArr); + } + + public static void RGBToHSL(int i, int i2, int i3, float[] fArr) { + waitForContext(); + nativeRGBToHSL(i, i2, i3, fArr); + } + + public static void RGBToHSV(int i, int i2, int i3, float[] fArr) { + waitForContext(); + nativeRGBToHSV(i, i2, i3, fArr); + } + + public static void RGBToLab(int i, int i2, int i3, float[] fArr) { + waitForContext(); + nativeRGBToLab(i, i2, i3, fArr); + } + + public static long animationPlayerCreate(String str, int i, int i2, boolean z) { + waitForContext(); + return nativeAnimationPlayerCreate(str, i, i2, z); + } + + public static int animationPlayerDecode(long j, Bitmap bitmap) { + waitForContext(); + return nativeAnimationPlayerDecode(j, bitmap); + } + + public static int animationPlayerGetSize(long j) { + waitForContext(); + return nativeAnimationPlayerGetSize(j); + } + + public static void animationPlayerRelease(long j) { + waitForContext(); + nativeAnimationPlayerRelease(j); + } + + public static boolean animationPlayerSeek(long j, int i) { + waitForContext(); + return nativeAnimationPlayerSeek(j, i); + } + + public static void applyCurveAndSaturationBitmap(Bitmap bitmap, int[] iArr, float f2) { + waitForContext(); + nativeCurveSaturation(bitmap, iArr, f2, Runtime.getRuntime().availableProcessors()); + } + + public static long audioGetTotalPcmDuration() { + waitForContext(); + return nativeAudioGetTotalPcmDuration(); + } + + public static byte[] audioGetWaveform(short[] sArr, int i) { + waitForContext(); + return nativeAudioGetWaveform(sArr, i); + } + + public static int audioIsOpusFile(String str) { + waitForContext(); + return nativeAudioIsOpusFile(str); + } + + public static void audioReadOpusFile(ByteBuffer byteBuffer, int i, int[] iArr) { + waitForContext(); + nativeAudioReadOpusFile(byteBuffer, i, iArr); + } + + public static int audioSeekOpusFile(float f2) { + waitForContext(); + return nativeAudioSeekOpusFile(f2); + } + + public static int audioStartRecord(String str) { // need to hack + waitForContext(); + return nativeAudioStartRecord(str); + } + + public static void audioStopRecord() { // need to hack + waitForContext(); + nativeAudioStopRecord(); + } + + public static int audioWriteFrame(ByteBuffer byteBuffer, int i) { // need to hack + waitForContext(); + return nativeAudioWriteFrame(byteBuffer, i); + } + + private static void waitForContext() { + synchronized (f17443d) { + if (context == null) { + try { + f17443d.wait(); + } catch (InterruptedException e2) { + Log.e(a, "Interrupted while waiting for context", e2); + } + } + } + } + + public static void blurBitmap(Bitmap bitmap, int i) { + waitForContext(); + nativeBlur(bitmap, i); + } + + private static String c() { + return getProcMaps(String.valueOf(Process.myPid())); + } + + public static void cameraProcessorCreate() { + waitForContext(); + nativeProcessorCreate(); + } + + public static boolean cameraProcessorDo(byte[] bArr, long[] jArr) { + waitForContext(); + return nativeProcessorDo(bArr, jArr); + } + + public static int cameraProcessorGetVersion() { + waitForContext(); + return nativeProcessorGetVersion(); + } + + public static boolean cameraProcessorInit(String str, int i, int i2) { + waitForContext(); + return nativeProcessorInit(str, i, i2); + } + + public static boolean cameraProcessorLoad(String str, boolean z) { + waitForContext(); + return nativeProcessorLoad(str, z); + } + + public static void cameraProcessorMouseTap(int i, float f2, float f3) { + waitForContext(); + nativeProcessorMouseTap(i, f2, f3); + } + + public static void cameraProcessorRelease() { + waitForContext(); + nativeProcessorRelease(); + } + + public static void colorCorrectionBitmap(Bitmap bitmap, Bitmap bitmap2, float[] fArr) { + waitForContext(); + nativeColorCorrection(bitmap, bitmap2, fArr, fArr.length, Runtime.getRuntime().availableProcessors()); + } + + public static byte[] compressBitmapJpeg(Bitmap bitmap, int i) { + waitForContext(); + return nativeJpegTurboCompress(bitmap, i); + } + + public static String createAllInOneShader() { + waitForContext(); + return nativeAllInOneShader(); + } + + public static void createCurve(int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4, int[] iArr5, float f2, float f3, float f4, float f5, float f6) { + waitForContext(); + nativeCreateCurve(iArr, iArr2, iArr3, iArr4, iArr5, f2, f3, f4, f5, f6); + } + + public static String createOneInAllShader() { + waitForContext(); + return nativeOneInAllShader(); + } + + private static String d() { + return getProcMaps("self"); + } + + public static String dump() { + return "LD_LIBRARY_PATH:\n" + SoLoader.makeLdLibraryPath() + "\nSELF_MAPS:\n" + d() + "\nPID_MAPS:\n" + c() + "\n"; + } + + private static void e() { + isMediaMasksLoaded = NativeLibLoader.loadLibrary("vkmediamasks"); + } + + public static void enhanceBitmap(Bitmap bitmap, float f2) { + waitForContext(); + nativeEnhance(bitmap, f2, Runtime.getRuntime().availableProcessors()); + } + + private static void loadVKMediaLibs() { + if (isMediaLoaded) { + return; + } + NativeLibLoader.loadLibrary("ffmpeg"); + isMediaLoaded = NativeLibLoader.loadLibrary("vkmedia"); + isMediaLoaded = NativeLibLoader.loadLibrary("vkmediaencoder"); + } + + public static void flipHorizontallyBitmap(Bitmap bitmap) { + waitForContext(); + nativeFlipHorizontally(bitmap); + } + + public static void flipVerticallyBitmap(Bitmap bitmap) { + waitForContext(); + nativeFlipVertically(bitmap); + } + + public static void generateHistogram(Bitmap bitmap, int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4) { + waitForContext(); + nativeHistogram(bitmap, iArr, iArr2, iArr3, iArr4); + } + + public static void init(Context context2) { + synchronized (f17443d) { + context = context2; + SoLoader.init(context2, false); + NativeLibLoader.loadLibrary("gnustl_shared"); + NativeLibLoader.loadLibrary("vkchronicle"); + loadVKMediaLibs(); + f17443d.notifyAll(); + } + } + + public static void intRGBToHSL(int i, float[] fArr) { + waitForContext(); + nativeRGBToHSL(Color.red(i), Color.green(i), Color.blue(i), fArr); + } + + public static void intRGBToHSV(int i, float[] fArr) { + waitForContext(); + nativeRGBToHSV(Color.red(i), Color.green(i), Color.blue(i), fArr); + } + + public static void intRGBToLab(int i, float[] fArr) { + waitForContext(); + nativeRGBToLab(Color.red(i), Color.green(i), Color.blue(i), fArr); + } + + public static boolean isAsus() { + return "asus".equalsIgnoreCase(Build.MANUFACTURER); + } + + public static boolean isMediaSupported() { + if (!isMediaMasksLoaded) { + e(); + } + return isMediaMasksLoaded; + } + + public static boolean isX86() { + try { + String str = Build.SUPPORTED_ABIS[0]; + if (str != null) { + return str.startsWith("x86"); + } + return false; + } catch (Throwable unused) { + return false; + } + } + + public static void lookupBitmap(Bitmap bitmap, Bitmap bitmap2) { + waitForContext(); + nativeLookup(bitmap, bitmap2, Runtime.getRuntime().availableProcessors()); + } + + public static long mediaEncoderCreate(MediaEncoderSettings mediaEncoderSettings) { + waitForContext(); + return nativeCreateEncoder(mediaEncoderSettings); + } + + public static int mediaEncoderDoEncode(long j) { + waitForContext(); + return nativeDoEncode(j); + } + + public static void mediaEncoderDoRelease(long j) { + waitForContext(); + nativeReleaseEncoder(j); + } + + private static native String nativeAllInOneShader(); + + private static native long nativeAnimationPlayerCreate(String str, int i, int i2, boolean z); + + private static native int nativeAnimationPlayerDecode(long j, Bitmap bitmap); + + private static native int nativeAnimationPlayerGetSize(long j); + + private static native void nativeAnimationPlayerRelease(long j); + + private static native boolean nativeAnimationPlayerSeek(long j, int i); + + private static native long nativeAudioGetTotalPcmDuration(); + + private static native byte[] nativeAudioGetWaveform(short[] sArr, int i); + + private static native int nativeAudioIsOpusFile(String str); + + private static native int nativeAudioOpenOpusFile(String str); + + private static native void nativeAudioReadOpusFile(ByteBuffer byteBuffer, int i, int[] iArr); + + private static native int nativeAudioSeekOpusFile(float f2); + + private static native int nativeAudioStartRecord(String str); + + private static native void nativeAudioStopRecord(); + + private static native int nativeAudioWriteFrame(ByteBuffer byteBuffer, int i); + + private static native void nativeBlur(Bitmap bitmap, int i); + + private static native void nativeColorCorrection(Bitmap bitmap, Bitmap bitmap2, float[] fArr, int i, int i2); + + private static native String nativeCreateColorCorrectionShader(float[] fArr, int i, int i2); + + private static native void nativeCreateCurve(int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4, int[] iArr5, float f2, float f3, float f4, float f5, float f6); + + private static native long nativeCreateEncoder(MediaEncoderSettings mediaEncoderSettings); + + private static native void nativeCurveSaturation(Bitmap bitmap, int[] iArr, double d2, int i); + + private static native int nativeDoEncode(long j); + + private static native void nativeEnhance(Bitmap bitmap, double d2, int i); + + private static native void nativeFlipHorizontally(Bitmap bitmap); + + private static native void nativeFlipVertically(Bitmap bitmap); + + private static native void nativeHSLToRGB(float f2, float f3, float f4, int[] iArr); + + private static native void nativeHSVToRGB(float f2, float f3, float f4, int[] iArr); + + private static native void nativeHistogram(Bitmap bitmap, int[] iArr, int[] iArr2, int[] iArr3, int[] iArr4); + + private static native byte[] nativeJpegTurboCompress(Bitmap bitmap, int i); + + private static native void nativeLabToRGB(float f2, float f3, float f4, int[] iArr); + + private static native void nativeLookup(Bitmap bitmap, Bitmap bitmap2, int i); + + private static native String nativeOneInAllShader(); + + private static native void nativePinBitmap(Bitmap bitmap); + + private static native void nativeProcessorCreate(); + + private static native boolean nativeProcessorDo(byte[] bArr, long[] jArr); + + private static native int nativeProcessorGetVersion(); + + private static native boolean nativeProcessorInit(String str, int i, int i2); + + private static native boolean nativeProcessorLoad(String str, boolean z); + + private static native void nativeProcessorMouseTap(int i, float f2, float f3); + + private static native void nativeProcessorRelease(); + + private static native void nativeRGBToHSL(int i, int i2, int i3, float[] fArr); + + private static native void nativeRGBToHSV(int i, int i2, int i3, float[] fArr); + + private static native void nativeRGBToLab(int i, int i2, int i3, float[] fArr); + + private static native void nativeReleaseEncoder(long j); + + private static native void nativeResize(Bitmap bitmap, Bitmap bitmap2, int i); + + public static int openOpusFile(String str) { + waitForContext(); + return nativeAudioOpenOpusFile(str); + } + + public static void pinBitmap(Bitmap bitmap) { + waitForContext(); + nativePinBitmap(bitmap); + } + + public static void resizeBitmap(Bitmap bitmap, Bitmap bitmap2) { + waitForContext(); + nativeResize(bitmap, bitmap2, Runtime.getRuntime().availableProcessors()); + } + + private static String getProcMaps(String str) { + try (InputStream inputStream = Runtime.getRuntime().exec("cat /proc/" + str + "/maps").getInputStream()) { + return new Scanner(inputStream).useDelimiter("\\A").next(); + } catch (Exception e) { + return "empty"; + } + } + + public static class EncoderHandler extends Handler { + private Callback a; + + public EncoderHandler(Looper looper) { + super(looper); + } + + @Keep + public static Object postEventFromNative(Object obj, int i, int i2, int i3) { + EncoderHandler encoderHandler; + if (obj != null && (encoderHandler = (EncoderHandler) ((WeakReference) obj).get()) != null) { + if (i == 2) { + return encoderHandler.a.getLayerBitmap(i2, i3); + } + encoderHandler.sendMessage(encoderHandler.obtainMessage(i, i2, i3, null)); + } + return null; + } + + @Override // android.os.Handler + public void handleMessage(Message message) { + Callback callback = this.a; + if (callback == null) { + return; + } + int i = message.what; + if (i == 0) { + callback.onBytes(message.arg1); + } else if (i != 1) { + String str = MediaNative.a; + Log.e(str, "Unknown message type " + message.what); + } else { + callback.onProgress(message.arg1); + } + } + + public void setCallback(Callback callback) { + this.a = callback; + } + + public interface Callback { + @Nullable + Bitmap getLayerBitmap(int i, int i2); + + void onBytes(int i); + + void onProgress(int i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vk/music/view/x/LyricsHolder.java b/app/src/main/java/com/vk/music/view/x/LyricsHolder.java index 2fa0644c19..3d72f3a4d2 100644 --- a/app/src/main/java/com/vk/music/view/x/LyricsHolder.java +++ b/app/src/main/java/com/vk/music/view/x/LyricsHolder.java @@ -24,7 +24,7 @@ import com.vtosters.lite.ui.MusicErrorViewHelper; import com.vtosters.lite.ui.holder.RecyclerHolder; import ru.vtosters.lite.music.Genius; -import ru.vtosters.lite.utils.Preferences; +import ru.vtosters.hooks.other.Preferences; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java b/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java index 0c8c8b19b4..bfcd7f2392 100644 --- a/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java +++ b/app/src/main/java/com/vk/profile/ui/community/CommunityBorderedImageView.java @@ -6,7 +6,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.util.AttributeSet; - import com.vk.core.ui.themes.Themable; import com.vk.core.ui.themes.VKThemeHelper; import com.vk.core.util.ContextExtKt; @@ -14,7 +13,7 @@ import com.vk.stories.view.BorderedCircleImageView; import com.vtosters.lite.R; -public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable{ +public final class CommunityBorderedImageView extends BorderedCircleImageView implements Themable { private boolean f0; private int g0; private int h0; @@ -23,19 +22,19 @@ public final class CommunityBorderedImageView extends BorderedCircleImageView im private int unread; private int read; - public CommunityBorderedImageView(Context context){ + public CommunityBorderedImageView(Context context) { this(context, null, 0, 6); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0, 4); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i, int i2) { this(context, (i2 & 2) != 0 ? null : attributeSet, (i2 & 4) != 0 ? 0 : i); } - public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i){ + public CommunityBorderedImageView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); this.g0 = VKThemeHelper.d((int) R.attr.accent); this.h0 = ContextExtKt.a(context, (int) R.color.gray_200); @@ -43,44 +42,44 @@ public CommunityBorderedImageView(Context context, AttributeSet attributeSet, in this.j0 = new PorterDuffColorFilter(this.h0, PorterDuff.Mode.SRC_IN); } - public final void c(int unread, int read){ // get unread and read resid + public final void c(int unread, int read) { // get unread and read resid this.unread = unread; this.read = read; this.T = BitmapFactory.decodeResource(getResources(), this.unread); } - public final int getPrimaryColor(){ + public final int getPrimaryColor() { return this.g0; } - public final void setPrimaryColor(int i){ + public final void setPrimaryColor(int i) { this.g0 = i; this.i0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN); } - public final int getWasViewedColor(){ + public final int getWasViewedColor() { return this.h0; } - public final void setWasViewedColor(int i){ + public final void setWasViewedColor(int i) { this.h0 = i; this.j0 = new PorterDuffColorFilter(i, PorterDuff.Mode.SRC_IN); } - public final void o(){ + public final void o() { this.f0 = false; l(); } @Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View - public void onDraw(Canvas canvas){ + public void onDraw(Canvas canvas) { super.onDraw(canvas); if (this.f0) { a(canvas); } } - public final void setStoryContainer(StoriesContainer storiesContainer){ + public final void setStoryContainer(StoriesContainer storiesContainer) { if (storiesContainer.L1()) { this.f0 = true; m(); @@ -101,6 +100,6 @@ public final void setStoryContainer(StoriesContainer storiesContainer){ } @Override // com.vk.core.ui.themes.Themable - public void v(){ + public void v() { } } \ No newline at end of file diff --git a/app/src/main/java/com/vk/sharing/view/TargetImageView.java b/app/src/main/java/com/vk/sharing/view/TargetImageView.java index 7358ce9171..a4f0fa8427 100644 --- a/app/src/main/java/com/vk/sharing/view/TargetImageView.java +++ b/app/src/main/java/com/vk/sharing/view/TargetImageView.java @@ -1,26 +1,13 @@ package com.vk.sharing.view; -import static b.h.z.f.roboto_medium; -import static ru.vtosters.lite.utils.ThemesUtils.getAccentColor; - import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.Typeface; +import android.graphics.*; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.AttributeSet; - import androidx.annotation.UiThread; import androidx.core.content.ContextCompat; - import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.view.DraweeHolder; @@ -31,10 +18,13 @@ import com.vk.navigation.NavigatorKeys; import com.vtosters.lite.R; import com.vtosters.lite.a0; -import ru.vtosters.lite.utils.Preferences; +import ru.vtosters.hooks.other.Preferences; import java.util.Arrays; +import static b.h.z.f.roboto_medium; +import static ru.vtosters.hooks.other.ThemesUtils.getAccentColor; + @UiThread public class TargetImageView extends VKMultiImageView { private static final int I = Screen.a(2); diff --git a/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java b/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java index a2f8301259..3aacdbe507 100644 --- a/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java +++ b/app/src/main/java/com/vk/stickers/details/StickerDetailsAdapter.java @@ -129,23 +129,23 @@ public void a(StickerStockItem stickerStockItem) { 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()); } } diff --git a/app/src/main/java/com/vk/stories/view/StoryCircleImageView.java b/app/src/main/java/com/vk/stories/view/StoryCircleImageView.java index ede58dcb6b..3da50ffcd1 100644 --- a/app/src/main/java/com/vk/stories/view/StoryCircleImageView.java +++ b/app/src/main/java/com/vk/stories/view/StoryCircleImageView.java @@ -11,11 +11,9 @@ import android.net.Uri; import android.util.AttributeSet; import android.view.ViewTreeObserver; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; - import com.airbnb.lottie.LottieComposition; import com.airbnb.lottie.LottieCompositionFactory; import com.airbnb.lottie.LottieDrawable; @@ -47,7 +45,7 @@ import java.util.List; import java.util.Objects; -public class StoryCircleImageView extends BorderedCircleImageView implements Themable{ +public class StoryCircleImageView extends BorderedCircleImageView implements Themable { private static final HashSet B0 = new HashSet<>(); private final PipelineDraweeControllerBuilder f0; private int A0; @@ -72,35 +70,35 @@ public class StoryCircleImageView extends BorderedCircleImageView implements The private String y0; private LottieDrawable z0; - public StoryCircleImageView(Context context){ + public StoryCircleImageView(Context context) { this(context, null); } - public StoryCircleImageView(Context context, AttributeSet attributeSet){ + public StoryCircleImageView(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0); } - public StoryCircleImageView(Context context, AttributeSet attributeSet, int i){ + public StoryCircleImageView(Context context, AttributeSet attributeSet, int i) { super(context, attributeSet, i); this.f0 = FrescoWrapper.c.d(); a(context, attributeSet, i); } - private void b(Canvas canvas){ + private void b(Canvas canvas) { this.z0.draw(canvas); } - private boolean o(){ + private boolean o() { return !this.u0 && !this.t0 && this.z0 == null && !this.w0 && !this.v0; } - private void p(){ + private void p() { this.z0.stop(); this.z0 = null; invalidate(); } - private void q(){ + private void q() { int i = this.j0; if (i != 0 && VKThemeHelper.k.a(i)) { this.k0 = VKThemeHelper.d(this.j0); @@ -132,19 +130,19 @@ private void q(){ this.s0 = DrawableUtils.a(a4, i8, i8); } - private void r(){ + private void r() { this.c0 = Screen.a(o() ? 0 : 2); m(); q(); } - private void setUploadFailed(boolean z){ + private void setUploadFailed(boolean z) { this.u0 = z; invalidate(); } @Override // com.vk.stories.view.ClippedImageView, android.widget.ImageView, android.view.View - public void onDraw(Canvas canvas){ + public void onDraw(Canvas canvas) { super.onDraw(canvas); setBorderAlpha(255); if (this.u0) { @@ -170,13 +168,13 @@ public void onDraw(Canvas canvas){ } @Override // com.vk.stories.view.BorderedCircleImageView, android.view.View - public void onSizeChanged(int i, int i2, int i3, int i4){ + public void onSizeChanged(int i, int i2, int i3, int i4) { super.onSizeChanged(i, i2, i3, i4); this.i0.setBounds(0, 0, i, i2); b(i); } - public void setStoryContainer(@Nullable StoriesContainer storiesContainer){ + public void setStoryContainer(@Nullable StoriesContainer storiesContainer) { boolean z = true; boolean z2 = storiesContainer != null && storiesContainer.Q1(); this.v0 = storiesContainer != null && storiesContainer.S1() && !storiesContainer.L1(); @@ -246,12 +244,12 @@ public void setStoryContainer(@Nullable StoriesContainer storiesContainer){ } @Override // com.vk.core.ui.themes.Themable - public void v(){ + public void v() { r(); invalidate(); } - private void b(int i){ + private void b(int i) { LottieDrawable lottieDrawable = this.z0; if (lottieDrawable == null || lottieDrawable.e() == null) { return; @@ -259,7 +257,7 @@ private void b(int i){ this.z0.d(i / this.z0.e().a().width()); } - protected void a(Context context, AttributeSet attributeSet, int i){ + protected void a(Context context, AttributeSet attributeSet, int i) { TypedArray obtainStyledAttributes = context.obtainStyledAttributes(attributeSet, com.vtosters.lite.a0.StoryCircleImageView, i, 0); a(context, attributeSet, obtainStyledAttributes); obtainStyledAttributes.recycle(); @@ -267,7 +265,7 @@ protected void a(Context context, AttributeSet attributeSet, int i){ this.T = this.p0; } - public void b(@Nullable List list){ + public void b(@Nullable List list) { PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder = this.f0; pipelineDraweeControllerBuilder.j(); pipelineDraweeControllerBuilder.a(getController()); @@ -277,7 +275,7 @@ public void b(@Nullable List list){ } @SuppressLint("ResourceType") - private void a(Context context, AttributeSet attributeSet, TypedArray typedArray){ // bullshit vk code is not working with custom bordered circle image + private void a(Context context, AttributeSet attributeSet, TypedArray typedArray) { // bullshit vk code is not working with custom bordered circle image Drawable drawable; this.A0 = typedArray.getDimensionPixelSize(0, Screen.a(64.0f)); if (typedArray.hasValue(2)) { @@ -344,13 +342,13 @@ private void a(Context context, AttributeSet attributeSet, TypedArray typedArray this.s0 = DrawableUtils.a(drawable4, i4, i4); } - public void a(StoriesContainer storiesContainer){ + public void a(StoriesContainer storiesContainer) { if (this.z0 == null) { LottieCompositionFactory.a(getContext(), this.y0).b(new a(storiesContainer)); } } - public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition){ + public void a(StoriesContainer storiesContainer, LottieComposition lottieComposition) { this.z0 = new LottieDrawable(); this.z0.a(lottieComposition); this.z0.d(-1); @@ -363,7 +361,7 @@ public void a(StoriesContainer storiesContainer, LottieComposition lottieComposi this.z0.stop(); } - private void a(boolean z, boolean z2){ + private void a(boolean z, boolean z2) { k(); this.t0 = z; if (!z2) { @@ -373,7 +371,7 @@ private void a(boolean z, boolean z2){ } } - private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List list){ + private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeControllerBuilder, @Nullable final List list) { pipelineDraweeControllerBuilder.b(null); pipelineDraweeControllerBuilder.a((Supplier>>) null); if (list != null && !list.isEmpty()) { @@ -390,7 +388,7 @@ private void a(@NonNull PipelineDraweeControllerBuilder pipelineDraweeController pipelineDraweeControllerBuilder.b(null); } - public DataSource> a(@Nullable List list){ + public DataSource> a(@Nullable List list) { ArrayList arrayList = new ArrayList<>(); for (String o : Objects.requireNonNull(list)) { arrayList.add(new Image(-1, -1, o)); @@ -398,15 +396,15 @@ public DataSource> a(@Nullable List l return AvatarDataSource.a(arrayList, this.A0, 0); } - public class a implements LottieListener{ + public class a implements LottieListener { final StoriesContainer a; - a(StoriesContainer storiesContainer){ + a(StoriesContainer storiesContainer) { this.a = storiesContainer; } @Override // com.airbnb.lottie.LottieListener - public void a(LottieComposition lottieComposition){ + public void a(LottieComposition lottieComposition) { if (StoryCircleImageView.this.getWidth() > 0) { StoryCircleImageView.this.a(this.a, lottieComposition); } else { @@ -414,15 +412,15 @@ public void a(LottieComposition lottieComposition){ } } - public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener{ + public class ViewTreeObserver$OnGlobalLayoutListenerC0393a implements ViewTreeObserver.OnGlobalLayoutListener { final LottieComposition a; - ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition){ + ViewTreeObserver$OnGlobalLayoutListenerC0393a(LottieComposition lottieComposition) { this.a = lottieComposition; } @Override // android.view.ViewTreeObserver.OnGlobalLayoutListener - public void onGlobalLayout(){ + public void onGlobalLayout() { StoryCircleImageView.this.getViewTreeObserver().removeOnGlobalLayoutListener(this); a aVar = a.this; StoryCircleImageView.this.a(aVar.a, this.a); @@ -430,12 +428,12 @@ public void onGlobalLayout(){ } } - public class b implements ValueAnimator.AnimatorUpdateListener{ - b(){ + public class b implements ValueAnimator.AnimatorUpdateListener { + b() { } @Override // android.animation.ValueAnimator.AnimatorUpdateListener - public void onAnimationUpdate(ValueAnimator valueAnimator){ + public void onAnimationUpdate(ValueAnimator valueAnimator) { StoryCircleImageView.this.invalidate(); } } diff --git a/app/src/main/java/com/vtosters/lite/R.java b/app/src/main/java/com/vtosters/lite/R.java index d39ab7a9ac..ebed0b760f 100644 --- a/app/src/main/java/com/vtosters/lite/R.java +++ b/app/src/main/java/com/vtosters/lite/R.java @@ -222,8 +222,10 @@ public static final class array { public static final int undel_value = 0x7f030075; // 2130903157 public static final int wallpaper_change_dialog = 0x7f030076; // 2130903158 public static final int accent_select_type = 0x7f030077; // 2130903159 - public static final int theme_type_name = 0x7f030078; // 2130903160 - public static final int theme_type_name_checkbox = 0x7f030079; // 2130903161 + public static final int compression = 0x7f030078; // 2130903160 + public static final int compression_value = 0x7f030079; // 2130903161 + public static final int theme_type_name = 0x7f03007a; // 2130903162 + public static final int theme_type_name_checkbox = 0x7f03007b; // 2130903163 } public static final class attr { @@ -4974,28 +4976,29 @@ public static final class drawable { public static final int ic_deepl_logo_icon = 0x7f080bcb; // 2131233739 public static final int ic_google_translate_logo = 0x7f080bcc; // 2131233740 public static final int ic_yandex_translate_icon = 0x7f080bd1; // 2131233745 - public static final int microsoft = 0x7f080bd2; // 2131233746 + public static final int $splash__0 = 0x7f080bd2; // 2131233746 public static final int roundmsg = 0x7f080bd3; // 2131233747 public static final int tgs_entry_background = 0x7f080bd4; // 2131233748 public static final int yandex = 0x7f080bd7; // 2131233751 - public static final int $splash__0 = 0x7f080bd8; // 2131233752 - public static final int $splash__1 = 0x7f080bd9; // 2131233753 - public static final int $splash__10 = 0x7f080bda; // 2131233754 - public static final int $splash__11 = 0x7f080bdb; // 2131233755 - public static final int $splash__12 = 0x7f080bdc; // 2131233756 - public static final int $splash__13 = 0x7f080bdd; // 2131233757 - public static final int $splash__14 = 0x7f080bde; // 2131233758 - public static final int $splash__15 = 0x7f080bdf; // 2131233759 - public static final int $splash__2 = 0x7f080be0; // 2131233760 - public static final int $splash__3 = 0x7f080be1; // 2131233761 - public static final int $splash__4 = 0x7f080be2; // 2131233762 - public static final int $splash__5 = 0x7f080be3; // 2131233763 - public static final int $splash__6 = 0x7f080be4; // 2131233764 - public static final int $splash__7 = 0x7f080be5; // 2131233765 - public static final int $splash__8 = 0x7f080be6; // 2131233766 - public static final int $splash__9 = 0x7f080be7; // 2131233767 + public static final int $splash__1 = 0x7f080bd8; // 2131233752 + public static final int $splash__10 = 0x7f080bd9; // 2131233753 + public static final int $splash__11 = 0x7f080bda; // 2131233754 + public static final int $splash__12 = 0x7f080bdb; // 2131233755 + public static final int $splash__13 = 0x7f080bdc; // 2131233756 + public static final int $splash__14 = 0x7f080bdd; // 2131233757 + public static final int $splash__15 = 0x7f080bde; // 2131233758 + public static final int $splash__2 = 0x7f080bdf; // 2131233759 + public static final int $splash__3 = 0x7f080be0; // 2131233760 + public static final int $splash__4 = 0x7f080be1; // 2131233761 + public static final int $splash__5 = 0x7f080be2; // 2131233762 + public static final int $splash__6 = 0x7f080be3; // 2131233763 + public static final int $splash__7 = 0x7f080be4; // 2131233764 + public static final int $splash__8 = 0x7f080be5; // 2131233765 + public static final int $splash__9 = 0x7f080be6; // 2131233766 public static final int checker_background = 0x7f080be8; // 2131233768 - public static final int splash = 0x7f080be9; // 2131233769 + public static final int custom_thumb_selector = 0x7f080be9; // 2131233769 + public static final int custom_track_selector = 0x7f080bea; // 2131233770 + public static final int splash = 0x7f080bec; // 2131233772 public static final int abc_control_background_material = 0x7f080032; // 2131230770 public static final int ic_action_close = 0x7f080322; // 2131231522 public static final int ic_camera_outline_28_new = 0x7f0803a0; // 2131231648 @@ -6793,6 +6796,9 @@ public static final class drawable { public static final int ic_telegram_24 = 0x7f080bcf; // 2131233743 public static final int ic_telegram_outline_28 = 0x7f080bd0; // 2131233744 public static final int vtlogo = 0x7f080bd6; // 2131233750 + public static final int bing = 0x7f080be7; // 2131233767 + public static final int sauceNAO = 0x7f080beb; // 2131233771 + public static final int tracemoe = 0x7f080bed; // 2131233773 public static final int bg_music_play_button_white = 0x7f08015e; // 2131231070 public static final int bg_poll_bg_thumb = 0x7f080181; // 2131231105 public static final int bg_stickers_suggestions_left = 0x7f0801ad; // 2131231149 @@ -11054,6 +11060,8 @@ public static final int record = 0x7f0a0b42; // 2131364674 public static final int color_name = 0x7f0a1022; // 2131365922 public static final int color_value = 0x7f0a1023; // 2131365923 public static final int color_preview_border = 0x7f0a1024; // 2131365924 + public static final int account_backup_view = 0x7f0a1025; // 2131365925 + public static final int copy_image = 0x7f0a1026; // 2131365926 } public static final class integer { @@ -12993,6 +13001,30 @@ public static final class mipmap { public static final int vt_changeable_foreground = 0x7f0f0044; // 2131689540 public static final int vt_launcher = 0x7f0f0045; // 2131689541 public static final int vt_party_foreground = 0x7f0f0047; // 2131689543 + public static final int ic_launcher_balloon_pink = 0x7f0f0048; // 2131689544 + public static final int ic_launcher_balloon_pink_background = 0x7f0f0049; // 2131689545 + public static final int ic_launcher_balloon_pink_foreground = 0x7f0f004a; // 2131689546 + public static final int ic_launcher_bubble_gum = 0x7f0f004b; // 2131689547 + public static final int ic_launcher_bubble_gum_background = 0x7f0f004c; // 2131689548 + public static final int ic_launcher_bubble_gum_foreground = 0x7f0f004d; // 2131689549 + public static final int ic_launcher_comics = 0x7f0f004e; // 2131689550 + public static final int ic_launcher_comics_background = 0x7f0f004f; // 2131689551 + public static final int ic_launcher_comics_foreground = 0x7f0f0050; // 2131689552 + public static final int ic_launcher_flowers = 0x7f0f0051; // 2131689553 + public static final int ic_launcher_flowers_background = 0x7f0f0052; // 2131689554 + public static final int ic_launcher_flowers_foreground = 0x7f0f0053; // 2131689555 + public static final int ic_launcher_metal_dark = 0x7f0f0054; // 2131689556 + public static final int ic_launcher_metal_dark_background = 0x7f0f0055; // 2131689557 + public static final int ic_launcher_metal_dark_foreground = 0x7f0f0056; // 2131689558 + public static final int ic_launcher_pin_blue = 0x7f0f0057; // 2131689559 + public static final int ic_launcher_pin_blue_background = 0x7f0f0058; // 2131689560 + public static final int ic_launcher_pin_blue_foreground = 0x7f0f0059; // 2131689561 + public static final int ic_launcher_rabbit = 0x7f0f005a; // 2131689562 + public static final int ic_launcher_rabbit_background = 0x7f0f005b; // 2131689563 + public static final int ic_launcher_rabbit_foreground = 0x7f0f005c; // 2131689564 + public static final int ic_launcher_shine = 0x7f0f005d; // 2131689565 + public static final int ic_launcher_shine_background = 0x7f0f005e; // 2131689566 + public static final int ic_launcher_shine_foreground = 0x7f0f005f; // 2131689567 public static final int vt_adaptive_launcher_foneground = 0x7f0f0043; // 2131689539 public static final int vt_launcher_round = 0x7f0f0046; // 2131689542 public static final int ic_logo_facebook_28 = 0x7f0f0003; // 2131689475 @@ -19547,49 +19579,91 @@ public static final class string { public static final int accent_color_summ = 0x7f12183b; // 2131892283 public static final int accent_color_title = 0x7f12183c; // 2131892284 public static final int accents = 0x7f12183d; // 2131892285 - public static final int add_to_ads_stories_whitelist = 0x7f12183e; // 2131892286 - public static final int amoledtheme_summ = 0x7f12183f; // 2131892287 - public static final int amoledtheme_title = 0x7f121840; // 2131892288 - public static final int app_open_by_default_settings = 0x7f121841; // 2131892289 - public static final int audio_deleted_from_cache = 0x7f121842; // 2131892290 - public static final int blockminiapps_summ = 0x7f121843; // 2131892291 - public static final int blockminiapps_title = 0x7f121844; // 2131892292 - public static final int businessDisabler_title = 0x7f121845; // 2131892293 - public static final int by_choice = 0x7f121846; // 2131892294 - public static final int calls_not_available = 0x7f121847; // 2131892295 - public static final int cannot_write = 0x7f121848; // 2131892296 - public static final int change_accent_color = 0x7f121849; // 2131892297 - public static final int copydebuginfo_summ = 0x7f12184a; // 2131892298 - public static final int copydebuginfo_title = 0x7f12184b; // 2131892299 - public static final int copyownlink_summ = 0x7f12184c; // 2131892300 - public static final int copyownlink_title = 0x7f12184d; // 2131892301 - public static final int crash_service_name = 0x7f12184e; // 2131892302 - public static final int current_theme = 0x7f12184f; // 2131892303 - public static final int custom_links_warning = 0x7f121850; // 2131892304 - public static final int data_updated = 0x7f121851; // 2131892305 - public static final int device_info = 0x7f121852; // 2131892306 - public static final int device_info_copied = 0x7f121853; // 2131892307 - public static final int dialogstats = 0x7f121854; // 2131892308 - public static final int downloading_update = 0x7f121855; // 2131892309 - public static final int invalid_pack_link = 0x7f121856; // 2131892310 - public static final int maxquality_summ = 0x7f121857; // 2131892311 - public static final int maxquality_title = 0x7f121858; // 2131892312 - public static final int monettheme_summ = 0x7f121859; // 2131892313 - public static final int monettheme_title = 0x7f12185a; // 2131892314 - public static final int newsfeed_notif_summ = 0x7f12185b; // 2131892315 - public static final int newsfeed_notif_title = 0x7f12185c; // 2131892316 - public static final int open_bot = 0x7f12185d; // 2131892317 - public static final int promotedstickers_title = 0x7f12185e; // 2131892318 - public static final int remove_from_ads_stories_whitelist = 0x7f12185f; // 2131892319 - public static final int select_color = 0x7f121860; // 2131892320 - public static final int select_palette = 0x7f121861; // 2131892321 - public static final int sources = 0x7f121862; // 2131892322 - public static final int updateverifdata_summ = 0x7f121863; // 2131892323 - public static final int updateverifdata_title = 0x7f121864; // 2131892324 - public static final int useOldAppVer_summ = 0x7f121865; // 2131892325 - public static final int useOldAppVer_title = 0x7f121866; // 2131892326 - public static final int vkx_integration_enabled_info = 0x7f121867; // 2131892327 - public static final int writebar_ios_title = 0x7f121868; // 2131892328 + public static final int accounts_saved_by_path = 0x7f12183e; // 2131892286 + public static final int add_to_ads_stories_whitelist = 0x7f12183f; // 2131892287 + public static final int amoledtheme_summ = 0x7f121840; // 2131892288 + public static final int amoledtheme_title = 0x7f121841; // 2131892289 + public static final int appLanguage = 0x7f121842; // 2131892290 + public static final int app_open_by_default_settings = 0x7f121843; // 2131892291 + public static final int applying_accent = 0x7f121844; // 2131892292 + public static final int audio_deleted_from_cache = 0x7f121845; // 2131892293 + public static final int blockminiapps_summ = 0x7f121846; // 2131892294 + public static final int blockminiapps_title = 0x7f121847; // 2131892295 + public static final int businessDisabler_title = 0x7f121848; // 2131892296 + public static final int by_choice = 0x7f121849; // 2131892297 + public static final int calls_not_available = 0x7f12184a; // 2131892298 + public static final int cannot_write = 0x7f12184b; // 2131892299 + public static final int change_accent_color = 0x7f12184c; // 2131892300 + public static final int compression_types = 0x7f12184d; // 2131892301 + public static final int compression_types_selection = 0x7f12184e; // 2131892302 + public static final int copydebuginfo_summ = 0x7f12184f; // 2131892303 + public static final int copydebuginfo_title = 0x7f121850; // 2131892304 + public static final int copyownlink_summ = 0x7f121851; // 2131892305 + public static final int copyownlink_title = 0x7f121852; // 2131892306 + public static final int crash_service_name = 0x7f121853; // 2131892307 + public static final int currentLanguage = 0x7f121854; // 2131892308 + public static final int current_theme = 0x7f121855; // 2131892309 + public static final int custom_links_warning = 0x7f121856; // 2131892310 + public static final int data = 0x7f121857; // 2131892311 + public static final int data_updated = 0x7f121858; // 2131892312 + public static final int datasettings_title = 0x7f121859; // 2131892313 + public static final int device_info = 0x7f12185a; // 2131892314 + public static final int device_info_copied = 0x7f12185b; // 2131892315 + public static final int dialogstats = 0x7f12185c; // 2131892316 + public static final int disable_analytics = 0x7f12185d; // 2131892317 + public static final int disable_analytics_summ = 0x7f12185e; // 2131892318 + public static final int downloading_update = 0x7f12185f; // 2131892319 + public static final int error_applying_accent = 0x7f121860; // 2131892320 + public static final int error_no_text = 0x7f121861; // 2131892321 + public static final int error_saving_file = 0x7f121862; // 2131892322 + public static final int failed_to_get_text = 0x7f121863; // 2131892323 + public static final int invalid_pack_link = 0x7f121864; // 2131892324 + public static final int invalidate_theme_cache_title = 0x7f121865; // 2131892325 + public static final int logColors_title = 0x7f121866; // 2131892326 + public static final int maxquality_summ = 0x7f121867; // 2131892327 + public static final int maxquality_title = 0x7f121868; // 2131892328 + public static final int microgsett = 0x7f121869; // 2131892329 + public static final int monettheme_summ = 0x7f12186a; // 2131892330 + public static final int monettheme_title = 0x7f12186b; // 2131892331 + public static final int newsfeed_notif_summ = 0x7f12186c; // 2131892332 + public static final int newsfeed_notif_title = 0x7f12186d; // 2131892333 + public static final int open_bot = 0x7f12186e; // 2131892334 + public static final int promotedstickers_title = 0x7f12186f; // 2131892335 + public static final int remove_encryption_key = 0x7f121870; // 2131892336 + public static final int remove_from_ads_stories_whitelist = 0x7f121871; // 2131892337 + public static final int reset_accounts = 0x7f121872; // 2131892338 + public static final int reset_all_settings = 0x7f121873; // 2131892339 + public static final int restore_accounts = 0x7f121874; // 2131892340 + public static final int restore_settings = 0x7f121875; // 2131892341 + public static final int save_accounts = 0x7f121876; // 2131892342 + public static final int save_online_data = 0x7f121877; // 2131892343 + public static final int select_color = 0x7f121878; // 2131892344 + public static final int select_palette = 0x7f121879; // 2131892345 + public static final int sources = 0x7f12187a; // 2131892346 + public static final int storiesGroupsAdBlock = 0x7f12187b; // 2131892347 + public static final int storiesGroupsAdBlocksummary = 0x7f12187c; // 2131892348 + public static final int systemtheme = 0x7f12187d; // 2131892349 + public static final int systemtheme_enabled = 0x7f12187e; // 2131892350 + public static final int systemtheme_summ = 0x7f12187f; // 2131892351 + public static final int updateverifdata_summ = 0x7f121880; // 2131892352 + public static final int updateverifdata_title = 0x7f121881; // 2131892353 + public static final int useCustomPrefsStyle_summ = 0x7f121882; // 2131892354 + public static final int useCustomPrefsStyle_title = 0x7f121883; // 2131892355 + public static final int useGenius_summ = 0x7f121884; // 2131892356 + public static final int useGenius_title = 0x7f121885; // 2131892357 + public static final int useNewColorEngine_summ = 0x7f121886; // 2131892358 + public static final int useNewColorEngine_title = 0x7f121887; // 2131892359 + public static final int useOldAppVer_summ = 0x7f121888; // 2131892360 + public static final int useOldAppVer_title = 0x7f121889; // 2131892361 + public static final int vkx_integration_enabled_info = 0x7f12188a; // 2131892362 + public static final int whitelisted_ad_groups_stories_title = 0x7f12188b; // 2131892363 + public static final int writebar_ios_title = 0x7f12188c; // 2131892364 + public static final int cache_size = 0x7f12188d; // 2131892365 + public static final int cache_size_summ = 0x7f12188e; // 2131892366 + public static final int clear_all_cache = 0x7f12188f; // 2131892367 + public static final int invalidate_theme_cache_each_update_title = 0x7f121890; // 2131892368 + public static final int invertFilters = 0x7f121891; // 2131892369 + public static final int invertFilters_summ = 0x7f121892; // 2131892370 } public static final class style { @@ -20786,5 +20860,6 @@ public static final class xml { public static final int preferences_proxy = 0x7f15001c; // 2132082716 public static final int preferences_tgs = 0x7f15001d; // 2132082717 public static final int preferences_themes = 0x7f15001e; // 2132082718 + public static final int locales_config = 0x7f15001f; // 2132082719 } } \ No newline at end of file diff --git a/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java b/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java index 137ac48d5f..3582163d42 100644 --- a/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java +++ b/app/src/main/java/com/vtosters/lite/ui/bottomnavigation/BottomNavigationItemView.java @@ -1,8 +1,5 @@ package com.vtosters.lite.ui.bottomnavigation; -import static ru.vtosters.lite.utils.Preferences.vkme; -import static ru.vtosters.lite.utils.ThemesUtils.getCSTDock; - import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; @@ -18,7 +15,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.view.menu.MenuItemImpl; @@ -26,12 +22,13 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.view.ViewCompat; - import com.vk.core.ui.themes.VKThemeHelper; import com.vtosters.lite.R; - import me.grishka.appkit.utils.V; +import static ru.vtosters.hooks.other.Preferences.vkme; +import static ru.vtosters.hooks.other.ThemesUtils.getCSTDock; + public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView { private final TextView mSmallLabel; private final TextView mLargeLabel; @@ -224,7 +221,7 @@ public void setChecked(boolean checked) { int textsize; - if (vkme()){ + if (vkme()) { textsize = 11; } else { textsize = 10; diff --git a/app/src/main/java/ru/vtosters/hooks/AboutHook.java b/app/src/main/java/ru/vtosters/hooks/AboutHook.java new file mode 100644 index 0000000000..4e739a368c --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/AboutHook.java @@ -0,0 +1,34 @@ +package ru.vtosters.hooks; + +import android.view.View; +import com.vtosters.lite.R; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.VersionReader; + +import static ru.vtosters.hooks.other.Preferences.devmenu; +import static ru.vtosters.hooks.other.Preferences.getBuildName; +import static ru.vtosters.lite.utils.AndroidUtils.getApplicationName; +import static ru.vtosters.lite.utils.AndroidUtils.sendToast; + +public class AboutHook { + public static void inject(View view) { + view.setOnLongClickListener(v -> { + if (devmenu()) { + sendToast(AndroidUtils.getString(R.string.debug_menu_already_activated)); + } else { + Preferences.getPreferences().edit().putBoolean("devmenu", true).apply(); + sendToast(AndroidUtils.getString(R.string.debug_menu_activated)); + } + return true; + }); + } + + public static String getCommitLink() { + return "https://github.com/vtosters/lite/commit/" + VersionReader.getVersionCommit(); + } + + public static String getAppVersion() { + return getApplicationName() + " " + getBuildName() + " | " + VersionReader.getVersionFull(); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/AdBlockHook.java b/app/src/main/java/ru/vtosters/hooks/AdBlockHook.java new file mode 100644 index 0000000000..55cc1fe43a --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/AdBlockHook.java @@ -0,0 +1,21 @@ +package ru.vtosters.hooks; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.utils.NewsFeedFiltersUtils; + +public class AdBlockHook { + public static JSONObject discoverInject(JSONObject json) throws JSONException { + return NewsFeedFiltersUtils.discoverInject(json); + } + + public static JSONArray feedInject(JSONArray items) { + return NewsFeedFiltersUtils.feedInject(items); + + } + + public static JSONObject storiesads(JSONObject json, boolean isDeleteFix) throws JSONException { + return NewsFeedFiltersUtils.storiesads(json, isDeleteFix); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/AppVerHook.java b/app/src/main/java/ru/vtosters/hooks/AppVerHook.java similarity index 75% rename from app/src/main/java/ru/vtosters/lite/hooks/AppVerHook.java rename to app/src/main/java/ru/vtosters/hooks/AppVerHook.java index 0b3678fe73..594d1e9166 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/AppVerHook.java +++ b/app/src/main/java/ru/vtosters/hooks/AppVerHook.java @@ -1,8 +1,8 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import b.h.g.g.BuildInfo; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; public class AppVerHook { public static int appBuild() { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/BusinessNotifHook.java b/app/src/main/java/ru/vtosters/hooks/BusinessNotifHook.java similarity index 61% rename from app/src/main/java/ru/vtosters/lite/hooks/BusinessNotifHook.java rename to app/src/main/java/ru/vtosters/hooks/BusinessNotifHook.java index dad0ca24d8..961050051c 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/BusinessNotifHook.java +++ b/app/src/main/java/ru/vtosters/hooks/BusinessNotifHook.java @@ -1,6 +1,6 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; public class BusinessNotifHook { public static boolean hook(boolean i) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/CallsHook.java b/app/src/main/java/ru/vtosters/hooks/CallsHook.java similarity index 98% rename from app/src/main/java/ru/vtosters/lite/hooks/CallsHook.java rename to app/src/main/java/ru/vtosters/hooks/CallsHook.java index dbb661a506..d7fb8c6ebc 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/CallsHook.java +++ b/app/src/main/java/ru/vtosters/hooks/CallsHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.content.Context; import android.content.Intent; diff --git a/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java b/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java new file mode 100644 index 0000000000..8368e27e4a --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/CatalogsHook.java @@ -0,0 +1,27 @@ +package ru.vtosters.hooks; + +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector; + +public class CatalogsHook { + public static JSONObject injectCatalogGetAudio(JSONObject json) { + try { + return CatalogJsonInjector.music(json); + } catch (JSONException e) { + return json; + } + } + + public static JSONObject injectCatalogGetAudioArtist(JSONObject json) { + try { + return CatalogJsonInjector.fixArtists(json); + } catch (JSONException e) { + return json; + } + } + + public static JSONObject injectCatalogGetSection(JSONObject json) { + return CatalogJsonInjector.injectIntoCatalogs(json); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/CryptImHook.java b/app/src/main/java/ru/vtosters/hooks/CryptImHook.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/hooks/CryptImHook.java rename to app/src/main/java/ru/vtosters/hooks/CryptImHook.java index 343e48464b..38da7d3dc6 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/CryptImHook.java +++ b/app/src/main/java/ru/vtosters/hooks/CryptImHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.app.Activity; import android.content.Context; @@ -17,11 +17,11 @@ import java.util.List; import java.util.Objects; -import static ru.vtosters.lite.dnr.DNRInjector.forceInvalidateDialogActions; +import static ru.vtosters.hooks.DialogHeaderInjector.forceInvalidateDialogActions; +import static ru.vtosters.hooks.other.ThemesUtils.getSTextAttr; import static ru.vtosters.lite.utils.AndroidUtils.dp2px; import static ru.vtosters.lite.utils.AndroidUtils.sendToast; import static ru.vtosters.lite.utils.LifecycleUtils.getCurrentActivity; -import static ru.vtosters.lite.utils.ThemesUtils.getSTextAttr; public class CryptImHook { public static boolean isPrivateProcessor(int peerID) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/DateHook.java b/app/src/main/java/ru/vtosters/hooks/DateHook.java similarity index 94% rename from app/src/main/java/ru/vtosters/lite/hooks/DateHook.java rename to app/src/main/java/ru/vtosters/hooks/DateHook.java index c16ea08fe9..397b5d595a 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/DateHook.java +++ b/app/src/main/java/ru/vtosters/hooks/DateHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.annotation.SuppressLint; import com.vtosters.lite.R; @@ -8,8 +8,8 @@ import java.util.Date; import java.util.Locale; +import static ru.vtosters.hooks.other.Preferences.getString; import static ru.vtosters.lite.utils.AndroidUtils.getStringDate; -import static ru.vtosters.lite.utils.Preferences.getString; public class DateHook { public static boolean fulltime() { diff --git a/app/src/main/java/ru/vtosters/lite/utils/DeletedMessagesHandler.java b/app/src/main/java/ru/vtosters/hooks/DeletedMessagesHook.java similarity index 90% rename from app/src/main/java/ru/vtosters/lite/utils/DeletedMessagesHandler.java rename to app/src/main/java/ru/vtosters/hooks/DeletedMessagesHook.java index 2579896838..7e708c0edc 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/DeletedMessagesHandler.java +++ b/app/src/main/java/ru/vtosters/hooks/DeletedMessagesHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.utils; +package ru.vtosters.hooks; import android.annotation.SuppressLint; import android.content.ContentValues; @@ -12,18 +12,16 @@ import com.vk.libsqliteext.CustomSqliteExtensionsKt; import io.requery.android.database.sqlite.SQLiteDatabase; import io.requery.android.database.sqlite.SQLiteOpenHelper; -import ru.vtosters.lite.encryption.EncryptProvider; import java.util.ArrayList; import java.util.List; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.getString; import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; -import static ru.vtosters.lite.utils.Preferences.getString; -public class DeletedMessagesHandler { +public class DeletedMessagesHook { private static List sDeletedMessagesList = new ArrayList<>(); - private static SQLiteDatabase sVKSQLiteDatabase; private static int sBodyIndex = -1; private static DeletedMessagesDBHelper sVTDatabase; @@ -60,13 +58,13 @@ private static void checkForNestedMsg(List nestedMsgs) { for (NestedMsg nestedMsg : nestedMsgs) { if (!nestedMsg.w0().isEmpty()) checkForNestedMsg(nestedMsg.w0()); - nestedMsg.d(EncryptProvider.decryptMessage(nestedMsg.f(), nestedMsg.getFrom().getId())); + nestedMsg.d(EncryptionMessagesHook.decryptMessage(nestedMsg.f(), nestedMsg.getFrom().getId())); } } private static void editTextOfMsg(MsgFromUser msgFromUser) { if (!msgFromUser.f().startsWith(getPrefixUndelete())) { - msgFromUser.d(getPrefixUndelete() + EncryptProvider.decryptMessage(msgFromUser)); + msgFromUser.d(getPrefixUndelete() + EncryptionMessagesHook.decryptMessage(msgFromUser)); } } @@ -87,7 +85,7 @@ public static void updateDialog(MsgDeleteLpTask msgDeleteLpTask) throws NoSuchFi @SuppressLint("Range") int localId = cursor.getInt(cursor.getColumnIndex("local_id")); - var className = DeletedMessagesHandler.class.getSimpleName(); + var className = DeletedMessagesHook.class.getSimpleName(); var imEnvironment = msgDeleteLpTask.b; var cint = msgDeleteLpTask.c; imEnvironment.a(className, new OnMsgUpdateEvent(className, cint, localId)); diff --git a/app/src/main/java/ru/vtosters/lite/hooks/DeviceInfoHook.java b/app/src/main/java/ru/vtosters/hooks/DeviceInfoHook.java similarity index 93% rename from app/src/main/java/ru/vtosters/lite/hooks/DeviceInfoHook.java rename to app/src/main/java/ru/vtosters/hooks/DeviceInfoHook.java index 5cce4f5332..1aa3c40906 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/DeviceInfoHook.java +++ b/app/src/main/java/ru/vtosters/hooks/DeviceInfoHook.java @@ -1,13 +1,13 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.annotation.SuppressLint; import android.os.Build; import android.provider.Settings; import android.util.Log; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; import static ru.vtosters.lite.utils.AndroidUtils.MD5; import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; public class DeviceInfoHook { public static String getDeviceInfo(String input) { diff --git a/app/src/main/java/ru/vtosters/hooks/DialogHeaderInjector.java b/app/src/main/java/ru/vtosters/hooks/DialogHeaderInjector.java new file mode 100644 index 0000000000..e7b2ec6c19 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/DialogHeaderInjector.java @@ -0,0 +1,138 @@ +package ru.vtosters.hooks; + +import android.annotation.SuppressLint; +import android.os.Environment; +import com.vk.im.engine.ImEngine1; +import com.vk.im.engine.events.OnDialogUpdateEvent; +import com.vk.im.engine.models.EntityIntMap; +import com.vk.im.engine.models.dialogs.Dialog; +import com.vk.im.ui.components.common.DialogAction; +import com.vk.im.ui.views.dialog_actions.DialogActionsListView; +import ru.vtosters.lite.dialogs.Requests; +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.utils.AndroidUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class DialogHeaderInjector { + public static boolean onClick(DialogAction action, Dialog dialog) { // header menu + int peerId; + + try { + peerId = dialog.getId(); + } catch (Exception e) { + return true; + } + + switch (action) { + case MARK_AS_READ -> { + Requests.hookRead(dialog); + forceInvalidateDialogActions(dialog); + } + case DNR_ON, DNR_OFF -> { + MessagesActivityHook.hookDNR(peerId); + forceInvalidateDialogActions(dialog); + return true; + } + case DNT_ON, DNT_OFF -> { + MessagesActivityHook.hookDNT(peerId); + forceInvalidateDialogActions(dialog); + return true; + } + case ENCRYPT_SETT -> { + CryptImHook.hookPref(peerId); + return true; + } + case ENCRYPT -> { + CryptImHook.hook(peerId, dialog); + return true; + } + case DOWNLOAD -> { + File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "dialog-" + peerId + ".html"); + try { + new MessagesDownloader().downloadDialog(peerId, new HtmlDialogDownloaderFormatProvider(), out); + } catch (Exception e) { + AndroidUtils.sendToast(AndroidUtils.getString(com.vtosters.lite.R.string.download_dl_error)); + e.printStackTrace(); + } + return true; + } + case STAT -> { + Requests.hookDialogInfo(dialog); + return true; + } + } + + return false; + } + + public static List injectToListAccess(List actions, Dialog dialog) { + int peerId; + + try { + peerId = dialog.getId(); + } catch (Exception e) { + return actions; + } + + actions.add(DialogAction.STAT); + + actions.add(DialogAction.DOWNLOAD); + + if (MessagesActivityHook.isDnrEnabledFor(peerId)) { + actions.add(DialogAction.DNR_OFF); + } else { + actions.add(DialogAction.DNR_ON); + } + + if (MessagesActivityHook.isDntEnabledFor(peerId)) { + actions.add(DialogAction.DNT_OFF); + } else { + actions.add(DialogAction.DNT_ON); + } + + actions.add(DialogAction.ENCRYPT); + + IMProcessor prov = EncryptProvider.getProcessorFor(peerId); + if (prov != null && !prov.isPublic()) { + actions.add(DialogAction.ENCRYPT_SETT); + } + + actions.add(DialogAction.MARK_AS_READ); + return actions; + } + + @SuppressLint("ResourceType") + public static List injectToList(List 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, com.vtosters.lite.R.attr.im_ic_msgdl, com.vtosters.lite.R.string.download_dl)); // DialogAction, Int, Icon, String + + list.add(new DialogActionsListView.b.a(DialogAction.DNR_ON, 2, com.vtosters.lite.R.attr.im_ic_pinned_msg_hide, com.vtosters.lite.R.string.DNR_ON)); // DialogAction, Int, Icon, String + list.add(new DialogActionsListView.b.a(DialogAction.DNR_OFF, 2, com.vtosters.lite.R.attr.im_ic_pinned_msg_show, com.vtosters.lite.R.string.DNR_OFF)); // DialogAction, Int, Icon, String + + list.add(new DialogActionsListView.b.a(DialogAction.DNT_ON, 2, com.vtosters.lite.R.attr.im_ic_edit_msg, com.vtosters.lite.R.string.DNT_ON)); // DialogAction, Int, Icon, String + list.add(new DialogActionsListView.b.a(DialogAction.DNT_OFF, 2, com.vtosters.lite.R.attr.im_ic_edit_msg, com.vtosters.lite.R.string.DNT_OFF)); // DialogAction, Int, Icon, String + + if (!AndroidUtils.isTablet()) { + list.add(new DialogActionsListView.b.a(DialogAction.ENCRYPT, 3, com.vtosters.lite.R.attr.im_ic_keyboard, com.vtosters.lite.R.string.encryption)); // DialogAction, Int, Icon, String + list.add(new DialogActionsListView.b.a(DialogAction.ENCRYPT_SETT, 3, com.vtosters.lite.R.attr.im_ic_more_vertical, com.vtosters.lite.R.string.encryption_sett)); // DialogAction, Int, Icon, String + } + + list.add(new DialogActionsListView.b.a(DialogAction.MARK_AS_READ, 4, com.vtosters.lite.R.attr.im_ic_done, com.vtosters.lite.R.string.vkim_dialogs_list_option_mark_as_read)); // DialogAction, Int, Icon, String + + return list; + } + + public static void forceInvalidateDialogActions(Dialog d) { + EntityIntMap map = new EntityIntMap<>(); + map.a(d.getId(), d); + ImEngine1.a().a(new OnDialogUpdateEvent(null, map)); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/DialogMenuInjectors.java b/app/src/main/java/ru/vtosters/hooks/DialogMenuInjectors.java new file mode 100644 index 0000000000..04c99c71d9 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/DialogMenuInjectors.java @@ -0,0 +1,115 @@ +package ru.vtosters.hooks; + +import android.os.Environment; +import com.vk.im.engine.models.dialogs.Dialog; +import com.vk.im.ui.components.common.DialogAction; +import com.vtosters.lite.R; +import ru.vtosters.lite.dialogs.Requests; +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 java.io.File; +import java.util.LinkedHashMap; +import java.util.List; + +public class DialogMenuInjectors { + public static void inject(Dialog dialog, List list) { + int peerId; + + try { + peerId = dialog.getId(); + } catch (Exception e) { + return; + } + + list.add(DialogAction.STAT); + list.add(DialogAction.DOWNLOAD); + + list.add(DialogAction.pinmsg); + list.add(DialogAction.unpinmsg); + + list.add(MessagesActivityHook.isDnrEnabledFor(peerId) ? DialogAction.DNR_OFF : DialogAction.DNR_ON); + + list.add(MessagesActivityHook.isDntEnabledFor(peerId) ? DialogAction.DNT_OFF : DialogAction.DNT_ON); + + list.add(DialogAction.ENCRYPT); + + IMProcessor prov = EncryptProvider.getProcessorFor(peerId); + if (prov != null && !prov.isPublic()) + list.add(DialogAction.ENCRYPT_SETT); + } + + public static LinkedHashMap injectToHashMap(LinkedHashMap 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); + hashMap.put(DialogAction.DNR_OFF, R.string.DNR_OFF); + hashMap.put(DialogAction.DNT_ON, R.string.DNT_ON); + hashMap.put(DialogAction.DNT_OFF, R.string.DNT_OFF); + + hashMap.put(DialogAction.ENCRYPT, R.string.encryption); + hashMap.put(DialogAction.ENCRYPT_SETT, R.string.encryption_sett); + +// hashMap.put(DialogAction.pinmsg, getIdentifier("pinmsg", "string")); +// hashMap.put(DialogAction.unpinmsg, getIdentifier("unpinmsg", "string")); + return hashMap; + } + + public static boolean onClick(Dialog dialog, DialogAction action) { // popup menu in dialogs + int peerId; + + try { + peerId = dialog.getId(); + } catch (Exception e) { + return true; + } + + switch (action) { + case MARK_AS_READ -> Requests.hookRead(dialog); + case STAT -> { + Requests.hookDialogInfo(dialog); + return true; + } + case DOWNLOAD -> { + File out = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), peerId + "-dialog.html"); + try { + new MessagesDownloader().downloadDialog(peerId, new HtmlDialogDownloaderFormatProvider(), out); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + case DNR_ON, DNR_OFF -> { + MessagesActivityHook.hookDNR(peerId); + return true; + } + case DNT_ON, DNT_OFF -> { + MessagesActivityHook.hookDNT(peerId); + return true; + } + case pinmsg -> { + Requests.pinnedMsg(peerId, true); + return true; + } + case unpinmsg -> { + Requests.pinnedMsg(peerId, false); + return true; + } + case ENCRYPT_SETT -> { + CryptImHook.hookPref(peerId); + return true; + } + case ENCRYPT -> { + CryptImHook.hook(peerId, dialog); + return true; + } + } + + return false; + } + +} + diff --git a/app/src/main/java/ru/vtosters/hooks/DialogMessageInjector.java b/app/src/main/java/ru/vtosters/hooks/DialogMessageInjector.java new file mode 100644 index 0000000000..19e8ea8f8d --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/DialogMessageInjector.java @@ -0,0 +1,52 @@ +package ru.vtosters.hooks; + +import android.content.Context; +import com.vk.im.engine.models.messages.Msg; +import com.vk.im.engine.models.messages.MsgFromUser; +import com.vk.im.ui.components.common.MsgAction; +import com.vk.im.ui.components.viewcontrollers.popup.DelegateMsg; +import ru.vtosters.lite.dialogs.Requests; +import ru.vtosters.lite.ui.dialogs.Translate; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.util.LinkedHashMap; + +public class DialogMessageInjector { + public static LinkedHashMap injectToHashMap(LinkedHashMap hashMap) { + hashMap.put(MsgAction.KICK, new DelegateMsg.a.a(com.vtosters.lite.R.string.vkim_accessibility_kick_from_chat)); + hashMap.put(MsgAction.TRANSLATE, new DelegateMsg.a.a(com.vtosters.lite.R.string.translator)); + hashMap.put(MsgAction.READTO, new DelegateMsg.a.a(com.vtosters.lite.R.string.readto)); + return hashMap; + } + + public static boolean onClick(Context context, MsgAction action, Msg msg) { // popup menu in chat + var peerId = msg.v1(); + + switch (action) { + case KICK -> { + Requests.hookKick(msg); + return true; + } + case READTO -> { + Requests.hookReadStartMsgTo(msg); + return true; + } + case TRANSLATE -> { + if (msg instanceof MsgFromUser) { + var text = ((MsgFromUser) msg).f(); + var isTextExist = !text.isEmpty() && !text.equals(" "); + + if (isTextExist) { + Translate.showTranslatedText(context, EncryptionMessagesHook.decryptMessage(text, peerId)); + } else { + AndroidUtils.sendToast(context.getString(com.vtosters.lite.R.string.translator_no_text)); + } + + return true; + } + } + } + + return false; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/feature/discover/DiscoverTemplates.java b/app/src/main/java/ru/vtosters/hooks/DiscoverTemplates.java similarity index 91% rename from app/src/main/java/ru/vtosters/lite/feature/discover/DiscoverTemplates.java rename to app/src/main/java/ru/vtosters/hooks/DiscoverTemplates.java index c0c81970f7..3542545901 100644 --- a/app/src/main/java/ru/vtosters/lite/feature/discover/DiscoverTemplates.java +++ b/app/src/main/java/ru/vtosters/hooks/DiscoverTemplates.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.feature.discover; +package ru.vtosters.hooks; import com.vk.dto.discover.DiscoverItem; diff --git a/app/src/main/java/ru/vtosters/lite/hooks/DockBarInjector.java b/app/src/main/java/ru/vtosters/hooks/DockBarInjector.java similarity index 98% rename from app/src/main/java/ru/vtosters/lite/hooks/DockBarInjector.java rename to app/src/main/java/ru/vtosters/hooks/DockBarInjector.java index fed5b38e22..08557a531d 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/DockBarInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/DockBarInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.annotation.SuppressLint; import android.view.Menu; @@ -24,10 +24,10 @@ import java.util.*; import static com.vtosters.lite.R.id.*; +import static ru.vtosters.hooks.other.Preferences.*; +import static ru.vtosters.hooks.other.ThemesUtils.getCSTDock; import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; import static ru.vtosters.lite.utils.AndroidUtils.getResources; -import static ru.vtosters.lite.utils.Preferences.*; -import static ru.vtosters.lite.utils.ThemesUtils.getCSTDock; public class DockBarInjector { private static final DockBarEditorManager sManager = DockBarEditorManager.getInstance(); diff --git a/app/src/main/java/ru/vtosters/hooks/DownloadersHook.java b/app/src/main/java/ru/vtosters/hooks/DownloadersHook.java new file mode 100644 index 0000000000..aeafbe815a --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/DownloadersHook.java @@ -0,0 +1,40 @@ +package ru.vtosters.hooks; + +import android.content.Context; +import com.vk.core.dialogs.bottomsheet.MenuBottomSheetAction; +import com.vk.dto.common.VideoFile; +import com.vk.dto.music.MusicTrack; +import com.vk.dto.stories.model.StoryEntry; +import ru.vtosters.lite.downloaders.AudioDownloader; +import ru.vtosters.lite.downloaders.StoryDownloader; +import ru.vtosters.lite.downloaders.VideoDownloader; +import ru.vtosters.lite.utils.ExternalLinkParser; + +import java.util.ArrayList; + +public class DownloadersHook { + public static boolean onClickVideo(int id, VideoFile video, Context ctx) { + if (id == VideoDownloader.DOWNLOAD_ID) { + VideoDownloader.downloadVideo(video, ctx); + return true; + } else if (id == VideoDownloader.OPEN_EXTERNAL_LINK_ID) { + ExternalLinkParser.parseVideoFile(video, ctx, true); + } + return false; + } + + public static void injectActionVideo(ArrayList list, VideoFile video) { + if (!video.U && !video.I1()) { + VideoDownloader.addAction(list, VideoDownloader.DOWNLOAD_ID, com.vtosters.lite.R.drawable.ic_download_outline_24, com.vtosters.lite.R.string.download, 9); + VideoDownloader.addAction(list, VideoDownloader.OPEN_EXTERNAL_LINK_ID, com.vtosters.lite.R.drawable.ic_link_outline_28, com.vtosters.lite.R.string.interfacevideoext_short, 9); + } + } + + public static Runnable injectActionStory(StoryEntry story) { + return () -> StoryDownloader.downloadStory(story); + } + + public static void injectCacheAudio(MusicTrack mt) { + AudioDownloader.cacheTrack(mt); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/EncryptionMessagesHook.java b/app/src/main/java/ru/vtosters/hooks/EncryptionMessagesHook.java new file mode 100644 index 0000000000..163acc1b08 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/EncryptionMessagesHook.java @@ -0,0 +1,102 @@ +package ru.vtosters.hooks; + +import android.util.Log; +import com.vk.im.engine.models.messages.Msg; +import com.vk.im.engine.models.messages.MsgFromUser; +import com.vk.im.ui.components.msg_list.MsgListComponent; +import ru.vtosters.lite.encryption.EncryptProvider; +import ru.vtosters.lite.encryption.base.IMProcessor; + +import static ru.vtosters.hooks.MessagesHook.injectOwnText; +import static ru.vtosters.hooks.MessagesHook.injectOwnTextAll; + +public class EncryptionMessagesHook { + // For message editing + public static String decryptMessage(MsgFromUser msg, boolean showEmoji) { + var decryptedMessage = decryptMessage(EncryptProvider.getBody(msg), EncryptProvider.getPeerId(msg)); + if (!showEmoji && !decryptedMessage.equals(injectOwnTextAll(EncryptProvider.getBody(msg)))) + decryptedMessage = decryptedMessage.replaceAll("^\uD83D\uDD12 ", ""); + return decryptedMessage; + } + + public static String decryptMessage(String msg, MsgListComponent list) { + var peerid = list.G().getId(); + var decryptedMessage = decryptMessage(msg, peerid); + if (!decryptedMessage.equals(injectOwnTextAll(msg))) + decryptedMessage = decryptedMessage.replaceAll("^\uD83D\uDD12 ", ""); + return decryptedMessage; + } + + public static CharSequence decryptMessage(CharSequence msg, int peerid) { + return decryptMessage(msg.toString(), peerid); + } + + public static CharSequence decryptMessage(Msg msg, CharSequence orig) { + if (msg instanceof MsgFromUser) { + var decrypted = decryptMessage(((MsgFromUser) msg).f(), msg.v1()); + + if (decrypted.contains("\uD83D\uDD12")) { + return decrypted; + } + + } + + return orig; + } + + public static String decryptMessages(String msgBody, int peer, boolean showEmoji) { + 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); + if (!showEmoji && !decryptedMessage.equals(injectOwnTextAll(msgBody))) + decryptedMessage = decryptedMessage.replaceAll("^\uD83D\uDD12 ", ""); + return decryptedMessage; + } + + // This will run through EVERY single processor available. + public static String decryptMessage(MsgFromUser msg) { + return decryptMessage(EncryptProvider.getBody(msg), EncryptProvider.getPeerId(msg)); + } + + public static String decryptMessage(String msgBody, int peer) { + try { + for (IMProcessor processor : EncryptProvider.processors) { + if ((processor.isUsed() || !processor.isPublic()) && processor.isEncrypted(msgBody) && (processor.isPublic() || EncryptProvider.getKeyForProcessor(processor, peer) != null)) + return "\uD83D\uDD12 " + injectOwnTextAll(processor.decode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer))); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return injectOwnTextAll(msgBody); + } + + // This will use only the processor which was chosen in chat menu + public static String encryptMessage(MsgFromUser msg) { + String msgBody = EncryptProvider.getBody(msg); + + Log.d("EncryptProvider", "encryptMessage: msg = " + msg); + Log.d("EncryptProvider", "encryptMessage: body = " + msgBody); + Log.d("EncryptProvider", "encryptMessage: peerId = " + EncryptProvider.getPeerId(msg)); + + for (IMProcessor processor : EncryptProvider.processors) { + int peer = EncryptProvider.getPeerId(msg); + if (processor.isUsedToEncrypt(peer)) { + return injectOwnText(processor.encode(msgBody, EncryptProvider.getKeyForProcessor(processor, peer))); + } + } + + return injectOwnText(msgBody); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/FragAnimationHook.java b/app/src/main/java/ru/vtosters/hooks/FragAnimationHook.java similarity index 84% rename from app/src/main/java/ru/vtosters/lite/hooks/FragAnimationHook.java rename to app/src/main/java/ru/vtosters/hooks/FragAnimationHook.java index eacaeff8b2..8dadd29369 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/FragAnimationHook.java +++ b/app/src/main/java/ru/vtosters/hooks/FragAnimationHook.java @@ -1,9 +1,9 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import androidx.fragment.app.FragmentTransaction; +import static ru.vtosters.hooks.other.Preferences.getString; import static ru.vtosters.lite.ui.components.FragAnimationKit.setAnimations; -import static ru.vtosters.lite.utils.Preferences.getString; public class FragAnimationHook { public static boolean animateOpen(FragmentTransaction transaction) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/GcmHook.java b/app/src/main/java/ru/vtosters/hooks/GcmHook.java similarity index 97% rename from app/src/main/java/ru/vtosters/lite/hooks/GcmHook.java rename to app/src/main/java/ru/vtosters/hooks/GcmHook.java index 35a7280a2d..28c15efc4a 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/GcmHook.java +++ b/app/src/main/java/ru/vtosters/hooks/GcmHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.os.Build; import android.util.Base64; @@ -15,15 +15,12 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; -import static ru.vtosters.lite.utils.Preferences.isValidSignature; +import static ru.vtosters.hooks.other.Preferences.isValidSignature; 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 { @@ -35,15 +32,15 @@ public static String requestTokenV2(String orig) { } public static String requestToken(String orig) { - return "yssp9o9p9pamz5t-nvmq8spgwtin3e0=="; + return isValidSignature() ? "yssp9o9p9pamz5t-nvmq8spgwtin3e0==" : requestToken(); } public static String requestToken() { - String xappide; try { String aid = getRandomAid(); String pub2 = genNewKey(); String sig = getSig(pub2); + String xappide; try { byte[] bytes = MessageDigest.getInstance("SHA1").digest(pair.getPublic().getEncoded()); bytes[0] = (byte) (((bytes[0] & 15) + 112) & 255); @@ -200,7 +197,6 @@ private static String getSig(String pub2) { }).getBytes(StandardCharsets.UTF_8)); return Base64.encodeToString(sign.sign(), 0); } catch (Exception e) { - e.printStackTrace(); return null; } } diff --git a/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java b/app/src/main/java/ru/vtosters/hooks/GmsHook.java similarity index 94% rename from app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java rename to app/src/main/java/ru/vtosters/hooks/GmsHook.java index 89ed416fc8..94499d0dec 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/GmsUtils.java +++ b/app/src/main/java/ru/vtosters/hooks/GmsHook.java @@ -1,16 +1,15 @@ -package ru.vtosters.lite.utils; +package ru.vtosters.hooks; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.content.ComponentName; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Build; import com.google.firebase.iid.FirebaseInstanceIdReceiver; import com.google.firebase.iid.FirebaseInstanceIdReceiver2; import com.vtosters.lite.R; +import ru.vtosters.lite.utils.AndroidUtils; -public class GmsUtils { +public class GmsHook { private static final boolean needToSpoof = !isGmsInstalled() && isFakeGmsInstalled(); public static boolean isGmsInstalled() { diff --git a/app/src/main/java/ru/vtosters/lite/feature/groupslist/GroupsCatalogInjector.java b/app/src/main/java/ru/vtosters/hooks/GroupsCatalogInjector.java similarity index 98% rename from app/src/main/java/ru/vtosters/lite/feature/groupslist/GroupsCatalogInjector.java rename to app/src/main/java/ru/vtosters/hooks/GroupsCatalogInjector.java index 9bbbe1685e..702e2f4c43 100644 --- a/app/src/main/java/ru/vtosters/lite/feature/groupslist/GroupsCatalogInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/GroupsCatalogInjector.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.feature.groupslist; +package ru.vtosters.hooks; import android.util.Log; import org.json.JSONArray; diff --git a/app/src/main/java/ru/vtosters/hooks/JsonInjectors.java b/app/src/main/java/ru/vtosters/hooks/JsonInjectors.java new file mode 100644 index 0000000000..251a00b494 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/JsonInjectors.java @@ -0,0 +1,39 @@ +package ru.vtosters.hooks; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.utils.FriendsCatalogUtils; +import ru.vtosters.lite.utils.NewsFeedFiltersUtils; +import ru.vtosters.lite.utils.OnlineBypass; +import ru.vtosters.lite.utils.SuperAppUtils; + +import java.io.IOException; +import java.text.ParseException; + +public class JsonInjectors { + public static JSONObject menu(JSONObject orig) throws JSONException { + return SuperAppUtils.menu(orig); + } + + public static JSONObject superapp(JSONObject json) throws JSONException { + return SuperAppUtils.superapp(json); + } + + + public static JSONObject setOnlineInfo(JSONObject json) throws JSONException { + return OnlineBypass.setOnlineInfo(json); + } + + public static JSONArray setOnlineInfoUsers(JSONArray profiles) throws JSONException { + return OnlineBypass.setOnlineInfoUsers(profiles); + } + + public static JSONArray newsfeedlist(JSONArray items) throws JSONException { + return NewsFeedFiltersUtils.newsfeedlist(items); + } + + public static JSONObject friends(JSONObject json) throws JSONException, ParseException, IOException { + return FriendsCatalogUtils.inject(json); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/LastFMHook.java b/app/src/main/java/ru/vtosters/hooks/LastFMHook.java new file mode 100644 index 0000000000..3bb245e319 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/LastFMHook.java @@ -0,0 +1,10 @@ +package ru.vtosters.hooks; + +import com.vk.dto.music.MusicTrack; +import ru.vtosters.lite.music.LastFMScrobbler; + +public class LastFMHook { + public static void grabMusicTrack(MusicTrack musictrack) { + LastFMScrobbler.grabMusicTrack(musictrack); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/MainActivityInjector.java b/app/src/main/java/ru/vtosters/hooks/MainActivityInjector.java similarity index 76% rename from app/src/main/java/ru/vtosters/lite/hooks/MainActivityInjector.java rename to app/src/main/java/ru/vtosters/hooks/MainActivityInjector.java index fda5515ca9..54a9026c51 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/MainActivityInjector.java +++ b/app/src/main/java/ru/vtosters/hooks/MainActivityInjector.java @@ -1,32 +1,39 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.app.Activity; import android.content.Intent; import android.os.Build; import b.h.g.k.VKProgressDialog; +import com.aefyr.tsg.g2.TelegramStickersService; import com.vk.core.dialogs.alert.VkAlertDialog; import com.vtosters.lite.R; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.hooks.other.ThemesUtils; +import ru.vtosters.hooks.ui.SystemThemeChangerHook; import ru.vtosters.lite.concurrent.VTExecutors; import ru.vtosters.lite.downloaders.notifications.NotificationChannels; -import ru.vtosters.lite.hooks.ui.SystemThemeChangerHook; 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 ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.LifecycleUtils; +import ru.vtosters.lite.utils.NavigatorUtils; +import ru.vtosters.lite.utils.VTVerifications; +import static ru.vtosters.hooks.other.Preferences.checkupdates; 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; public class MainActivityInjector { public static void inject(Activity activity) { SystemThemeChangerHook.themeOnStart(activity); sendRequest(); UsersList.getUsersList(); + VTVerifications.load(activity); if (checkupdates()) OTADialog.checkUpdates(activity); @@ -40,12 +47,15 @@ public static void inject(Activity activity) { NotificationChannels.createChannels(); } - if (Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && ThemesManager.canApplyCustomAccent() && ThemesUtils.useNewColorEngine()) { + if (Preferences.isNewBuild() + &&!ThemesUtils.isMonetTheme() + &&ThemesManager.canApplyCustomAccent()) + { Preferences.updateBuildNumber(); updateBinsAndTmpArchive(activity); } - VTExecutors.getIoScheduler().a(DeletedMessagesHandler::reloadMessagesList); // ioScheduler + VTExecutors.getIoScheduler().a(DeletedMessagesHook::reloadMessagesList); // ioScheduler if (activity.getIntent().getAction() != null && Intent.ACTION_APPLICATION_PREFERENCES.equals(activity.getIntent().getAction())) { NavigatorUtils.switchToSettings(activity); @@ -56,6 +66,8 @@ public static void inject(Activity activity) { InstallGMS.alert(activity); DisableBattery.alert(activity); // VKIDProtection.alert(activity); + //needs to show selected tgs pack count in settings after cold launch + TelegramStickersService.getInstance(activity); } private static void updateBinsAndTmpArchive(Activity activity) { diff --git a/app/src/main/java/ru/vtosters/hooks/MessagesActivityHook.java b/app/src/main/java/ru/vtosters/hooks/MessagesActivityHook.java new file mode 100644 index 0000000000..b6563279f9 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/MessagesActivityHook.java @@ -0,0 +1,51 @@ +package ru.vtosters.hooks; + +import com.vk.im.engine.commands.messages.SetUserActivityCmd; +import com.vk.im.engine.models.dialogs.Dialog; +import ru.vtosters.lite.dialogs.helpers.DoNotReadDBHelper; +import ru.vtosters.lite.dialogs.helpers.DoNotTypeDBHelper; + +import java.util.List; + +public class MessagesActivityHook { + public static DoNotReadDBHelper mDoNotReadDBHelper = new DoNotReadDBHelper(); + public static DoNotTypeDBHelper mDoNotTypeDBHelper = new DoNotTypeDBHelper(); + + public static boolean isDnrEnabledFor(int id) { + return mDoNotReadDBHelper.isEnabledForPeerId(id); + } + + public static List getDnrEnabled() { + return mDoNotReadDBHelper.get(); + } + + public static List getDntEnabled() { + return mDoNotTypeDBHelper.get(); + } + + public static boolean isDntEnabledFor(int id) { + return mDoNotTypeDBHelper.isEnabledForPeerId(id); + } + + public static boolean isDntEnabledFor(SetUserActivityCmd cmd) { + return isDntEnabledFor(cmd.b); + } + + public static boolean isDnrEnabledFor(Dialog dialog) { + if (dialog == null) return false; + return isDnrEnabledFor(dialog.getId()); + } + + public static boolean isDntEnabledFor(Dialog dialog) { + if (dialog == null) return false; + return isDntEnabledFor(dialog.getId()); + } + + public static void hookDNR(int peerId) { + mDoNotReadDBHelper.setEnabledForPeerId(peerId, !mDoNotReadDBHelper.isEnabledForPeerId(peerId)); + } + + public static void hookDNT(int peerId) { + mDoNotTypeDBHelper.setEnabledForPeerId(peerId, !mDoNotTypeDBHelper.isEnabledForPeerId(peerId)); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/MessagesHook.java b/app/src/main/java/ru/vtosters/hooks/MessagesHook.java similarity index 87% rename from app/src/main/java/ru/vtosters/lite/hooks/MessagesHook.java rename to app/src/main/java/ru/vtosters/hooks/MessagesHook.java index 9fd923a10d..64281f0538 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/MessagesHook.java +++ b/app/src/main/java/ru/vtosters/hooks/MessagesHook.java @@ -1,9 +1,12 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.content.Context; import android.text.TextUtils; import android.view.View; import com.vk.api.internal.MethodCall; +import com.vk.im.ui.providers.audiomsg.ImAudioMsgPlayerProvider; +import com.vk.im.ui.providers.audiomsg.PlayerActionSources; +import com.vtosters.lite.im.ImEngineProvider; import ru.vtosters.lite.translators.BaseTranslator; import ru.vtosters.lite.ui.dialogs.MessageSettings; import ru.vtosters.lite.utils.LifecycleUtils; @@ -11,9 +14,9 @@ import java.util.ArrayList; import java.util.regex.Pattern; +import static ru.vtosters.hooks.other.Preferences.*; 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.*; public class MessagesHook { public static String injectOwnText(String oldText) { @@ -104,4 +107,10 @@ private static int expireTime() { default -> 0; }; } + + public static void reloadMessages() { + ImEngineProvider.b().a(); + ImAudioMsgPlayerProvider.b().e(PlayerActionSources.a); + ImAudioMsgPlayerProvider.b().d(PlayerActionSources.a); + } // Delete and reload msg cache } diff --git a/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java b/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java new file mode 100644 index 0000000000..f2e97b8d4f --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/MusicCacheFilesHook.java @@ -0,0 +1,16 @@ +package ru.vtosters.hooks; + +import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; +import ru.vtosters.lite.music.cache.FileCacheImplementation; + +import java.io.File; + +public class MusicCacheFilesHook { + public static File getTrackFile(String trackId) { + return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3"); + } + + public static boolean isTrackExist(String trackId) { + return CacheDatabaseDelegate.isCached(trackId); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/NewsfeedHook.java b/app/src/main/java/ru/vtosters/hooks/NewsfeedHook.java new file mode 100644 index 0000000000..9f082c81a6 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/NewsfeedHook.java @@ -0,0 +1,99 @@ +package ru.vtosters.hooks; + +import android.content.Context; +import android.os.PowerManager; +import androidx.recyclerview.widget.RecyclerView; +import com.vk.core.preference.Preference; +import com.vk.discover.DiscoverItemDecorator; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import static java.lang.Long.MAX_VALUE; +import static ru.vtosters.hooks.other.Preferences.*; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; + +public class NewsfeedHook { + public static long getUpdateNewsfeed(boolean refresh_timeout) { + if (vkme()) { + return MAX_VALUE; + } + 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 boolean samsungFixRem(RecyclerView recyclerView, DiscoverItemDecorator discoverItemDecorator) { + if (!recyclerView.isComputingLayout() && recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { + recyclerView.removeItemDecoration(discoverItemDecorator); + return true; + } + + return false; + } + + public static boolean samsungFixAdd(RecyclerView recyclerView, DiscoverItemDecorator discoverItemDecorator) { + if (!recyclerView.isComputingLayout() && recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) { + recyclerView.addItemDecoration(discoverItemDecorator); + return true; + } + + return false; + } + + public static List hideElement(ArrayList list) { + if (!getBoolValue("whatsnew", true)) { + list.clear(); + } + return list; + } + + public static String[] feedParams() { + HashSet params = new HashSet<>(); + params.add("post"); + params.add("photo"); + params.add("photo_tag"); + + if (!friendsrecomm()) { + params.add("friends_recomm"); + } + + if (!ads()) { + params.add("app_widget"); + params.add("promo_button"); + } + + if (!authorsrecomm()) { + params.add("authors_rec"); + } + + return params.toArray(new String[0]); + } + + public static void adsParams(HashSet hashSet) { + 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"); + hashSet.add("ads_app_video"); + hashSet.add("ads_post_pretty_cards"); + hashSet.add("ads_post_snippet_video"); + } + + public static boolean isPowerSaveMode() { + var pw = (PowerManager) getGlobalContext().getSystemService(Context.POWER_SERVICE); + +// return !getBoolValue("force_disable_psm", false) && pw.isPowerSaveMode(); + return false; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/OnlineFormatterHook.java b/app/src/main/java/ru/vtosters/hooks/OnlineFormatterHook.java similarity index 88% rename from app/src/main/java/ru/vtosters/lite/hooks/OnlineFormatterHook.java rename to app/src/main/java/ru/vtosters/hooks/OnlineFormatterHook.java index e507a67df9..0fb3891370 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/OnlineFormatterHook.java +++ b/app/src/main/java/ru/vtosters/hooks/OnlineFormatterHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.util.Log; import com.vtosters.lite.R; @@ -10,13 +10,11 @@ import java.io.IOException; import java.text.ParseException; -import static ru.vtosters.lite.hooks.JsonInjectors.setOnlineInfo; -import static ru.vtosters.lite.hooks.JsonInjectors.setOnlineInfoUsers; +import static ru.vtosters.hooks.other.Preferences.*; 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.sendToast; -import static ru.vtosters.lite.utils.Preferences.*; public class OnlineFormatterHook { private static String AppName; @@ -71,6 +69,7 @@ public static String getAppName(int appid) { // thanks to egormetlitsky (vk mp3 case 6614620 -> "Laney"; case 5632485 -> "SpaceVK"; case 6287487 -> "vk.com"; + case 8202606 -> "VK Me Web"; case 4542624 -> "Black VK"; case 3917910 -> "Miranda NG (bridge)"; case 8043814 -> "Quise"; @@ -117,7 +116,7 @@ public static String getOnline(int appid) { } public static JSONObject onlineHook(JSONObject json) throws ParseException, IOException, JSONException { - if (getBoolValue("onlinefix", false)) setOnlineInfo(json); + if (getBoolValue("onlinefix", false)) JsonInjectors.setOnlineInfo(json); return json; } @@ -126,7 +125,7 @@ public static JSONArray onlineHookList(JSONArray jsonArr) throws ParseException, if (!getBoolValue("onlinefix", false)) return jsonArr; try { - setOnlineInfoUsers(jsonArr); + JsonInjectors.setOnlineInfoUsers(jsonArr); } catch (Exception e) { Log.e("onlineHookProfiles", e.getMessage()); } @@ -134,25 +133,27 @@ public static JSONArray onlineHookList(JSONArray jsonArr) throws ParseException, return jsonArr; } - public static JSONObject onlineHookProfiles(JSONObject json) throws ParseException, IOException, JSONException { + public static JSONObject onlineHookProfiles(JSONObject json) { if (!getBoolValue("onlinefix", false)) return json; try { - setOnlineInfoUsers(json.optJSONArray("profiles")); + JsonInjectors.setOnlineInfoUsers(json.optJSONArray("profiles")); } catch (Exception e) { Log.e("onlineHookProfiles", e.getMessage()); } + return json; } - public static JSONObject onlineHookItems(JSONObject json) throws ParseException, IOException, JSONException { + public static JSONObject onlineHookItems(JSONObject json) { if (!getBoolValue("onlinefix", false)) return json; try { - setOnlineInfoUsers(json.optJSONArray("items")); + JsonInjectors.setOnlineInfoUsers(json.optJSONArray("items")); } catch (Exception e) { Log.e("onlineHookItems", e.getMessage()); } + return json; } @@ -160,22 +161,23 @@ public static JSONObject onlineHookRequestsAndRecommendations(JSONObject json) t if (!getBoolValue("onlinefix", false)) return json; try { - setOnlineInfoUsers(json.optJSONObject("read_requests").optJSONArray("items")); + JsonInjectors.setOnlineInfoUsers(json.optJSONObject("read_requests").optJSONArray("items")); } catch (Exception e) { Log.e("onlineHookItems", e.getMessage()); } try { - setOnlineInfoUsers(json.optJSONObject("recommendations").optJSONArray("items")); + JsonInjectors.setOnlineInfoUsers(json.optJSONObject("recommendations").optJSONArray("items")); } catch (Exception e) { Log.e("onlineHookItems", e.getMessage()); } try { - setOnlineInfoUsers(json.optJSONArray("profiles")); + JsonInjectors.setOnlineInfoUsers(json.optJSONArray("profiles")); } catch (Exception e) { Log.e("onlineHookItems", e.getMessage()); } + return json; } } diff --git a/app/src/main/java/ru/vtosters/hooks/PhotoViewer.java b/app/src/main/java/ru/vtosters/hooks/PhotoViewer.java new file mode 100644 index 0000000000..d9d4997fb3 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/PhotoViewer.java @@ -0,0 +1,178 @@ +package ru.vtosters.hooks; + + +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; +import androidx.core.content.FileProvider; +import com.vk.core.dialogs.actionspopup.ActionsPopup; +import com.vk.core.dialogs.alert.VkAlertDialog; +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 okio.Okio; +import ru.vtosters.lite.di.singleton.VtOkHttpClient; +import ru.vtosters.lite.themes.utils.RecolorUtils; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.LifecycleUtils; +import ru.vtosters.lite.utils.SearchEngine; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class PhotoViewer { + static OkHttpClient client = VtOkHttpClient.getInstance(); + + //region INSERTION INTO SMALI + @SuppressLint("UseCompatLoadingForDrawables") + public static void addMenuItems(AttachmentWithMedia attachment, ActionsPopup.b actionPopup, int i, boolean z) { + actionPopup.a( + R.string.copy, + RecolorUtils.fixActionMenuIcons(R.drawable.ic_copy_outline_28), + false, + () -> { + copyImage(attachment); + return null; + } + ).a( + R.string.search_photo_content, + RecolorUtils.fixActionMenuIcons(R.drawable.ic_menu_search_outline_28), + false, + () -> { + searchImageWithUrl(attachment); + return null; + } + ).a( + R.string.copy_photo_url_content, + RecolorUtils.fixActionMenuIcons(R.drawable.ic_copy_outline_28), + false, + () -> { + copyImageUrl(attachment); + return null; + } + ).a( + R.string.open_original_photo_content, + RecolorUtils.fixActionMenuIcons(R.drawable.ic_link_outline_28), + false, + () -> { + openUrl(getImageUrlFromAttachment(attachment)); + return null; + } + ); + } + + @SuppressLint("NonConstantResourceId") + public static boolean interceptClick(AttachmentWithMedia attachment, MenuItem item, View view) { + switch (item.getItemId()) { + case R.id.copy_image -> copyImage(attachment); + case R.id.search_photo -> searchImageWithUrl(attachment); + case R.id.copy_photo_url -> copyImageUrl(attachment); + case R.id.open_original_photo -> openUrl(getImageUrlFromAttachment(attachment)); + } + return true; + } + //endregion + + private static void copyImage(AttachmentWithMedia attachment) { + var req = new Request.a() + .b(getImageUrlFromAttachment(attachment)) + .a(); + client.a(req).a(new Callback() { + @Override + public void a(Call call, IOException e) { + e.printStackTrace(); + } + + @Override + public void a(Call call, Response response) + throws IOException { + var tmpImage = new File(AndroidUtils.getGlobalContext().getExternalCacheDir(), attachment.getId() + ".jpg"); + try (var resp = client.a(req).execute(); + var sink = Okio.a(Okio.b(tmpImage))) { + sink.a(resp.a().f()); + var manager = (ClipboardManager) AndroidUtils.getGlobalContext().getSystemService(Context.CLIPBOARD_SERVICE); + var uri = FileProvider.getUriForFile( + AndroidUtils.getGlobalContext(), + "com.vtosters.lite.common.VKFileProvider", + tmpImage); + var data = ClipData.newUri( + AndroidUtils.getGlobalContext().getContentResolver(), + "image", + uri); + manager.setPrimaryClip(data); + } + } + }); + } + + private static void searchImageWithUrl(AttachmentWithMedia attachment) { + String url = getImageUrlFromAttachment(attachment); + if (TextUtils.isEmpty(url)) return; + int selectedEngine = SearchEngine.getDefaultSearchEngine(); + if (selectedEngine < 0) { + var items = new String[SearchEngine.values().length]; + for (int i = 0; i < SearchEngine.values().length; ++i) + items[i] = SearchEngine.values()[i].mTitle; + new VkAlertDialog.Builder(LifecycleUtils.getCurrentActivity()) + .setItems(items, (di, i) -> openUrl(SearchEngine.values()[i].buildSearchUrl(url))) + .show(); + } else { + // index correction for old versions with numeration "1-x" + if (selectedEngine >= SearchEngine.values().length) SearchEngine.setDefaultSearchEngine(--selectedEngine); + openUrl(SearchEngine.values()[selectedEngine].buildSearchUrl(url)); + } + } + + private static void copyImageUrl(AttachmentWithMedia attachment) { + String url = getImageUrlFromAttachment(attachment); + if (TextUtils.isEmpty(url)) return; + ClipboardManager manager = (ClipboardManager) AndroidUtils.getGlobalContext().getSystemService(Context.CLIPBOARD_SERVICE); + manager.setPrimaryClip(ClipData.newPlainText("vk_photo_url", url)); + Toast.makeText(AndroidUtils.getGlobalContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); + } + + private static void openUrl(String url) { + if (TextUtils.isEmpty(url)) { + Log.d("PhotoViewer", "url is null or empty"); + return; + } + var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + AndroidUtils.getGlobalContext().startActivity(intent); + } + + private static String getImageUrlFromAttachment(AttachmentWithMedia attachment) { + List imageSizes; + + if (attachment instanceof PhotoAttachment) { + imageSizes = ((PhotoAttachment) attachment).D.Q.t1(); + } else if (attachment instanceof DocumentAttachment && ((DocumentAttachment) attachment).J != null) { + imageSizes = ((DocumentAttachment) attachment).J.t1(); + } else { + ToastUtils.a(R.string.photo_get_error); + return ""; + } + + if (imageSizes.isEmpty()) return ""; + + var max = imageSizes.get(0); + for (int i = 1; i < imageSizes.size(); i++) { + var tmp = imageSizes.get(i); + if (max.t1() < tmp.t1()) max = tmp; + } + return max.url; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/PicRoundingHook.java b/app/src/main/java/ru/vtosters/hooks/PicRoundingHook.java similarity index 97% rename from app/src/main/java/ru/vtosters/lite/hooks/PicRoundingHook.java rename to app/src/main/java/ru/vtosters/hooks/PicRoundingHook.java index e1a36c8b03..b3aa24faa4 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/PicRoundingHook.java +++ b/app/src/main/java/ru/vtosters/hooks/PicRoundingHook.java @@ -1,9 +1,9 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.graphics.*; import com.facebook.drawee.generic.RoundingParams; +import ru.vtosters.hooks.other.Preferences; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.Preferences; public class PicRoundingHook { private static final int pref = Preferences.getPreferences().getInt("pic_rounding", 0); diff --git a/app/src/main/java/ru/vtosters/lite/hooks/PollHook.java b/app/src/main/java/ru/vtosters/hooks/PollHook.java similarity index 89% rename from app/src/main/java/ru/vtosters/lite/hooks/PollHook.java rename to app/src/main/java/ru/vtosters/hooks/PollHook.java index 273b3fe521..377ffe0bc9 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/PollHook.java +++ b/app/src/main/java/ru/vtosters/hooks/PollHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.view.View; import android.view.ViewGroup; @@ -6,7 +6,7 @@ import com.vk.dto.polls.PollOption; import com.vk.polls.ui.views.PollOptionView; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; public class PollHook { public static void show(PollOptionView view) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/PostViewHook.java b/app/src/main/java/ru/vtosters/hooks/PostViewHook.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/hooks/PostViewHook.java rename to app/src/main/java/ru/vtosters/hooks/PostViewHook.java index cc54a97ded..42c37643d8 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/PostViewHook.java +++ b/app/src/main/java/ru/vtosters/hooks/PostViewHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.view.View; import android.view.ViewGroup; @@ -6,7 +6,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.vk.newsfeed.holders.BaseFooterHolder; import com.vtosters.lite.R; -import ru.vtosters.lite.utils.Preferences; +import ru.vtosters.hooks.other.Preferences; public class PostViewHook { diff --git a/app/src/main/java/ru/vtosters/hooks/ProfileMenuHook.java b/app/src/main/java/ru/vtosters/hooks/ProfileMenuHook.java new file mode 100644 index 0000000000..ab4a309c11 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/ProfileMenuHook.java @@ -0,0 +1,16 @@ +package ru.vtosters.hooks; + +import com.vk.core.dialogs.actionspopup.ActionsPopup; +import com.vk.profile.ui.components.CommunityFragmentActionsMenuBuilder; +import com.vk.profile.ui.components.ProfileFragmentActionsMenuBuilder; +import ru.vtosters.lite.ui.vkui.MenuBuilder; + +public class ProfileMenuHook { + public static void inject(final CommunityFragmentActionsMenuBuilder mb, final ActionsPopup.b builder) { + MenuBuilder.injectAP(mb, builder); + } + + public static void inject(final ProfileFragmentActionsMenuBuilder mb, final ActionsPopup.b builder) { + MenuBuilder.injectAP(mb, builder); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/PromoStickersHook.java b/app/src/main/java/ru/vtosters/hooks/PromoStickersHook.java similarity index 65% rename from app/src/main/java/ru/vtosters/lite/hooks/PromoStickersHook.java rename to app/src/main/java/ru/vtosters/hooks/PromoStickersHook.java index e7b3522e76..70fd682e00 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/PromoStickersHook.java +++ b/app/src/main/java/ru/vtosters/hooks/PromoStickersHook.java @@ -1,6 +1,6 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; public class PromoStickersHook { public static String hook() { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/ProxyHook.java b/app/src/main/java/ru/vtosters/hooks/ProxyHook.java similarity index 70% rename from app/src/main/java/ru/vtosters/lite/hooks/ProxyHook.java rename to app/src/main/java/ru/vtosters/hooks/ProxyHook.java index 696de6d87b..b0ddc40429 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/ProxyHook.java +++ b/app/src/main/java/ru/vtosters/hooks/ProxyHook.java @@ -1,7 +1,8 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.RadioButton; @@ -10,18 +11,87 @@ import com.vk.auth.ui.VkAuthTextView; import com.vk.core.dialogs.alert.VkAlertDialog; import com.vtosters.lite.R; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.lite.proxy.api.VikaMobile; import ru.vtosters.lite.ui.fragments.DataSettingsFragment; import ru.vtosters.lite.ui.fragments.ProxySettingsFragment; import ru.vtosters.lite.utils.AndroidUtils; import ru.vtosters.lite.utils.NavigatorUtils; -import ru.vtosters.lite.utils.Preferences; +import static ru.vtosters.hooks.other.Preferences.getString; +import static ru.vtosters.hooks.other.ThemesUtils.getTextAttr; import static ru.vtosters.lite.proxy.ProxyUtils.*; import static ru.vtosters.lite.utils.AndroidUtils.dp2px; import static ru.vtosters.lite.utils.LifecycleUtils.restartApplication; -import static ru.vtosters.lite.utils.ThemesUtils.getTextAttr; public class ProxyHook { + public static String linkReplacer(String link) { + var vkapi = "api.vk.com"; + var oauth = "oauth.vk.com"; + var vkstatic = "static.vk.com"; + + var proxyapi = getString("proxyapi"); + var proxyoauth = getString("proxyoauth"); + var proxystatic = getString("proxystatic"); + + if (isVikaProxyEnabled()) { + proxyapi = VikaMobile.getApiHost(); + proxyoauth = VikaMobile.getOauthHost(); + proxystatic = VikaMobile.getStaticHost(); + } + + if (!isAnyProxyEnabled() || link.isEmpty()) { + return link; + } + + if (proxyapi.isEmpty() || proxyoauth.isEmpty() || proxystatic.isEmpty()) { + Log.d("VTLite", "Proxy is not set" + " " + proxyapi + " " + proxyoauth + " " + proxystatic); + return link; + } + + if (link.contains(vkapi)) { + return link.replaceAll(vkapi, proxyapi); + } + + if (link.contains(oauth)) { + return link.replaceAll(oauth, proxyoauth); + } + + if (link.contains(vkstatic)) { + return link.replaceAll(vkstatic, proxystatic); + } + + return link; + } + + public static String staticFix(String str) { + var string = getString("proxystatic"); + + if (isVikaProxyEnabled()) { + string = VikaMobile.getStaticHost(); + } + + if (isAnyProxyEnabled() && !string.isEmpty()) { + return str.replaceAll(string, "static.vk.com"); + } + + return str; + } + + public static String getAwayPhpCom() { + var proxyapi = getString("proxyapi"); + + if (isVikaProxyEnabled()) { + proxyapi = VikaMobile.getApiHost(); + } + + if (isAnyProxyEnabled() && !proxyapi.isEmpty()) { + return proxyapi; + } + + return "m.vk.com"; + } + public static void hookAuth(View v) { VkAuthTextView button = v.findViewById(AndroidUtils.getIdentifier("already_have_account", "id")); button.setText(R.string.proxy_setup); diff --git a/app/src/main/java/ru/vtosters/hooks/RenameHook.java b/app/src/main/java/ru/vtosters/hooks/RenameHook.java new file mode 100644 index 0000000000..0d5133fd66 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/RenameHook.java @@ -0,0 +1,29 @@ +package ru.vtosters.hooks; + +import org.json.JSONException; +import org.json.JSONObject; + +public class RenameHook { + public static void injectIntoJson(JSONObject obj) throws JSONException { +// int i = obj.getInt("id"); +// if (RenameTool.updateRequested) { +// RenameTool.reloadDB(); +// } +// +// Pair user = RenameTool.renamedUsers.get(i); +// if (user == null) return; +// obj.put(RenameTool.COLUMN_FIRSTNAME, user.first).put(RenameTool.COLUMN_LASTNAME, user.second); + } + + public static void injectIntoJsonGroup(JSONObject obj) throws JSONException { +// int i = obj.getInt("id"); +// +// if (RenameTool.updateRequested) { +// RenameTool.reloadDB(); +// } +// +// String user = RenameTool.renamedGroups.get(i); +// if (user == null) return; +// obj.put("name", user); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/ReplyMsgHook.java b/app/src/main/java/ru/vtosters/hooks/ReplyMsgHook.java similarity index 91% rename from app/src/main/java/ru/vtosters/lite/hooks/ReplyMsgHook.java rename to app/src/main/java/ru/vtosters/hooks/ReplyMsgHook.java index bdfd9c2e31..e098eb0d28 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/ReplyMsgHook.java +++ b/app/src/main/java/ru/vtosters/hooks/ReplyMsgHook.java @@ -1,11 +1,11 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import com.vk.im.engine.models.ProfilesSimpleInfo; import com.vk.im.engine.models.messages.MsgFromUser; import com.vk.im.engine.models.messages.NestedMsg; import com.vk.im.ui.views.ReplyView; -import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage; +import static ru.vtosters.hooks.EncryptionMessagesHook.decryptMessage; public class ReplyMsgHook { public static void injectWithDecrypt(ReplyView replyView, MsgFromUser msgFromUser, ProfilesSimpleInfo profilesSimpleInfo, boolean someBool) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/RequestDumper.java b/app/src/main/java/ru/vtosters/hooks/RequestDumper.java similarity index 88% rename from app/src/main/java/ru/vtosters/lite/hooks/RequestDumper.java rename to app/src/main/java/ru/vtosters/hooks/RequestDumper.java index 09fa8ccc13..5ec2b53a5b 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/RequestDumper.java +++ b/app/src/main/java/ru/vtosters/hooks/RequestDumper.java @@ -1,11 +1,11 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.util.Log; import com.vk.api.internal.MethodCall; import java.util.LinkedHashMap; -import static ru.vtosters.lite.utils.Preferences.dev; +import static ru.vtosters.hooks.other.Preferences.dev; public class RequestDumper { public static void addParams(MethodCall.a paramslist, String method, LinkedHashMap params, String apiver) { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/StartFragmentHook.java b/app/src/main/java/ru/vtosters/hooks/StartFragmentHook.java similarity index 97% rename from app/src/main/java/ru/vtosters/lite/hooks/StartFragmentHook.java rename to app/src/main/java/ru/vtosters/hooks/StartFragmentHook.java index 35d160a729..568829f017 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/StartFragmentHook.java +++ b/app/src/main/java/ru/vtosters/hooks/StartFragmentHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import com.vk.apps.AppsFragment; import com.vk.discover.DiscoverFragment; @@ -23,8 +23,8 @@ import com.vtosters.lite.general.fragments.GamesFragment; import com.vtosters.lite.general.fragments.PhotosFragment; +import static ru.vtosters.hooks.other.Preferences.*; import static ru.vtosters.lite.ui.components.DockBarEditorManager.getInstance; -import static ru.vtosters.lite.utils.Preferences.*; public class StartFragmentHook { public static Class getStartFragment() { diff --git a/app/src/main/java/ru/vtosters/lite/hooks/StoriesHook.java b/app/src/main/java/ru/vtosters/hooks/StoriesHook.java similarity index 51% rename from app/src/main/java/ru/vtosters/lite/hooks/StoriesHook.java rename to app/src/main/java/ru/vtosters/hooks/StoriesHook.java index 7a5492b039..0a130c1bd4 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/StoriesHook.java +++ b/app/src/main/java/ru/vtosters/hooks/StoriesHook.java @@ -1,7 +1,6 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; -import static ru.vtosters.lite.utils.Preferences.adsstories; -import static ru.vtosters.lite.utils.Preferences.stories; +import static ru.vtosters.hooks.other.Preferences.*; public class StoriesHook { public static String ads() { @@ -11,4 +10,8 @@ public static String ads() { public static boolean showstories() { return stories(); } + + public static boolean getStoriesRead() { + return getBoolValue("read_s", false); + } } diff --git a/app/src/main/java/ru/vtosters/lite/hooks/SwitchHook.java b/app/src/main/java/ru/vtosters/hooks/SwitchHook.java similarity index 97% rename from app/src/main/java/ru/vtosters/lite/hooks/SwitchHook.java rename to app/src/main/java/ru/vtosters/hooks/SwitchHook.java index bbcd69fbf7..3ebc7c87ee 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/SwitchHook.java +++ b/app/src/main/java/ru/vtosters/hooks/SwitchHook.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import android.content.Context; import android.content.res.ColorStateList; @@ -10,9 +10,9 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.widget.CompoundButtonCompat; import com.vk.core.util.ColorUtils; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.hooks.other.ThemesUtils; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.Preferences; -import ru.vtosters.lite.utils.ThemesUtils; import static com.vtosters.lite.R.color.*; diff --git a/app/src/main/java/ru/vtosters/hooks/TelegramStickersHook.java b/app/src/main/java/ru/vtosters/hooks/TelegramStickersHook.java new file mode 100644 index 0000000000..123b738d8e --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/TelegramStickersHook.java @@ -0,0 +1,87 @@ +package ru.vtosters.hooks; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import com.aefyr.tsg.g2.TelegramStickersPack; +import com.aefyr.tsg.g2.TelegramStickersService; +import com.vk.dto.common.Attachment; +import com.vk.dto.stickers.StickerItem; +import com.vk.dto.stickers.StickerStockItem; +import com.vk.im.engine.models.attaches.Attach; +import com.vtosters.lite.attachments.PendingGraffitiAttachment; +import com.vtosters.lite.im.AppAttachToImAttachConverter; +import com.vtosters.lite.upload.Upload; +import ru.vtosters.lite.tgs.TGRoot; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.util.List; + +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; + +public class TelegramStickersHook { + public static StickerStockItem getPackBySticker(int stickerId) { + if (stickerId < TGRoot.N) return null; + int index = (stickerId - TGRoot.N) / 120; + + TelegramStickersPack p = null; + for (TelegramStickersPack p_ : TelegramStickersService.getInstance(AndroidUtils.getGlobalContext()) + .getActivePacksListReference()) { + if (p_.index == index) { + p = p_; + break; + } + } + if (p == null) return null; + + try { + return TGRoot.toStickerPack(p); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static Attachment processSticker(StickerItem item) { + int id = item.getId(); + if (id < TGRoot.N) return null; + + int index = (id - TGRoot.N) / 120; + int stickerId = (id - TGRoot.N) % 120; + + TelegramStickersPack pack = null; + for (TelegramStickersPack p : TelegramStickersService.getInstance(getGlobalContext()).getActivePacksListReference()) { + if (p.index == index) { + pack = p; + break; + } + } + + String image = pack.getStickerFile(stickerId).getAbsolutePath(); + + Bitmap bm = BitmapFactory.decodeFile(image); + int upid = Upload.a(); + TGRoot.pendingStickers.push(upid); + + Attachment att = new PendingGraffitiAttachment(upid, 0, image, bm.getWidth(), bm.getHeight(), null); + bm.recycle(); + + return att; + } + + public static void injectStickers(List list) { + try { + List packs = TelegramStickersService.getInstance(getGlobalContext()).getActivePacksListReference(); + for (int i = packs.size() - 1; i >= 0; i--) + list.add(0, TGRoot.toStickerPack(packs.get(i))); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Attach modifyStickerIM(int i, StickerItem stickerItem, String str) { + if (stickerItem.getId() < 76820000) { + return AppAttachToImAttachConverter.a.a(i, stickerItem, str); + } + return AppAttachToImAttachConverter.a.a(processSticker(stickerItem)); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/dialogs/ThemeChanging.java b/app/src/main/java/ru/vtosters/hooks/ThemeChangeDrawerHook.java similarity index 87% rename from app/src/main/java/ru/vtosters/lite/ui/dialogs/ThemeChanging.java rename to app/src/main/java/ru/vtosters/hooks/ThemeChangeDrawerHook.java index 1bdc6efcf9..d6142e78d2 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/dialogs/ThemeChanging.java +++ b/app/src/main/java/ru/vtosters/hooks/ThemeChangeDrawerHook.java @@ -1,12 +1,12 @@ -package ru.vtosters.lite.ui.dialogs; +package ru.vtosters.hooks; import android.app.Activity; import com.vk.core.dialogs.alert.VkAlertDialog; import com.vtosters.lite.R; -import ru.vtosters.lite.utils.Preferences; -import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.hooks.other.ThemesUtils; -public class ThemeChanging { +public class ThemeChangeDrawerHook { public static void changeTheme(Activity activity, float[] f) { if (Preferences.systemtheme()) { new VkAlertDialog.Builder(activity) diff --git a/app/src/main/java/ru/vtosters/hooks/TogglesHook.java b/app/src/main/java/ru/vtosters/hooks/TogglesHook.java new file mode 100644 index 0000000000..95d98d8497 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/TogglesHook.java @@ -0,0 +1,35 @@ +package ru.vtosters.hooks; + +import com.vk.medianative.MediaImageEncoder; +import com.vk.toggle.FeatureManager; +import com.vk.toggle.Features; + +import static ru.vtosters.hooks.other.Preferences.*; + +public class TogglesHook { + public static boolean shouldPatch(Features.Type feature) { + return switch (feature) { + case AB_IM_LONGPOLL_MSG_BATCHING, AB_IM_VIEW_POOL, AB_MULTI_GIFTS, AB_NEWS_VIDEO_LAYOUT_TEXT, AB_STICKERS_DISCOVER, AB_GIFTS_FROM_KEYBOARD, FEATURE_FEED_DEFAULT_LIST_ALWAYS, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_DISCOVER_CATEGORIES, FEATURE_FAVE_PERF_IMPROVE, FEATURE_GROUP_ADMIN_MESSAGES, FEATURE_GROUP_MSG_PUSH_PARAM, FEATURE_IMAGE_QUALITY_UPGRADE, FEATURE_IM_AUDIO_MSG_TRANSCRIPT, FEATURE_IM_CASPER_MSGS, FEATURE_IM_DISABLE_FORCE_OPEN_VK_ME, FEATURE_IM_GIF_AUTOPLAY, FEATURE_IM_MR_IN_VKAPP, FEATURE_MEMORIES_ENABLED, FEATURE_MENU_GAMES_CAROUSEL, FEATURE_MILKSHAKE, FEATURE_MILKSHAKE_CHANGE_FEED_BY_TIMEOUT, FEATURE_MILKSHAKE_NEWS_SCROLL_ON_BACK, FEATURE_MILKSHAKE_SWITCH_THEME_ON_TAP, FEATURE_ML_BRANDS, FEATURE_ML_FEATURES, FEATURE_ML_MODELS_LOADING, FEATURE_MONEY_TRANSFERS_VKPAY, FEATURE_MUSIC_ARTIST_CATALOG, FEATURE_MUSIC_NEW_CATALOG, FEATURE_NARRATIVE_SNIPPET_TYPE, FEATURE_NEWS_GAMES_IN_DISCOVER, FEATURE_NEWS_HEADER_SCROLL, FEATURE_NEW_FORMAT_SHOW_WPB, FEATURE_PODCASTS_PAGE, FEATURE_QR_CREATE_QR, FEATURE_QR_DYNAMIC_FRAME, FEATURE_QR_SCAN_FROM_PHOTO, FEATURE_QR_VISION_DECODER, FEATURE_QUEUE_COUNTERS, FEATURE_REEF, FEATURE_ROAMING_AUTOPLAY, FEATURE_SHARE_MSGS_ON_INVITE, FEATURE_STICKERS_BOT_LINK, FEATURE_STICKERS_NEW_CATALOG, FEATURE_STORIES_AVATAR, FEATURE_STORIES_FEED_TYPE, FEATURE_STORIES_SHOW_ALWAYS, FEATURE_STORY_ANSWER_PUBLIC, FEATURE_STORY_APP_STICKER, FEATURE_STORY_ARCHIVE, FEATURE_STORY_ARCHIVE_POST, FEATURE_STORY_BAKGROUND_ALL, FEATURE_STORY_BOX, FEATURE_STORY_CADRE, FEATURE_STORY_CAMERA_TOOLTIP, FEATURE_STORY_EDITOR_GALLERY, FEATURE_STORY_EDITOR_TYPE, FEATURE_STORY_ENDLESS_VIDEO, FEATURE_STORY_FAST_REACTIONS, FEATURE_STORY_GIF, FEATURE_STORY_GROUPED, FEATURE_STORY_HIGH_QUALITY, FEATURE_STORY_MASKS_FRONTAL, FEATURE_STORY_MENTION_ICON, FEATURE_STORY_MODERN_PUBLISH, FEATURE_STORY_MUSIC_EDITOR, FEATURE_STORY_MUSIC_REPLIES, FEATURE_STORY_MUSIC_REPLIES_2, FEATURE_STORY_NEW_FRAME, FEATURE_STORY_ONE_TIME, FEATURE_STORY_PHOTO_SHARING, FEATURE_STORY_PHOTO_STICKER, FEATURE_STORY_POLLS, FEATURE_STORY_POST_REPOST, FEATURE_STORY_PRELOADING, FEATURE_STORY_QUESTION, FEATURE_STORY_QUESTION_STYLE, FEATURE_STORY_RLOTTIE, FEATURE_STORY_RLOTTIE_CACHE, FEATURE_STORY_SAVE_DEFAULT_ON, FEATURE_STORY_SEND_DIALOG_LIST, FEATURE_STORY_STICKER_PACK, FEATURE_STORY_TIME, FEATURE_STORY_VIEWER_CAMERA, FEATURE_STORY_VOICE_ANSWER, FEATURE_STORY_WITH_CONTEXT, FEATURE_SUPERAPP_MENU, FEATURE_TRAFFIC_SAVER, FEATURE_VKPAY_WIDGET, FEATURE_VKUI_INTERNAL_TO_MINI_APPS, FEATURE_VKUI_TOKEN_CACHE, FEATURE_VK_APPS_SEARCH, FEATURE_VOTES_BALANCE, FEATURE_WEBVIEW_TOKEN_ACTIVE, FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED, EXPERIMENT_NEWS_DISABLE_CACHE -> + true; + default -> false; + }; + } + + public static boolean redirect(Features.Type feature) { + return switch (feature) { + case EXPERIMENT_NEWS_DISABLE_CACHE -> !feedcache(); + case FEATURE_SUPERAPP_MENU -> superapp(); + case FEATURE_MILKSHAKE -> milkshake(); + case FEATURE_MILKSHAKE_FORCE_DISABLED, FEATURE_MILKSHAKE_ACTIVATION_DISABLED -> !milkshake(); + case AB_NEWS_VIDEO_LAYOUT_TEXT, FEATURE_COMPACT_REPOST, EXPERIMENT_NEWS_VIDEO_LAYOUT_TEXT -> + postsredesign(); + case FEATURE_IMAGE_QUALITY_UPGRADE -> MediaImageEncoder.needToSkipCompression(); + case FEATURE_MENU_GAMES_CAROUSEL -> miniapps(); + default -> true; + }; + } + + public static FeatureManager.b redirectStrings(Features.Type feature) { + return null; + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/VKUIHook.java b/app/src/main/java/ru/vtosters/hooks/VKUIHook.java new file mode 100644 index 0000000000..5d0c8c2f0a --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/VKUIHook.java @@ -0,0 +1,49 @@ +package ru.vtosters.hooks; + +import android.webkit.WebView; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.hooks.other.ThemesUtils; +import ru.vtosters.lite.themes.ThemesCore; +import ru.vtosters.lite.utils.WebViewColoringUtils; + +import static android.util.Base64.encodeToString; +import static ru.vtosters.hooks.other.Preferences.getBoolValue; + +public class VKUIHook { + public static void inject(WebView webView) { + debugWebView(webView); + applyVKUIStyles(webView); + } + + private static void debugWebView(WebView webView) { + if (Preferences.getBoolValue("__dbg_webview", false)) { + WebView.setWebContentsDebuggingEnabled(true); + webView.evaluateJavascript("if (!window.eruda) {let parent = document.head || document.documentElement; let script = parent.appendChild(document.createElement('script')); script.src = 'https://cdn.jsdelivr.net/npm/eruda'; script.onload = () => eruda.init();}", null); + } + } + + private static void applyVKUIStyles(WebView webView) { + if (getBoolValue("VKUI_INJ", true) && webView != null && webView.getUrl() != null && !webView.getUrl().contains("static.vk.com/memories")) { + if (!WebViewColoringUtils.isLoaded) { + WebViewColoringUtils.load(); + } + loadAndApplyCSS(webView); + } + } + + private static void loadAndApplyCSS(WebView webView) { + StringBuilder sb = new StringBuilder(); + + if (ThemesCore.isCachedAccents()) { + sb.append("\n\n"); + sb.append(WebViewColoringUtils.loadedCSS); + } + + if (ThemesUtils.isAmoledTheme()) { + sb.append("\n\n"); + sb.append(WebViewColoringUtils.loadedCSSAmoled); + } + + WebViewColoringUtils.inject(webView, encodeToString(sb.toString().getBytes(), 2)); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/VerificationsHook.java b/app/src/main/java/ru/vtosters/hooks/VerificationsHook.java new file mode 100644 index 0000000000..79d59ae1d4 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/VerificationsHook.java @@ -0,0 +1,53 @@ +package ru.vtosters.hooks; + +import com.vk.dto.common.VerifyInfo; +import org.json.JSONObject; +import ru.vtosters.lite.utils.VTVerifications; + +import static ru.vtosters.hooks.other.Preferences.getBoolValue; + +public class VerificationsHook { + public static boolean isVerified(int id) { + return VTVerifications.sVerifications.contains(id); + } + + public static boolean vtverif() { + return getBoolValue("VT_Verification", true); + } + + public static boolean isVerified(JSONObject jSONObject) { + if (jSONObject.optInt("verified", 0) == 1) { + return true; + } + + if (!getBoolValue("VT_Verification", true)) { + return false; + } + + return isVerified(VTVerifications.getId(jSONObject)); + } + + public static boolean hasPrometheus(JSONObject jSONObject) { + if (jSONObject.optInt("trending", 0) == 1) { + return true; + } + + if (!getBoolValue("VT_Fire", true)) { + return false; + } + + return VTVerifications.isPrometheus(VTVerifications.getId(jSONObject)); + } + + public static boolean hasDeveloper(JSONObject jSONObject) { + if (!getBoolValue("VT_Dev", true)) { + return false; + } + + return VTVerifications.isDeveloper(VTVerifications.getId(jSONObject)); + } + + public static VerifyInfo VerifyInfo(JSONObject jSONObject) { + return new VerifyInfo(isVerified(jSONObject), hasPrometheus(jSONObject)); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/VideoPlayerHook.java b/app/src/main/java/ru/vtosters/hooks/VideoPlayerHook.java new file mode 100644 index 0000000000..a69da7c459 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/VideoPlayerHook.java @@ -0,0 +1,23 @@ +package ru.vtosters.hooks; + +import android.app.Activity; +import android.content.Context; +import com.vk.dto.common.VideoFile; +import ru.vtosters.lite.utils.ExternalLinkParser; + +import static ru.vtosters.hooks.other.Preferences.isEnableExternalOpening; +import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; + +public class VideoPlayerHook { + public static boolean parseVideoFile(VideoFile file) { + return ExternalLinkParser.parseVideoFile(file, getGlobalContext(), isEnableExternalOpening()); + } + + public static boolean parseVideoFile(VideoFile file, Context context) { + return ExternalLinkParser.parseVideoFile(file, context, isEnableExternalOpening()); + } + + public static boolean parseVideoFile(VideoFile file, Activity activity) { + return ExternalLinkParser.parseVideoFile(file, activity, isEnableExternalOpening()); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/VoiceMessagesHook.java b/app/src/main/java/ru/vtosters/hooks/VoiceMessagesHook.java new file mode 100644 index 0000000000..c74e15a1bc --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/VoiceMessagesHook.java @@ -0,0 +1,9 @@ +package ru.vtosters.hooks; + +import static ru.vtosters.hooks.other.Preferences.getBoolValue; + +public class VoiceMessagesHook { + public static boolean getVoiceListened() { + return getBoolValue("listen_v", false); + } +} diff --git a/app/src/main/java/ru/vtosters/hooks/WallpapersHooks.java b/app/src/main/java/ru/vtosters/hooks/WallpapersHooks.java new file mode 100644 index 0000000000..e82e0fa10c --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/WallpapersHooks.java @@ -0,0 +1,19 @@ +package ru.vtosters.hooks; + +import android.view.View; +import android.widget.ImageView; +import com.vk.im.engine.h; +import ru.vtosters.hooks.other.ThemesUtils; + +import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.getWallpaper; +import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.hasWallpapers; + +public class WallpapersHooks { + public static void setBg(View view) { + if (hasWallpapers()) { + ((ImageView) view).setImageDrawable(getWallpaper()); // set picture to background + } else { + view.setBackgroundColor(ThemesUtils.getColorFromAttr(h.im_bg_chat)); // set default bg color + } + } +} diff --git a/app/src/main/java/ru/vtosters/lite/utils/WebAppUtils.java b/app/src/main/java/ru/vtosters/hooks/WebAppHook.java similarity index 62% rename from app/src/main/java/ru/vtosters/lite/utils/WebAppUtils.java rename to app/src/main/java/ru/vtosters/hooks/WebAppHook.java index 49a88db345..326b7ab82b 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/WebAppUtils.java +++ b/app/src/main/java/ru/vtosters/hooks/WebAppHook.java @@ -1,21 +1,18 @@ -package ru.vtosters.lite.utils; +package ru.vtosters.hooks; import com.vk.api.base.ApiConfig; import com.vk.core.preference.Preference; import com.vk.core.ui.themes.VKThemeHelper; import org.json.JSONException; import org.json.JSONObject; -import ru.vtosters.lite.proxy.api.ApiProxy; - -public class WebAppUtils { +public class WebAppHook { public static JSONObject getWebAppConfig() throws JSONException { - JSONObject jSONObject = new JSONObject() + return new JSONObject() .put("scheme", VKThemeHelper.l().b()) .put("app", "vkclient") .put("app_id", ApiConfig.a) .put("appearance", VKThemeHelper.l().a() ? "light" : "dark") - .put("api_host", ApiProxy.linkReplacer(Preference.a().getString("apiHost", "api.vk.com"))); - return jSONObject; + .put("api_host", ProxyHook.linkReplacer(Preference.a().getString("apiHost", "api.vk.com"))); } } diff --git a/app/src/main/java/ru/vtosters/lite/hooks/WritebarHook.java b/app/src/main/java/ru/vtosters/hooks/WritebarHook.java similarity index 68% rename from app/src/main/java/ru/vtosters/lite/hooks/WritebarHook.java rename to app/src/main/java/ru/vtosters/hooks/WritebarHook.java index 81a06192f7..c6df90111e 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/WritebarHook.java +++ b/app/src/main/java/ru/vtosters/hooks/WritebarHook.java @@ -1,9 +1,9 @@ -package ru.vtosters.lite.hooks; +package ru.vtosters.hooks; import com.vtosters.lite.R; -import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.hooks.other.ThemesUtils; -import static ru.vtosters.lite.utils.Preferences.wbios; +import static ru.vtosters.hooks.other.Preferences.wbios; public class WritebarHook { public static int getWriteBar() { diff --git a/app/src/main/java/ru/vtosters/lite/utils/Preferences.java b/app/src/main/java/ru/vtosters/hooks/other/Preferences.java similarity index 87% rename from app/src/main/java/ru/vtosters/lite/utils/Preferences.java rename to app/src/main/java/ru/vtosters/hooks/other/Preferences.java index 66d7d0608a..45dec79b2f 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/Preferences.java +++ b/app/src/main/java/ru/vtosters/hooks/other/Preferences.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.utils; +package ru.vtosters.hooks.other; import android.app.Application; import android.content.Context; @@ -6,11 +6,16 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.StrictMode; +import com.vk.medianative.MediaImageEncoder; +import com.vk.medianative.MediaNative; import com.vtosters.lite.data.Users; import com.vtosters.lite.fragments.SettingsListFragment; +import ru.vtosters.hooks.GmsHook; +import ru.vtosters.hooks.VerificationsHook; import ru.vtosters.lite.BuildConfig; import ru.vtosters.lite.proxy.ProxyUtils; import ru.vtosters.lite.ui.fragments.VTSettings; +import ru.vtosters.lite.utils.*; import java.security.NoSuchAlgorithmException; @@ -18,9 +23,11 @@ public class Preferences { public static void init(Application application) throws Exception { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); - StrictMode.setThreadPolicy(policy); + StrictMode.setThreadPolicy(policy); // fix profiles hide hook - GmsUtils.fixGapps(); + MediaNative.init(application); + + GmsHook.fixGapps(); ProxyUtils.setProxy(); NewsFeedFiltersUtils.setupFilters(); VTVerifications.load(application); @@ -159,7 +166,7 @@ public static boolean feedcache() { } public static boolean superapp() { - return getBoolValue("superapp", true); + return getBoolValue("superapp", true) && milkshake(); } public static boolean vkpay() { @@ -256,7 +263,7 @@ public static boolean ssl() { } public static boolean stories() { - return getBoolValue("stories", true); + return getBoolValue("stories", false); } public static boolean swipe() { @@ -285,12 +292,16 @@ public static boolean checkupdates() { } public static boolean isNewBuild() { - try { - return getPreferences().getLong("setupTime", 0L) != AndroidUtils.getGlobalContext().getPackageManager().getPackageInfo(AndroidUtils.getPackageName(), 0).lastUpdateTime; - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return false; - } + if(Preferences.getBoolValue("invalidate_theme_cache_each_update",true)) + try { + return AndroidUtils.getGlobalContext() + .getPackageManager() + .getPackageInfo(AndroidUtils.getPackageName(), 0) + .lastUpdateTime != getPreferences().getLong("setupTime", 0L); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return false; } public static void updateBuildNumber() { @@ -311,7 +322,7 @@ public static boolean isValidSignature() { } public static boolean hasVerification() { - return VTVerifications.isVerified(AccountManagerUtils.getUserId()); + return VerificationsHook.isVerified(AccountManagerUtils.getUserId()); } public static boolean hasSpecialVerif() { @@ -323,7 +334,7 @@ public static boolean isLikesOnRightEnabled() { } public static long getSizeForDelete() { - return switch (Preferences.getPreferences().getString("clearcache", "")) { + return switch (Preferences.getPreferences().getString("autoclearcache", "")) { case "100mb" -> 104857600L; case "500mb" -> 524288000L; case "1gb" -> 1073741824L; @@ -334,9 +345,7 @@ public static long getSizeForDelete() { } public static int compress(int origquality) { - if (!getBoolValue("compressPhotos", true)) { - return 100; - } + if (MediaImageEncoder.needToSkipCompression()) return 100; return origquality; } } diff --git a/app/src/main/java/ru/vtosters/lite/utils/ThemesUtils.java b/app/src/main/java/ru/vtosters/hooks/other/ThemesUtils.java similarity index 89% rename from app/src/main/java/ru/vtosters/lite/utils/ThemesUtils.java rename to app/src/main/java/ru/vtosters/hooks/other/ThemesUtils.java index 44b10782ef..184e709b42 100644 --- a/app/src/main/java/ru/vtosters/lite/utils/ThemesUtils.java +++ b/app/src/main/java/ru/vtosters/hooks/other/ThemesUtils.java @@ -1,4 +1,4 @@ -package ru.vtosters.lite.utils; +package ru.vtosters.hooks.other; import android.annotation.SuppressLint; import android.app.Activity; @@ -16,26 +16,27 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; -import androidx.appcompat.app.AppCompatDelegate; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import com.vk.articles.preload.WebCachePreloader; import com.vk.core.drawable.RecoloredDrawable; -import com.vk.core.preference.Preference; import com.vk.core.ui.themes.MilkshakeHelper; import com.vk.core.ui.themes.VKTheme; import com.vk.core.ui.themes.VKThemeHelper; import com.vtosters.lite.R; import com.vtosters.lite.data.ThemeTracker; import ru.vtosters.lite.deviceinfo.OEMDetector; -import ru.vtosters.lite.hooks.VKUIHook; +import ru.vtosters.lite.themes.ThemesCore; import ru.vtosters.lite.themes.ThemesHacks; +import ru.vtosters.lite.themes.ThemesManager; import ru.vtosters.lite.ui.wallpapers.WallpapersHooks; +import ru.vtosters.lite.utils.LifecycleUtils; +import ru.vtosters.lite.utils.WebViewColoringUtils; import java.lang.reflect.Field; +import static ru.vtosters.hooks.other.Preferences.*; import static ru.vtosters.lite.utils.AndroidUtils.*; -import static ru.vtosters.lite.utils.Preferences.*; public class ThemesUtils { public static void applyTheme(VKTheme theme, Boolean restartActivity) { @@ -46,22 +47,10 @@ public static void applyTheme(VKTheme theme, Boolean restartActivity) { } } // Apply VKTheme and ImTheme (hard applying without dynamic theme changing) - public static boolean isCustomAccentEnabled() { - return ThemesUtils.isMonetTheme() || !useNewColorEngine(); - } - public static ColorStateList getAccenedColorStateList() { return ColorStateList.valueOf(getAccentColor()); } - public static int getSystemModeTheme() { - return AppCompatDelegate.getDefaultNightMode(); - } - - public static void setSystemModeTheme(int theme) { - AppCompatDelegate.setDefaultNightMode(theme); - } - public static void setTheme(VKTheme theme, Activity activity, Boolean restartActivity) { setThemeFL(theme, activity, getCenterScreenCoords(), restartActivity); } // apply changed theme @@ -71,9 +60,10 @@ public static void setThemeFL(VKTheme theme, Activity activity, float[] fl, Bool activity = LifecycleUtils.getCurrentActivity(); } VKThemeHelper.theme(theme, activity, fl); + if (isMonetTheme() || ThemesManager.canApplyCustomAccent()) ThemesCore.clear(); if (restartActivity) activity.recreate(); ThemeTracker.a(); - VKUIHook.isLoaded = false; + WebViewColoringUtils.isLoaded = false; new WebView(activity).clearCache(true); WebCachePreloader.e(); } @@ -90,13 +80,8 @@ public static boolean isAmoledTheme() { return getBoolValue("amoledtheme", false); } - public static boolean useNewColorEngine() { - return getBoolValue("useNewColorEngine", false); - } - public static int getAccentColor() { - var color = Preferences.getPreferences().getInt("accent_color", getColorFromAttr(R.attr.accent)); - return color == 0 || useNewColorEngine() || isMonetTheme() ? getColorFromAttr(R.attr.accent) : color; + return getColorFromAttr(R.attr.accent); } // Color accent //region Used for migrating accent color to new version @@ -260,14 +245,6 @@ public static int getNeededColorNavbar() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? isDarkTheme() ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0 : 0; } - public static int getStockAccent() { - if (isMilkshake()) { - return isDarkTheme() ? Color.parseColor("#71aaeb") : Color.parseColor("#3f8ae0"); - } else { - return isDarkTheme() ? Color.parseColor("#71aaeb") : Color.parseColor("#528bcc"); - } - } - public static int getDarkThemeRes() { if (isMonetTheme()) { if (isAmoledTheme()) { @@ -322,7 +299,7 @@ public static Drawable recolorDrawable(Drawable drawable) { } // Recolor drawable to accent color public static Drawable recolorToolbarDrawable(Drawable drawable) { - if (!ThemesUtils.isCustomAccentEnabled()) return drawable; + if (!ThemesUtils.isMonetTheme()) return drawable; if (drawable == null) return null; return new RecoloredDrawable(drawable, (ThemesUtils.isMilkshake() && !ThemesUtils.isDarkTheme()) ? ThemesUtils.getAccentColor() : ThemesUtils.getHeaderText()); } @@ -407,15 +384,11 @@ public static String hexx(int i) { } public static int getNavigationHeight(int Default) { - int VKME = R.dimen.design_bottom_sheet_peek_height_min; - - return vkme() ? VKME : Default; + return vkme() ? R.dimen.design_bottom_sheet_peek_height_min : Default; } public static int getNavigationWidth(int Default) { - int VKME = R.dimen.app_minimumsize_h; - - return vkme() ? VKME : Default; + return vkme() ? R.dimen.app_minimumsize_h : Default; } public static ColorStateList getCSTDock() { @@ -426,12 +399,8 @@ public static ColorStateList getCSTDock() { }, new int[]{ dockbar_accent() ? getAccentColor() : (isDarkTheme() ? getResources().getColor(R.color.white) : getResources().getColor(R.color.gray_700)), - isDarkTheme() ? getResources().getColor(R.color.gray_500) : getResources().getColor(R.color.vk_steel_gray_300) + getColorFromAttr(R.attr.tabbar_inactive_icon) } ); } - - private void getCurrentThemeID(VKTheme paramVKTheme) { - Preference.b("vk_theme_helper", "current_theme", paramVKTheme.getId()); - } } diff --git a/app/src/main/java/ru/vtosters/hooks/ssfs/BannerHook.java b/app/src/main/java/ru/vtosters/hooks/ssfs/BannerHook.java new file mode 100644 index 0000000000..78ffe82d17 --- /dev/null +++ b/app/src/main/java/ru/vtosters/hooks/ssfs/BannerHook.java @@ -0,0 +1,35 @@ +package ru.vtosters.hooks.ssfs; + +import com.vk.core.dialogs.alert.VkAlertDialog; +import org.json.JSONObject; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.lite.ssfs.ImBanner; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.LifecycleUtils; + +public class BannerHook { + public static JSONObject convBar(JSONObject orig) { + return ImBanner.convertToBanner(orig); + } + + public static boolean showAlert() { + var context = LifecycleUtils.getCurrentActivity(); + var bool = Preferences.getBoolValue("linkalert", false); + + if (!bool) { + new VkAlertDialog.Builder(context) + .setTitle(com.vtosters.lite.R.string.warning) + .setMessage(AndroidUtils.getString("custom_links_warning")) + .setCancelable(false) + .setPositiveButton(com.vtosters.lite.R.string.continue_, (dialogInterface, i) -> { + Preferences.getPreferences().edit().putBoolean("linkalert", true).commit(); + }) + .setNeutralButton(com.vtosters.lite.R.string.cancel, (dialogInterface, i) -> { + dialogInterface.cancel(); + }) + .show(); + } + + return bool; + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ssfs/ProfileHider.java b/app/src/main/java/ru/vtosters/hooks/ssfs/ProfileHook.java similarity index 82% rename from app/src/main/java/ru/vtosters/lite/ssfs/ProfileHider.java rename to app/src/main/java/ru/vtosters/hooks/ssfs/ProfileHook.java index 7d273c9077..4838eb8d0b 100644 --- a/app/src/main/java/ru/vtosters/lite/ssfs/ProfileHider.java +++ b/app/src/main/java/ru/vtosters/hooks/ssfs/ProfileHook.java @@ -1,15 +1,18 @@ -package ru.vtosters.lite.ssfs; +package ru.vtosters.hooks.ssfs; import android.text.TextUtils; import android.util.Log; import com.vk.profile.presenter.UserPresenter; import com.vtosters.lite.api.ExtendedUserProfile; import org.json.JSONObject; +import ru.vtosters.lite.ssfs.Handler; +import ru.vtosters.lite.ssfs.ProfileButtons; +import ru.vtosters.lite.ssfs.UsersList; import static ru.vtosters.lite.utils.AccountManagerUtils.getUserID; import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -public class ProfileHider { +public class ProfileHook { private static final String TAG = "ProfileHider"; public static boolean isService(Integer id) { @@ -39,4 +42,8 @@ public static String getInfo(ExtendedUserProfile extendedUserProfile) { } return getGlobalContext().getString(UserPresenter.q0.a(userID)); } -} \ No newline at end of file + + public static JSONObject getProfileButton(JSONObject orig) { + return ProfileButtons.profileButton(orig); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/hooks/ui/SystemThemeChangerHook.java b/app/src/main/java/ru/vtosters/hooks/ui/SystemThemeChangerHook.java similarity index 91% rename from app/src/main/java/ru/vtosters/lite/hooks/ui/SystemThemeChangerHook.java rename to app/src/main/java/ru/vtosters/hooks/ui/SystemThemeChangerHook.java index 166c6c88e7..d4a51592c2 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/ui/SystemThemeChangerHook.java +++ b/app/src/main/java/ru/vtosters/hooks/ui/SystemThemeChangerHook.java @@ -1,11 +1,11 @@ -package ru.vtosters.lite.hooks.ui; +package ru.vtosters.hooks.ui; import android.app.Activity; import android.content.res.Configuration; import com.vk.core.ui.themes.VKTheme; +import ru.vtosters.hooks.other.Preferences; +import ru.vtosters.hooks.other.ThemesUtils; import ru.vtosters.lite.utils.LifecycleUtils; -import ru.vtosters.lite.utils.Preferences; -import ru.vtosters.lite.utils.ThemesUtils; public class SystemThemeChangerHook { public static void onThemeChanged(Configuration configuration) { diff --git a/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java b/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java index 61a8498627..4d722e2af1 100644 --- a/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java +++ b/app/src/main/java/ru/vtosters/lite/deviceinfo/Device.java @@ -1,10 +1,10 @@ package ru.vtosters.lite.deviceinfo; +import ru.vtosters.hooks.other.ThemesUtils; import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.ThemesUtils; +import ru.vtosters.lite.utils.VersionReader; import static ru.vtosters.lite.deviceinfo.OEMDetector.*; -import static ru.vtosters.lite.utils.About.getBuildNumber; public class Device { private String boardName; @@ -88,7 +88,8 @@ public Device withEmuiVersionName(String str) { public String toDeviceName() { return "Device information: " - + "commit='" + getBuildNumber() + + "commit='" + VersionReader.getVersionBuild() + + "', fullVersionInfo='" + VersionReader.getVersionFull() + "', isMilkshake='" + ThemesUtils.isMilkshake() + "', sdkVersion='" + this.sdkVersion + "', productName='" + this.productName @@ -105,7 +106,8 @@ public String toDeviceName() { public String forLogging() { return "**" + AndroidUtils.getString("device_info") + ":** " + "\n\n" - + "- Commit: " + getBuildNumber() + "\n" + + "- Commit: " + VersionReader.getVersionBuild() + "\n" + + "- Full Version Information: " + VersionReader.getVersionFull() + "\n" + "- Android SDK: " + this.sdkVersion + "\n" + "- Product: " + this.productName + "\n" + "- Device: " + this.deviceName + "\n" diff --git a/app/src/main/java/ru/vtosters/lite/dnr/DNRPrefs.java b/app/src/main/java/ru/vtosters/lite/dialogs/DNRPrefs.java similarity index 64% rename from app/src/main/java/ru/vtosters/lite/dnr/DNRPrefs.java rename to app/src/main/java/ru/vtosters/lite/dialogs/DNRPrefs.java index 6e5a464e98..77bc2b413a 100644 --- a/app/src/main/java/ru/vtosters/lite/dnr/DNRPrefs.java +++ b/app/src/main/java/ru/vtosters/lite/dialogs/DNRPrefs.java @@ -1,15 +1,10 @@ -package ru.vtosters.lite.dnr; +package ru.vtosters.lite.dialogs; -import static ru.vtosters.lite.utils.Preferences.getBoolValue; +import ru.vtosters.hooks.MessagesActivityHook; -public class DNRPrefs { - public static boolean getStoriesRead() { - return getBoolValue("read_s", false); - } +import static ru.vtosters.hooks.other.Preferences.getBoolValue; - public static boolean getVoiceListened() { - return getBoolValue("listen_v", false); - } +public class DNRPrefs { public static boolean getMarkAsReadWithoutExceptions(int peerId) { if (readPM(peerId)) @@ -24,34 +19,34 @@ public static boolean getMarkAsRead(int peerId) { } public static boolean isInDNRExceptions(int peerId) { - return DNRModule.mDoNotReadDBHelper.isEnabledForPeerId(peerId); + return MessagesActivityHook.mDoNotReadDBHelper.isEnabledForPeerId(peerId); } public static boolean isInDNTExceptions(int peerId) { - return DNRModule.mDoNotTypeDBHelper.isEnabledForPeerId(peerId); + return MessagesActivityHook.mDoNotTypeDBHelper.isEnabledForPeerId(peerId); } - public static boolean readPM(int peer) { + private static boolean readPM(int peer) { return (peer > 0 && peer < 2000000000) && getBoolValue("read_pm", false); } - public static boolean readConversations(int peer) { + private static boolean readConversations(int peer) { return (peer > 2000000000) && getBoolValue("read_conversations", false); } - public static boolean readBots(int peer) { + private static boolean readBots(int peer) { return (peer < 0) && getBoolValue("read_bot", false); } - public static boolean writePM(int peer) { + private static boolean writePM(int peer) { return (peer > 0 && peer < 2000000000) && getBoolValue("write_pm", false); } - public static boolean writeConversations(int peer) { + private static boolean writeConversations(int peer) { return (peer > 2000000000) && getBoolValue("write_conversations", false); } - public static boolean writeBots(int peer) { + private static boolean writeBots(int peer) { return (peer < 0) && getBoolValue("write_bot", false); } diff --git a/app/src/main/java/ru/vtosters/lite/dialogs/Requests.java b/app/src/main/java/ru/vtosters/lite/dialogs/Requests.java new file mode 100644 index 0000000000..2f1e26c1de --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/dialogs/Requests.java @@ -0,0 +1,62 @@ +package ru.vtosters.lite.dialogs; + +import com.vk.core.network.Network; +import com.vk.core.util.LangUtils; +import com.vk.im.engine.models.dialogs.Dialog; +import com.vk.im.engine.models.messages.Msg; +import com.vk.im.engine.utils.ImDialogsUtils; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import ru.vtosters.hooks.other.ThemesUtils; +import ru.vtosters.lite.concurrent.VTExecutors; +import ru.vtosters.lite.net.Request; +import ru.vtosters.lite.proxy.ProxyUtils; +import ru.vtosters.lite.utils.AccountManagerUtils; +import ru.vtosters.lite.utils.AndroidUtils; +import ru.vtosters.lite.utils.LifecycleUtils; + +public class Requests { + public static void hookRead(Dialog dialog) { + Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.F1() + "&peer_id=" + dialog.getId() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> { + }); + } + + public static void hookReadStartMsgTo(Msg dialog) { + Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.C1() + "&peer_id=" + dialog.v1() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> { + }); + } + + public static void hookKick(Msg dialog) { + if (dialog.getFrom().t1() == AccountManagerUtils.getUserId()) return; + Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.removeChatUser?member_id=" + dialog.getFrom().t1() + "&chat_id=" + ImDialogsUtils.c(dialog.v1()) + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> { + }); + } + + 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 pinnedMsg(int dialogid, boolean needToBePinned) { + VTExecutors.getIoExecutor().execute(() -> { + try { + var request = new okhttp3.Request.a() + .b("https://" + ProxyUtils.getApi() + "/method/" + (needToBePinned ? "messages.pinConversation" : "messages.unpinConversation") + "?peer_id=" + dialogid + "&access_token=" + AccountManagerUtils.getUserToken() + "&v=5.119") + .a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")) + .a(); + + new OkHttpClient().a(request).execute().a().g(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + AndroidUtils.sendToast(AndroidUtils.getString(com.vtosters.lite.R.string.pin_dialog) + " " + AndroidUtils.getString(needToBePinned ? com.vtosters.lite.R.string.dialog_pinned : com.vtosters.lite.R.string.dialog_unpinned)); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotReadDBHelper.java b/app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotReadDBHelper.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotReadDBHelper.java rename to app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotReadDBHelper.java index 7837b47fe6..e621c7b193 100644 --- a/app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotReadDBHelper.java +++ b/app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotReadDBHelper.java @@ -1,10 +1,10 @@ -package ru.vtosters.lite.dnr.helpers; +package ru.vtosters.lite.dialogs.helpers; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import ru.vtosters.lite.dnr.DNRPrefs; +import ru.vtosters.lite.dialogs.DNRPrefs; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotTypeDBHelper.java b/app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotTypeDBHelper.java similarity index 96% rename from app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotTypeDBHelper.java rename to app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotTypeDBHelper.java index 653019d837..33a267c61c 100644 --- a/app/src/main/java/ru/vtosters/lite/dnr/helpers/DoNotTypeDBHelper.java +++ b/app/src/main/java/ru/vtosters/lite/dialogs/helpers/DoNotTypeDBHelper.java @@ -1,10 +1,10 @@ -package ru.vtosters.lite.dnr.helpers; +package ru.vtosters.lite.dialogs.helpers; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import ru.vtosters.lite.dnr.DNRPrefs; +import ru.vtosters.lite.dialogs.DNRPrefs; import ru.vtosters.lite.utils.AndroidUtils; import java.util.ArrayList; diff --git a/app/src/main/java/ru/vtosters/lite/dnr/DNRInjector.java b/app/src/main/java/ru/vtosters/lite/dnr/DNRInjector.java deleted file mode 100644 index b3669348d7..0000000000 --- a/app/src/main/java/ru/vtosters/lite/dnr/DNRInjector.java +++ /dev/null @@ -1,378 +0,0 @@ -package ru.vtosters.lite.dnr; - -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; -import com.vk.im.engine.models.EntityIntMap; -import com.vk.im.engine.models.dialogs.Dialog; -import com.vk.im.engine.models.messages.Msg; -import com.vk.im.engine.models.messages.MsgFromUser; -import com.vk.im.ui.components.common.DialogAction; -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.proxy.ProxyUtils; -import ru.vtosters.lite.ui.dialogs.Translate; -import ru.vtosters.lite.utils.AccountManagerUtils; -import ru.vtosters.lite.utils.AndroidUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; - -public class DNRInjector { - public static void inject(Dialog dialog, List list) { - int peerId = 0; - if (dialog != null) { - try { - peerId = dialog.getId(); - } catch (Exception e) { - Log.d("DialoggetId", "null"); - } - } - - if (peerId == 0) { - return; - } - - list.add(DialogAction.STAT); - list.add(DialogAction.DOWNLOAD); - - list.add(DialogAction.pinmsg); - list.add(DialogAction.unpinmsg); - - if (DNRModule.isDnrEnabledFor(peerId)) { - list.add(DialogAction.DNR_OFF); - } else { - list.add(DialogAction.DNR_ON); - } - - if (DNRModule.isDntEnabledFor(peerId)) { - list.add(DialogAction.DNT_OFF); - } else { - list.add(DialogAction.DNT_ON); - } - - list.add(DialogAction.ENCRYPT); - IMProcessor prov = EncryptProvider.getProcessorFor(peerId); - if (prov != null && !prov.isPublic()) { - list.add(DialogAction.ENCRYPT_SETT); - } - } - - public static LinkedHashMap injectToHashMap(LinkedHashMap 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); - hashMap.put(DialogAction.DNR_OFF, R.string.DNR_OFF); - hashMap.put(DialogAction.DNT_ON, R.string.DNT_ON); - hashMap.put(DialogAction.DNT_OFF, R.string.DNT_OFF); - - hashMap.put(DialogAction.ENCRYPT, R.string.encryption); - hashMap.put(DialogAction.ENCRYPT_SETT, R.string.encryption_sett); - -// hashMap.put(DialogAction.pinmsg, getIdentifier("pinmsg", "string")); -// hashMap.put(DialogAction.unpinmsg, getIdentifier("unpinmsg", "string")); - return hashMap; - } - - @SuppressLint("ResourceType") - public static List injectToList(List 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 - list.add(new DialogActionsListView.b.a(DialogAction.DNR_OFF, 2, R.attr.im_ic_pinned_msg_show, R.string.DNR_OFF)); // DialogAction, Int, Icon, String - - list.add(new DialogActionsListView.b.a(DialogAction.DNT_ON, 2, R.attr.im_ic_edit_msg, R.string.DNT_ON)); // DialogAction, Int, Icon, String - list.add(new DialogActionsListView.b.a(DialogAction.DNT_OFF, 2, R.attr.im_ic_edit_msg, R.string.DNT_OFF)); // DialogAction, Int, Icon, String - - if (!AndroidUtils.isTablet()) { - list.add(new DialogActionsListView.b.a(DialogAction.ENCRYPT, 3, R.attr.im_ic_keyboard, R.string.encryption)); // DialogAction, Int, Icon, String - list.add(new DialogActionsListView.b.a(DialogAction.ENCRYPT_SETT, 3, R.attr.im_ic_more_vertical, R.string.encryption_sett)); // DialogAction, Int, Icon, String - } - - list.add(new DialogActionsListView.b.a(DialogAction.MARK_AS_READ, 4, R.attr.im_ic_done, R.string.vkim_dialogs_list_option_mark_as_read)); // DialogAction, Int, Icon, String - - return list; - } - - public static void forceInvalidateDialogActions(Dialog d) { - EntityIntMap map = new EntityIntMap<>(); - map.a(d.getId(), d); - ImEngine1.a().a(new OnDialogUpdateEvent(null, map)); - } - - public static List injectToListAccess(List actions, Dialog dialog) { - int peerId = 0; - - if (dialog != null) { - try { - peerId = dialog.getId(); - } catch (Exception e) { - Log.d("DialoggetId", "null"); - } - } - - if (peerId == 0) { - return actions; - } - - actions.add(DialogAction.STAT); - - actions.add(DialogAction.DOWNLOAD); - - if (DNRModule.isDnrEnabledFor(peerId)) { - actions.add(DialogAction.DNR_OFF); - } else { - actions.add(DialogAction.DNR_ON); - } - - if (DNRModule.isDntEnabledFor(peerId)) { - actions.add(DialogAction.DNT_OFF); - } else { - actions.add(DialogAction.DNT_ON); - } - - actions.add(DialogAction.ENCRYPT); - IMProcessor prov = EncryptProvider.getProcessorFor(peerId); - if (prov != null && !prov.isPublic()) { - actions.add(DialogAction.ENCRYPT_SETT); - } - - actions.add(DialogAction.MARK_AS_READ); - return actions; - } - - public static boolean onClickHeader(DialogAction action, Dialog dialog) { - int peerId = 0; - - if (dialog != null) { - try { - peerId = dialog.getId(); - } catch (Exception e) { - Log.d("DialoggetId", "null"); - } - } - - if (peerId == 0) { - return true; - } - - if (action == DialogAction.MARK_AS_READ) { - DNRModule.hookRead(dialog); - 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 HtmlDialogDownloaderFormatProvider(), out); - } catch (Exception e) { - AndroidUtils.sendToast(AndroidUtils.getString(R.string.download_dl_error)); - e.printStackTrace(); - } - return true; - } - - if (action == DialogAction.DNR_ON) { - setDnr(dialog, true); - forceInvalidateDialogActions(dialog); - return true; - } else if (action == DialogAction.DNR_OFF) { - setDnr(dialog, false); - forceInvalidateDialogActions(dialog); - return true; - } - - if (action == DialogAction.DNT_ON) { - setDnt(dialog, true); - forceInvalidateDialogActions(dialog); - return true; - } else if (action == DialogAction.DNT_OFF) { - setDnt(dialog, false); - forceInvalidateDialogActions(dialog); - return true; - } - - if (action == DialogAction.ENCRYPT_SETT) { - CryptImHook.hookPref(peerId); - return true; - } else if (action == DialogAction.ENCRYPT) { - CryptImHook.hook(peerId, dialog); - return true; - } - - return false; - } - - public static boolean onClick(Dialog dialog, DialogAction action) { - int peerId = 0; - - if (dialog != null) { - try { - peerId = dialog.getId(); - } catch (Exception e) { - Log.d("DialoggetId", "null"); - } - } - - if (peerId == 0) { - return true; - } - - if (action == DialogAction.MARK_AS_READ) { - 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 HtmlDialogDownloaderFormatProvider(), out); - } catch (Exception e) { - e.printStackTrace(); - } - return true; - } - - if (action == DialogAction.DNR_ON) { - setDnr(dialog, true); - return true; - } else if (action == DialogAction.DNR_OFF) { - setDnr(dialog, false); - return true; - } - - if (action == DialogAction.DNT_ON) { - setDnt(dialog, true); - return true; - } else if (action == DialogAction.DNT_OFF) { - setDnt(dialog, false); - return true; - } - - if (action == DialogAction.pinmsg) { - pinnedMsg(peerId, true); - return true; - } else if (action == DialogAction.unpinmsg) { - pinnedMsg(peerId, false); - return true; - } - - if (action == DialogAction.ENCRYPT_SETT) { - CryptImHook.hookPref(peerId); - return true; - } else if (action == DialogAction.ENCRYPT) { - CryptImHook.hook(peerId, dialog); - return true; - } - - return false; - } - - public static LinkedHashMap injectToHashMapMsg(LinkedHashMap hashMap) { - hashMap.put(MsgAction.valueOf("TRANSLATE"), new DelegateMsg.a.a(R.string.translator)); - hashMap.put(MsgAction.valueOf("READTO"), new DelegateMsg.a.a(R.string.readto)); - return hashMap; - } - - public static boolean onClickMsg(Context context, MsgAction action, Msg msg) { - var peerId = msg.v1(); - - if (action == MsgAction.valueOf("READTO")) { - DNRModule.hookReadStartMsgTo(msg); - return true; - } - - if (msg instanceof MsgFromUser) { - var text = ((MsgFromUser) msg).f(); - var isTextExist = !text.isEmpty() && !text.equals(" "); - - if (isTextExist) { - text = EncryptProvider.decryptMessage(text, peerId); - } - - if (action == MsgAction.valueOf("TRANSLATE")) { - if (isTextExist) { - Translate.showTranslatedText(context, text); - } else { - AndroidUtils.sendToast(context.getString(R.string.translator_no_text)); - } - } - } - - return false; - } - - public static void pinnedMsg(int dialogid, boolean needToBePinned) { - Thread thread = new Thread(() -> { - try { - var request = new Request.a() - .b("https://" + ProxyUtils.getApi() + "/method/" + (needToBePinned ? "messages.pinConversation" : "messages.unpinConversation") + "?peer_id=" + dialogid + "&access_token=" + AccountManagerUtils.getUserToken() + "&v=5.119") - .a(Headers.a("User-Agent", Network.l.c().a(), "Content-Type", "application/x-www-form-urlencoded; charset=utf-8")) - .a(); - - try { - var response = new OkHttpClient().a(request).execute().a().g(); - Log.d("Pins", response); - } catch (IOException e) { - Log.e("Pins", "Error while pinning message", e); - } - } catch (Exception e) { - e.printStackTrace(); - } - }); - - thread.start(); - - AndroidUtils.sendToast(AndroidUtils.getString(R.string.pin_dialog) + " " + AndroidUtils.getString(needToBePinned ? R.string.dialog_pinned : R.string.dialog_unpinned)); - } - - public static void setDnr(Dialog dialog, boolean value) { - int peerId = 0; - - if (dialog != null) { - peerId = dialog.getId(); - } - - DNRModule.hookDNR(peerId); - } - - public static void setDnt(Dialog dialog, boolean value) { - int peerId = 0; - - if (dialog != null) { - peerId = dialog.getId(); - } - - DNRModule.hookDNT(peerId); - } -} - diff --git a/app/src/main/java/ru/vtosters/lite/dnr/DNRModule.java b/app/src/main/java/ru/vtosters/lite/dnr/DNRModule.java deleted file mode 100644 index 602e2c9a92..0000000000 --- a/app/src/main/java/ru/vtosters/lite/dnr/DNRModule.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.vtosters.lite.dnr; - -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 ru.vtosters.lite.dnr.helpers.DoNotReadDBHelper; -import ru.vtosters.lite.dnr.helpers.DoNotTypeDBHelper; -import ru.vtosters.lite.net.Request; -import ru.vtosters.lite.proxy.ProxyUtils; -import ru.vtosters.lite.utils.AccountManagerUtils; -import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.LifecycleUtils; -import ru.vtosters.lite.utils.ThemesUtils; - -import java.util.List; - -public class DNRModule { - public static DoNotReadDBHelper mDoNotReadDBHelper = new DoNotReadDBHelper(); - public static DoNotTypeDBHelper mDoNotTypeDBHelper = new DoNotTypeDBHelper(); - public static Dialog mDialog; - - public static boolean isDnrEnabledFor(int id) { - var isEnabled = mDoNotReadDBHelper.isEnabledForPeerId(id); - return isEnabled; - } - - public static List getDnrEnabled() { - return mDoNotReadDBHelper.get(); - } - - public static List getDntEnabled() { - return mDoNotTypeDBHelper.get(); - } - - public static boolean isDntEnabledFor(int id) { - var isEnabled = mDoNotTypeDBHelper.isEnabledForPeerId(id); - return isEnabled; - } - - public static boolean isDntEnabledFor(SetUserActivityCmd cmd) { - return isDntEnabledFor(cmd.b); - } - - public static boolean isDnrEnabledFor(Dialog dialog) { - if (dialog == null) return false; - return isDnrEnabledFor(dialog.getId()); - } - - public static boolean isDntEnabledFor(Dialog dialog) { - if (dialog == null) return false; - return isDntEnabledFor(dialog.getId()); - } - - public static void hookRead(Dialog dialog) { - Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.F1() + "&peer_id=" + dialog.getId() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> { - }); - } - - public static void hookReadStartMsgTo(Msg dialog) { - Log.d("DNR", "hookReadStartMsgTo: " + dialog.C1() + " " + dialog.v1()); - Request.makeRequest("https://" + ProxyUtils.getApi() + "/method/messages.markAsRead?start_message_id=" + dialog.C1() + "&peer_id=" + dialog.v1() + "&v=5.119&access_token=" + AccountManagerUtils.getUserToken(), response -> { - }); - } - - 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)); - } - - public static void hookDNT(int peerId) { - mDoNotTypeDBHelper.setEnabledForPeerId(peerId, !mDoNotTypeDBHelper.isEnabledForPeerId(peerId)); - } -} diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java index 2f07c5186b..3892d7fc77 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/AudioDownloader.java @@ -6,8 +6,8 @@ import com.vk.dto.music.MusicTrack; import com.vk.dto.music.Playlist; import com.vtosters.lite.R; +import ru.vtosters.hooks.MusicCacheFilesHook; import ru.vtosters.lite.music.cache.CacheDatabaseDelegate; -import ru.vtosters.lite.music.cache.FileCacheImplementation; import ru.vtosters.lite.music.callback.MusicCallbackBuilder; import ru.vtosters.lite.music.converter.playlist.PlaylistConverter; import ru.vtosters.lite.music.downloader.AudioGet; @@ -66,7 +66,7 @@ public static void cacheTrack(MusicTrack track) { return; } - var trackFile = FileCacheImplementation.getTrackFile(trackId); + var trackFile = MusicCacheFilesHook.getTrackFile(trackId); if (!trackFile.exists()) trackFile.getParentFile().mkdirs(); executor.submit(() -> downloadM3U8(track, true)); diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/StoryDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/StoryDownloader.java index c427abf8cc..a02d6ef4b4 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/StoryDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/StoryDownloader.java @@ -13,9 +13,6 @@ import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; public class StoryDownloader { - public static Runnable injectButton(StoryEntry story) { - return () -> downloadStory(story); - } public static void downloadStory(StoryEntry story) { if (story.T1()) { diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/VideoDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/VideoDownloader.java index d954c20ba6..f11c8b0395 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/VideoDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/VideoDownloader.java @@ -20,39 +20,21 @@ 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 static ru.vtosters.hooks.other.ThemesUtils.getTextAttr; 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; - private static final int OPEN_EXTERNAL_LINK_ID = 1; - - public static boolean onClick(int id, VideoFile video, Context ctx) { - if (id == DOWNLOAD_ID) { - downloadVideo(video, ctx); - return true; - } else if (id == OPEN_EXTERNAL_LINK_ID) { - ExternalLinkParser.parseVideoFile(video, ctx, true); - } - return false; - } - - public static void injectAction(ArrayList list, VideoFile video) { - if (!video.U && !video.I1()) { - addAction(list, DOWNLOAD_ID, R.drawable.ic_download_outline_24, R.string.download, 9); - addAction(list, OPEN_EXTERNAL_LINK_ID, R.drawable.ic_link_outline_28, R.string.interfacevideoext_short, 9); - } - } + public static final int DOWNLOAD_ID = 0; + public static final int OPEN_EXTERNAL_LINK_ID = 1; - private static void addAction(List actions, int... ints) { + public static void addAction(List actions, int... ints) { actions.add(new MenuBottomSheetAction(ints[0], ints[1], ints[2], ints[3])); } diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java index 4b28b705dd..cdc0f49f34 100644 --- a/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.regex.Pattern; -import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage; +import static ru.vtosters.hooks.EncryptionMessagesHook.decryptMessage; import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; import static ru.vtosters.lite.utils.AndroidUtils.getArray; @@ -20,7 +20,6 @@ public class HtmlDialogDownloaderFormatProvider extends DialogDownloaderFormatPr @Override public String provideDocumentStart(String dialogName, String date) { - return "" + AndroidUtils.getString(R.string.chat_export_title) + "