package ru.vtosters.lite.themes; import android.app.Application; import android.content.Context; import android.graphics.Color; import android.util.Log; import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile; import com.vtosters.lite.R; import ru.vtosters.lite.themes.loaders.ResourcesLoader; import ru.vtosters.lite.themes.utils.ArscEditor; import ru.vtosters.lite.utils.IOUtils; import ru.vtosters.lite.utils.Preferences; import ru.vtosters.lite.utils.ThemesUtils; import java.io.*; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; public class ThemesManager { private final static int[] ACCENT_COLORS = { R.color.vk_sky_300, R.color.sky_300, R.color.viewer_retry_button_text_color, R.color.azure_300, R.color.picker_camera_button, R.color.music_check_button_bg_pressed, R.color.notification_color, R.color.music_text_counter, R.color.music_follow_button_bg_pressed, R.color.music_blue_button_normal, R.color.music_check_button_bg_normal, R.color.vk_terms_link, R.color.vk_blue_200, R.color.accent_blue, R.color.azure_A400, R.color.vk_azure_A400, R.color.blue, R.color.blue_200, R.color.vk_blue_300, R.color.vk_blue_400, R.color.blue_300, R.color.blue_400, R.color.cool_blue, R.color.cornflower_blue, R.color.cornflower_blue_two, R.color.dot_unread, R.color.fave_promo_btn_pressed, R.color.header_blue, R.color.colorAccent, R.color.live_emoji_butt_hide, R.color.music_action_button_blue, R.color.name, R.color.picker_blue, R.color.picker_blue_pressed, R.color.picker_tab_bg_selected, R.color.picker_tab_text_selected, R.color.sharing_blue_btn_normal, R.color.sharing_blue_btn_pressed, R.color.vk_sky_300, R.color.vk_blue_A400, R.color.blue_A400, R.color.sky_300, R.color.text_blue, R.color.tip_background, R.color.tw__blue_default, R.color.tw__blue_pressed, R.color.vkim_msg_sending_ic, R.color.vkim_playing_drawable_rect, R.color.vk_white_blue32, R.color.date_picker_selector, R.color.post_suggest_blue, R.color.list_dialog_blue, R.color.music_tab_bg_normal, R.color.toolbar_blue_background, R.color.toolbar_blue_statusBarColorBack, R.color.blue_200_muted, R.color.vk_blue_200_muted, R.color.white_blue32, R.color.vk_white_blue32, R.color.header_blue_opacity16, R.color.music_check_button_bg_checked, R.color.attachpicker_item_background, R.color.music_placeholder_artist_bg, R.color.azure_100_muted, R.color.vk_azure_100_muted, R.color.muted_blue, R.color.blue_600, R.color.light_blue, R.color.vk_blue_600, R.color.muted_blue_opacity40, R.color.muted_blue_old, R.color.darker_blue, R.color.header_blue_opacity40 }; // modify it if "doNotCompress" in apktool.yml has been edited private final static String[] DO_NOT_COMPRESS = { ".png", ".jpg", ".mp3", "res/raw/keep_cronet_api.xml" }; private static String baseApkPath; private static boolean validated = false; // Directories private static File mainDir; private static File colorSchemesDir; // Modified archive with resources.arsc, other resources and assets private static File modApk; public static void init(Application app) { try { initPaths(app); validateModApk(); if (!Preferences.isNewBuild() && !ThemesUtils.isMonetTheme() && canApplyCustomAccent() && ThemesUtils.useNewColorEngine()) { ResourcesLoader.init(app); ResourcesLoader.load(app, modApk.getAbsolutePath(), false); } } catch (Exception e) { Log.e("ThemesManager", e.getMessage()); } } // validate modded apk before loading private static void validateModApk() throws IOException { final var apk = new ZipFile(modApk); apk.close(); validated = true; } private static void initPaths(Context context) { baseApkPath = context.getApplicationInfo().publicSourceDir; mainDir = new File(context.getFilesDir(), "VTLThemes"); if (!mainDir.exists()) mainDir.mkdir(); colorSchemesDir = new File(mainDir, "color_schemes"); if (!colorSchemesDir.exists()) colorSchemesDir.mkdir(); modApk = new File(mainDir, "mod.apk"); } public static boolean canApplyCustomAccent() { return validated && modApk.exists() && ThemesUtils.getReservedAccent() != Color.TRANSPARENT; } public static void generateModApk(int accentColor) throws Throwable { try ( final var apk = new ZipFile(baseApkPath) ) { final var arscEntry = apk.getEntry("resources.arsc"); final var arscBis = new BufferedInputStream(apk.getInputStream(arscEntry)); final var arsc = BinaryResourceFile.fromInputStream(arscBis); if (!ArscEditor.changeColors(arsc, ACCENT_COLORS, accentColor)) throw new IllegalStateException("Not all colors have been changed"); try ( final var zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(modApk))) ) { final var arscBuff = arsc.toByteArray(); final var newArscEntry = new ZipEntry("resources.arsc"); final var crc = new CRC32(); crc.update(arscBuff); newArscEntry.setSize(arscBuff.length); newArscEntry.setMethod(ZipEntry.STORED); newArscEntry.setCrc(crc.getValue()); zos.putNextEntry(newArscEntry); zos.write(arscBuff); zos.closeEntry(); final var entries = apk.entries(); while (entries.hasMoreElements()) { final var entry = entries.nextElement(); final var name = entry.getName(); if (!name.startsWith("res/") && !name.startsWith("assets/") || name.equals("resources.arsc")) continue; var doNotCompress = false; for (var suffix : DO_NOT_COMPRESS) { if (name.endsWith(suffix)) { doNotCompress = true; break; } } final var entryBis = new BufferedInputStream(apk.getInputStream(entry)); final var newEntry = new ZipEntry(name); if (doNotCompress) { newEntry.setMethod(ZipEntry.STORED); newEntry.setSize(entry.getSize()); newEntry.setCrc(entry.getCrc()); } else newEntry.setMethod(ZipEntry.DEFLATED); zos.putNextEntry(newEntry); IOUtils.copy(entryBis, zos); entryBis.close(); zos.closeEntry(); } } } } public static void deleteModification() { modApk.delete(); } }