From 2bd7b5aeedaa838e36dad4bfea0a2b02c53eb299 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:34:03 +0900 Subject: [PATCH] fix(YouTube - Seekbar components): `Custom seekbar color` not applied to gradient seekbar in YouTube 19.34.42 --- .../patches/utils/DrawableColorPatch.java | 2 +- .../youtube/patches/player/PlayerPatch.java | 61 ++-- .../patches/player/SeekbarColorPatch.java | 260 ++++++++++++++++++ .../patches/utils/DrawableColorPatch.java | 2 +- .../patches/utils/ProgressBarDrawable.java | 6 +- .../music/general/amoled/AmoledPatch.kt | 2 +- .../youtube/layout/theme/SharedThemePatch.kt | 2 +- .../youtube/player/seekbar/Fingerprints.kt | 31 +++ .../player/seekbar/SeekbarComponentsPatch.kt | 175 +++++++++++- .../utils/playservice/VersionCheckPatch.kt | 3 + .../youtube/seekbar/values/attrs.xml | 4 + .../youtube/settings/host/values/strings.xml | 1 + 12 files changed, 487 insertions(+), 62 deletions(-) create mode 100644 extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java create mode 100644 patches/src/main/resources/youtube/seekbar/values/attrs.xml diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java index d72807458..ddb7b0101 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java @@ -6,7 +6,7 @@ public class DrawableColorPatch { -14606047 // comments box background }; - public static int getColor(int originalValue) { + public static int getLithoColor(int originalValue) { if (anyEquals(originalValue, DARK_VALUES)) return -16777215; diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java index deb2bb5b8..8b9c2de35 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java @@ -7,7 +7,6 @@ import static app.revanced.extension.youtube.utils.ExtendedUtils.validateValue; import android.app.Activity; import android.content.pm.ActivityInfo; -import android.graphics.Color; import android.support.v7.widget.RecyclerView; import android.util.TypedValue; import android.view.View; @@ -597,12 +596,26 @@ public class PlayerPatch { return !Settings.HIDE_PLAYER_FLYOUT_MENU_PIP.get(); } + /** + * Overriding this values is possible only after the litho component has been loaded. + * Otherwise, crash will occur. + * See {@link InitializationPatch#onCreate}. + * + * @param original original value. + * @return whether to enable Sleep timer Mode in the player flyout menu. + */ + public static boolean hideDeprecatedSleepTimerMenu(boolean original) { + if (!BaseSettings.SETTINGS_INITIALIZED.get()) { + return original; + } + + return !Settings.HIDE_PLAYER_FLYOUT_MENU_SLEEP_TIMER.get(); + } + // endregion // region [Seekbar components] patch - public static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000; - public static String appendTimeStampInformation(String original) { if (!Settings.APPEND_TIME_STAMP_INFORMATION.get()) return original; @@ -641,48 +654,6 @@ public class PlayerPatch { } } - public static int getSeekbarClickedColorValue(final int colorValue) { - return colorValue == ORIGINAL_SEEKBAR_COLOR - ? overrideSeekbarColor(colorValue) - : colorValue; - } - - public static int resumedProgressBarColor(final int colorValue) { - return Settings.ENABLE_CUSTOM_SEEKBAR_COLOR.get() - ? getSeekbarClickedColorValue(colorValue) - : colorValue; - } - - /** - * Overrides all drawable color that use the YouTube seekbar color. - * Used only for the video thumbnails seekbar. - *

- * If {@link Settings#HIDE_SEEKBAR_THUMBNAIL} is enabled, this returns a fully transparent color. - */ - public static int getColor(int colorValue) { - if (colorValue == ORIGINAL_SEEKBAR_COLOR) { - if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) { - return 0x00000000; - } - return overrideSeekbarColor(ORIGINAL_SEEKBAR_COLOR); - } - return colorValue; - } - - /** - * Points where errors occur when playing videos on the PlayStore (ROOT Build) - */ - public static int overrideSeekbarColor(final int colorValue) { - try { - return Settings.ENABLE_CUSTOM_SEEKBAR_COLOR.get() - ? Color.parseColor(Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.get()) - : colorValue; - } catch (Exception ignored) { - Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault(); - } - return colorValue; - } - public static boolean enableSeekbarTapping() { return Settings.ENABLE_SEEKBAR_TAPPING.get(); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java new file mode 100644 index 000000000..65e379851 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java @@ -0,0 +1,260 @@ +package app.revanced.extension.youtube.patches.player; + +import static app.revanced.extension.shared.utils.StringRef.str; + +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.AnimatedVectorDrawable; + +import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.shared.utils.ResourceUtils; +import app.revanced.extension.shared.utils.Utils; +import app.revanced.extension.youtube.settings.Settings; + +import java.util.Arrays; +import java.util.Locale; + +@SuppressWarnings("unused") +public class SeekbarColorPatch { + + private static final boolean CUSTOM_SEEKBAR_COLOR_ENABLED = + Settings.ENABLE_CUSTOM_SEEKBAR_COLOR.get(); + + /** + * Default color of the litho seekbar. + * Differs slightly from the default custom seekbar color setting. + */ + private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000; + + /** + * Default colors of the gradient seekbar. + */ + private static final int[] ORIGINAL_SEEKBAR_GRADIENT_COLORS = { 0xFFFF0033, 0xFFFF2791 }; + + /** + * Default positions of the gradient seekbar. + */ + private static final float[] ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = { 0.8f, 1.0f }; + + /** + * Default YouTube seekbar color brightness. + */ + private static final float ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS; + + /** + * If {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR} is enabled, + * this is the color value of {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE}. + * Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}. + */ + private static int seekbarColor = ORIGINAL_SEEKBAR_COLOR; + + /** + * Custom seekbar hue, saturation, and brightness values. + */ + private static final float[] customSeekbarColorHSV = new float[3]; + + static { + float[] hsv = new float[3]; + Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv); + ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS = hsv[2]; + + if (CUSTOM_SEEKBAR_COLOR_ENABLED) { + loadCustomSeekbarColor(); + } + } + + private static void loadCustomSeekbarColor() { + try { + seekbarColor = Color.parseColor(Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.get()); + Color.colorToHSV(seekbarColor, customSeekbarColorHSV); + } catch (Exception ex) { + Utils.showToastShort(str("revanced_custom_seekbar_color_value_invalid_invalid_toast")); + Utils.showToastShort(str("revanced_extended_reset_to_default_toast")); + Settings.ENABLE_CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault(); + loadCustomSeekbarColor(); + } + } + + public static int getSeekbarColor() { + return seekbarColor; + } + + /** + * Injection point + */ + public static boolean playerSeekbarGradientEnabled(boolean original) { + if (CUSTOM_SEEKBAR_COLOR_ENABLED) return false; + + return original; + } + + /** + * Injection point + */ + public static boolean useLotteLaunchSplashScreen(boolean original) { + Logger.printDebug(() -> "useLotteLaunchSplashScreen original: " + original); + + if (CUSTOM_SEEKBAR_COLOR_ENABLED) return false; + + return original; + } + + private static int colorChannelTo3Bits(int channel8Bits) { + final float channel3Bits = channel8Bits * 7 / 255f; + + // If a color channel is near zero, then allow rounding up so values between + // 0x12 and 0x23 will show as 0x24. But always round down when the channel is + // near full saturation, otherwise rounding to nearest will cause all values + // between 0xEC and 0xFE to always show as full saturation (0xFF). + return channel3Bits < 6 + ? Math.round(channel3Bits) + : (int) channel3Bits; + } + + private static String get9BitStyleIdentifier(int color24Bit) { + final int r3 = colorChannelTo3Bits(Color.red(color24Bit)); + final int g3 = colorChannelTo3Bits(Color.green(color24Bit)); + final int b3 = colorChannelTo3Bits(Color.blue(color24Bit)); + + return String.format(Locale.US, "splash_seekbar_color_style_%d_%d_%d", r3, g3, b3); + } + + /** + * Injection point + */ + public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) { + // Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable + // without using any styles, but a color filter cannot selectively change the seekbar + // while keeping the red YT logo untouched. + // Even if the seekbar color xml value is changed to a completely different color (such as green), + // a color filter still cannot be selectively applied when the drawable has more than 1 color. + try { + String seekbarStyle = get9BitStyleIdentifier(seekbarColor); + Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle); + + final int styleIdentifierDefault = ResourceUtils.getStyleIdentifier(seekbarStyle); + if (styleIdentifierDefault == 0) { + throw new RuntimeException("Seekbar style not found: " + seekbarStyle); + } + + Resources.Theme theme = Utils.getContext().getResources().newTheme(); + theme.applyStyle(styleIdentifierDefault, true); + + vectorDrawable.applyTheme(theme); + } catch (Exception ex) { + Logger.printException(() -> "setSplashAnimationDrawableTheme failure", ex); + } + } + + /** + * Injection point. + *

+ * Overrides all Litho components that use the YouTube seekbar color. + * Used only for the video thumbnails seekbar. + *

+ * If {@link Settings#HIDE_SEEKBAR_THUMBNAIL} is enabled, this returns a fully transparent color. + */ + public static int getLithoColor(int colorValue) { + if (colorValue == ORIGINAL_SEEKBAR_COLOR) { + if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) { + return 0x00000000; + } + + return getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR); + } + return colorValue; + } + + /** + * Injection point. + */ + public static void setLinearGradient(int[] colors, float[] positions) { + final boolean hideSeekbar = Settings.HIDE_SEEKBAR_THUMBNAIL.get(); + + if (CUSTOM_SEEKBAR_COLOR_ENABLED || hideSeekbar) { + // Most litho usage of linear gradients is hooked here, + // so must only change if the values are those for the seekbar. + if (Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors) + && Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions)) { + Arrays.fill(colors, hideSeekbar + ? 0x00000000 + : seekbarColor); + return; + } + + Logger.printDebug(() -> "Ignoring gradient colors: " + Arrays.toString(colors) + + " positions: " + Arrays.toString(positions)); + } + } + + /** + * Injection point. + *

+ * Overrides color when video player seekbar is clicked. + */ + public static int getVideoPlayerSeekbarClickedColor(int colorValue) { + if (!CUSTOM_SEEKBAR_COLOR_ENABLED) { + return colorValue; + } + + return colorValue == ORIGINAL_SEEKBAR_COLOR + ? getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR) + : colorValue; + } + + /** + * Injection point. + *

+ * Overrides color used for the video player seekbar. + */ + public static int getVideoPlayerSeekbarColor(int originalColor) { + if (!CUSTOM_SEEKBAR_COLOR_ENABLED) { + return originalColor; + } + + return getSeekbarColorValue(originalColor); + } + + /** + * Color parameter is changed to the custom seekbar color, while retaining + * the brightness and alpha changes of the parameter value compared to the original seekbar color. + */ + private static int getSeekbarColorValue(int originalColor) { + try { + if (!CUSTOM_SEEKBAR_COLOR_ENABLED || originalColor == seekbarColor) { + return originalColor; // nothing to do + } + + final int alphaDifference = Color.alpha(originalColor) - Color.alpha(ORIGINAL_SEEKBAR_COLOR); + + // The seekbar uses the same color but different brightness for different situations. + float[] hsv = new float[3]; + Color.colorToHSV(originalColor, hsv); + final float brightnessDifference = hsv[2] - ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS; + + // Apply the brightness difference to the custom seekbar color. + hsv[0] = customSeekbarColorHSV[0]; + hsv[1] = customSeekbarColorHSV[1]; + hsv[2] = clamp(customSeekbarColorHSV[2] + brightnessDifference, 0, 1); + + final int replacementAlpha = clamp(Color.alpha(seekbarColor) + alphaDifference, 0, 255); + final int replacementColor = Color.HSVToColor(replacementAlpha, hsv); + Logger.printDebug(() -> String.format("Original color: #%08X replacement color: #%08X", + originalColor, replacementColor)); + return replacementColor; + } catch (Exception ex) { + Logger.printException(() -> "getSeekbarColorValue failure", ex); + return originalColor; + } + } + + /** @noinspection SameParameterValue */ + private static int clamp(int value, int lower, int upper) { + return Math.max(lower, Math.min(value, upper)); + } + + /** @noinspection SameParameterValue */ + private static float clamp(float value, float lower, float upper) { + return Math.max(lower, Math.min(value, upper)); + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/DrawableColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/DrawableColorPatch.java index 853779b37..772dcd3e6 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/DrawableColorPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/DrawableColorPatch.java @@ -22,7 +22,7 @@ public class DrawableColorPatch { private static int whiteColor = 0; private static int blackColor = 0; - public static int getColor(int originalValue) { + public static int getLithoColor(int originalValue) { if (anyEquals(originalValue, DARK_VALUES)) { return getBlackColor(); } else if (anyEquals(originalValue, WHITE_VALUES)) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/ProgressBarDrawable.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/ProgressBarDrawable.java index 3cb20d617..b3d4543cd 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/ProgressBarDrawable.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/ProgressBarDrawable.java @@ -1,8 +1,5 @@ package app.revanced.extension.youtube.patches.utils; -import static app.revanced.extension.youtube.patches.player.PlayerPatch.ORIGINAL_SEEKBAR_COLOR; -import static app.revanced.extension.youtube.patches.player.PlayerPatch.resumedProgressBarColor; - import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; @@ -12,6 +9,7 @@ import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.extension.youtube.patches.player.SeekbarColorPatch; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") @@ -24,7 +22,7 @@ public class ProgressBarDrawable extends Drawable { if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) { return; } - paint.setColor(resumedProgressBarColor(ORIGINAL_SEEKBAR_COLOR)); + paint.setColor(SeekbarColorPatch.getSeekbarColor()); canvas.drawRect(getBounds(), paint); } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/amoled/AmoledPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/amoled/AmoledPatch.kt index 310578ff1..c2a79075d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/amoled/AmoledPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/amoled/AmoledPatch.kt @@ -23,7 +23,7 @@ val amoledPatch = resourcePatch( ) execute { - addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getColor(I)I") + addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getLithoColor(I)I") document("res/values/colors.xml").use { document -> val resourcesNode = document.getElementsByTagName("resources").item(0) as Element diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt index 89d7f1133..0ca1403b1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt @@ -24,7 +24,7 @@ val sharedThemePatch = resourcePatch( ) execute { - addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getColor(I)I") + addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getLithoColor(I)I") // edit the resource files to change the splash screen color val attrsResourceFile = "res/values/attrs.xml" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt index 34240c475..d59944886 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt @@ -1,11 +1,42 @@ package app.revanced.patches.youtube.player.seekbar import app.revanced.patches.youtube.utils.resourceid.reelTimeBarPlayedColor +import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +internal const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L + +internal val playerSeekbarGradientConfigFingerprint = legacyFingerprint( + name = "playerSeekbarGradientConfigFingerprint", + returnType = "Z", + parameters = emptyList(), + literals = listOf(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG), +) + +internal val lithoLinearGradientFingerprint = legacyFingerprint( + name = "lithoLinearGradientFingerprint", + accessFlags = AccessFlags.STATIC.value, + returnType = "Landroid/graphics/LinearGradient;", + parameters = listOf("F", "F", "F", "F", "[I", "[F") +) +internal const val launchScreenLayoutTypeLotteFeatureFlag = 268507948L + +internal val launchScreenLayoutTypeFingerprint = legacyFingerprint( + name = "launchScreenLayoutTypeFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + returnType = "V", + customFingerprint = { method, _ -> + val firstParameter = method.parameterTypes.firstOrNull() + // 19.25 - 19.45 + (firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;" + || firstParameter == "Landroid/app/Activity;") // 19.46+ + && method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag) + } +) + internal val controlsOverlayStyleFingerprint = legacyFingerprint( name = "controlsOverlayStyleFingerprint", opcodes = listOf(Opcode.CONST_HIGH16), diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt index b55f7febd..2372a7df5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt @@ -1,5 +1,6 @@ package app.revanced.patches.youtube.player.seekbar +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction @@ -9,14 +10,19 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.drawable.addDrawableColorHook import app.revanced.patches.shared.drawable.drawableColorHookPatch +import app.revanced.patches.shared.mainactivity.onCreateMethod import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH import app.revanced.patches.youtube.utils.flyoutmenu.flyoutMenuHookPatch +import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch import app.revanced.patches.youtube.utils.patch.PatchList.SEEKBAR_COMPONENTS import app.revanced.patches.youtube.utils.playerButtonsResourcesFingerprint import app.revanced.patches.youtube.utils.playerButtonsVisibilityFingerprint import app.revanced.patches.youtube.utils.playerSeekbarColorFingerprint +import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarColorizedBarPlayedColorDark import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarPlayedNotHighlightedColor @@ -29,6 +35,8 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.totalTimeFingerprint import app.revanced.patches.youtube.video.information.videoInformationPatch +import app.revanced.util.copyXmlNode +import app.revanced.util.findElementByAttributeValueOrThrow import app.revanced.util.findMethodsOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow @@ -38,6 +46,7 @@ import app.revanced.util.getReference import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import app.revanced.util.inputStreamFromBundledResource import app.revanced.util.updatePatchStatus import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -46,6 +55,45 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference import org.w3c.dom.Element +import java.io.ByteArrayInputStream + +internal const val splashSeekbarColorAttributeName = "splash_custom_seekbar_color" + +/** + * Generate a style xml with all combinations of 9-bit colors. + */ +private fun create9BitSeekbarColorStyles(): String = StringBuilder().apply { + append("") + append("\n") + + for (red in 0..7) { + for (green in 0..7) { + for (blue in 0..7) { + val name = "${red}_${green}_${blue}" + + fun roundTo3BitHex(channel8Bits: Int) = + (channel8Bits * 255 / 7).toString(16).padStart(2, '0') + val r = roundTo3BitHex(red) + val g = roundTo3BitHex(green) + val b = roundTo3BitHex(blue) + val color = "#ff$r$g$b" + + append( + """ + + """ + ) + } + } + } + + append("") +}.toString() + +private const val EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR = + "$PLAYER_PATH/SeekbarColorPatch;" @Suppress("unused") val seekbarComponentsPatch = bytecodePatch( @@ -57,6 +105,7 @@ val seekbarComponentsPatch = bytecodePatch( dependsOn( drawableColorHookPatch, flyoutMenuHookPatch, + mainActivityResolvePatch, sharedResourceIdPatch, settingsPatch, videoInformationPatch, @@ -157,26 +206,25 @@ val seekbarComponentsPatch = bytecodePatch( // region patch for seekbar color - fun MutableMethod.hookSeekbarColor(literal: Long) { + fun MutableMethod.addColorChangeInstructions(literal: Long) { val insertIndex = indexOfFirstLiteralInstructionOrThrow(literal) + 2 val insertRegister = getInstruction(insertIndex).registerA addInstructions( insertIndex + 1, """ - invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->overrideSeekbarColor(I)I + invoke-static {v$insertRegister}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getVideoPlayerSeekbarColor(I)I move-result v$insertRegister """ ) } - playerSeekbarColorFingerprint.methodOrThrow().apply { - hookSeekbarColor(inlineTimeBarColorizedBarPlayedColorDark) - hookSeekbarColor(inlineTimeBarPlayedNotHighlightedColor) + addColorChangeInstructions(inlineTimeBarColorizedBarPlayedColorDark) + addColorChangeInstructions(inlineTimeBarPlayedNotHighlightedColor) } shortsSeekbarColorFingerprint.methodOrThrow().apply { - hookSeekbarColor(reelTimeBarPlayedColor) + addColorChangeInstructions(reelTimeBarPlayedColor) } controlsOverlayStyleFingerprint.matchOrThrow().let { @@ -187,16 +235,68 @@ val seekbarComponentsPatch = bytecodePatch( addInstructions( 0, """ - invoke-static {v$colorRegister}, $PLAYER_CLASS_DESCRIPTOR->getSeekbarClickedColorValue(I)I + invoke-static {v$colorRegister}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getVideoPlayerSeekbarClickedColor(I)I move-result v$colorRegister """ ) } } - addDrawableColorHook("$PLAYER_CLASS_DESCRIPTOR->getColor(I)I") + addDrawableColorHook("$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getLithoColor(I)I") - getContext().document("res/drawable/resume_playback_progressbar_drawable.xml") + if (is_19_25_or_greater) { + playerSeekbarGradientConfigFingerprint.injectLiteralInstructionBooleanCall( + PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG, + "$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z" + ) + + lithoLinearGradientFingerprint.methodOrThrow().addInstruction( + 0, + "invoke-static/range { p4 .. p5 }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->setLinearGradient([I[F)V" + ) + + // Don't use the lotte splash screen layout if using custom seekbar. + arrayOf( + launchScreenLayoutTypeFingerprint.methodOrThrow(), + onCreateMethod + ).forEach { method -> + method.apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(launchScreenLayoutTypeLotteFeatureFlag) + val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) + val register = getInstruction(resultIndex).registerA + + addInstructions( + resultIndex + 1, + """ + invoke-static { v$register }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z + move-result v$register + """ + ) + } + } + + // Hook the splash animation drawable to set the a seekbar color theme. + onCreateMethod.apply { + val drawableIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Landroid/widget/ImageView;" && + reference.name == "getDrawable" + } + val checkCastIndex = indexOfFirstInstructionOrThrow(drawableIndex, Opcode.CHECK_CAST) + val drawableRegister = getInstruction(checkCastIndex).registerA + + addInstruction( + checkCastIndex + 1, + "invoke-static { v$drawableRegister }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->" + + "setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V" + ) + } + + } + + val context = getContext() + + context.document("res/drawable/resume_playback_progressbar_drawable.xml") .use { document -> val layerList = document.getElementsByTagName("layer-list").item(0) as Element val progressNode = layerList.getElementsByTagName("item").item(1) as Element @@ -211,6 +311,63 @@ val seekbarComponentsPatch = bytecodePatch( scaleNode.replaceChild(replacementNode, shapeNode) } + if (is_19_25_or_greater) { + // Add attribute and styles for splash screen custom color. + // Using a style is the only way to selectively change just the seekbar fill color. + // + // Because the style colors must be hard coded for all color possibilities, + // instead of allowing 24 bit color the style is restricted to 9-bit (3 bits per color channel) + // and the style color closest to the users custom color is used for the splash screen. + arrayOf( + inputStreamFromBundledResource("youtube/seekbar/values", "attrs.xml")!! to "res/values/attrs.xml", + ByteArrayInputStream(create9BitSeekbarColorStyles().toByteArray()) to "res/values/styles.xml" + ).forEach { (source, destination) -> + "resources".copyXmlNode( + context.document(source), + context.document(destination), + ).close() + } + + fun setSplashDrawablePathFillColor(xmlFileNames: Iterable, vararg resourceNames: String) { + xmlFileNames.forEach { xmlFileName -> + context.document(xmlFileName).use { document -> + resourceNames.forEach { elementId -> + val element = document.childNodes.findElementByAttributeValueOrThrow( + "android:name", + elementId + ) + + val attribute = "android:fillColor" + if (!element.hasAttribute(attribute)) { + throw PatchException("Could not find $attribute for $elementId") + } + + element.setAttribute(attribute, "?attr/$splashSeekbarColorAttributeName") + } + } + } + } + + setSplashDrawablePathFillColor( + listOf( + "res/drawable/\$startup_animation_light__0.xml", + "res/drawable/\$startup_animation_dark__0.xml" + ), + "_R_G_L_10_G_D_0_P_0" + ) + + if (!is_19_46_or_greater) { + // Resources removed in 19.46+ + setSplashDrawablePathFillColor( + listOf( + "res/drawable/\$buenos_aires_animation_light__0.xml", + "res/drawable/\$buenos_aires_animation_dark__0.xml" + ), + "_R_G_L_8_G_D_0_P_0" + ) + } + } + // endregion // region patch for high quality thumbnails diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt index 6e42b8a41..6ab15d409 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt @@ -43,6 +43,8 @@ var is_19_43_or_greater = false private set var is_19_44_or_greater = false private set +var is_19_46_or_greater = false + private set val versionCheckPatch = resourcePatch( description = "versionCheckPatch", @@ -77,5 +79,6 @@ val versionCheckPatch = resourcePatch( is_19_41_or_greater = 244305000 <= playStoreServicesVersion is_19_43_or_greater = 244405000 <= playStoreServicesVersion is_19_44_or_greater = 244505000 <= playStoreServicesVersion + is_19_46_or_greater = 244705000 <= playStoreServicesVersion } } diff --git a/patches/src/main/resources/youtube/seekbar/values/attrs.xml b/patches/src/main/resources/youtube/seekbar/values/attrs.xml new file mode 100644 index 000000000..2bf349f0d --- /dev/null +++ b/patches/src/main/resources/youtube/seekbar/values/attrs.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index c05f0c50a..3a236f234 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1112,6 +1112,7 @@ Tap and hold to toggle the appended information type." Custom seekbar color is disabled. Custom seekbar color value Type the hex code of the seekbar color. + Invalid seekbar color value. Enable seekbar tapping Seekbar tapping is enabled. Seekbar tapping is disabled.