fix(YouTube - Player controls): Show player control buttons with A/B layout (#3901)

This commit is contained in:
LisoUseInAIKyrios 2024-11-12 10:07:32 +04:00 committed by GitHub
parent 53cb19f4a9
commit bb526bc00a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 106 additions and 5 deletions

View File

@ -53,4 +53,18 @@ public final class EnableDebuggingPatch {
return value; return value;
} }
/**
* Injection point.
*/
public static String isStringFeatureFlagEnabled(String value, long flag, String defaultValue) {
if (BaseSettings.DEBUG.get() && !defaultValue.equals(value)) {
if (featureFlags.putIfAbsent(flag, true) == null) {
Logger.printDebug(() -> " string feature is enabled: " + flag
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
}
}
return value;
}
} }

View File

@ -8,6 +8,7 @@ import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlayerControlsPatch { public class PlayerControlsPatch {
/** /**
* Injection point. * Injection point.
*/ */
@ -41,4 +42,11 @@ public class PlayerControlsPatch {
public static void fullscreenButtonVisibilityChanged(boolean isVisible) { public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
// Code added during patching. // Code added during patching.
} }
/**
* Injection point.
*/
public static String getPlayerTopControlsLayoutResourceName(String original) {
return "default";
}
} }

View File

@ -1444,10 +1444,12 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I

View File

@ -11,6 +11,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
@ -105,7 +106,23 @@ val enableDebuggingPatch = bytecodePatch(
) )
} }
// There exists other experimental accessor methods for String, byte[], and wrappers for obfuscated classes, experimentalStringFeatureFlagFingerprint.match(
// but currently none of those are hooked. experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply {
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
addInstructions(
insertIndex,
"""
move-result-object v0
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
move-result-object v0
return-object v0
"""
)
}
// There exists other experimental accessor methods for byte[]
// and wrappers for obfuscated classes, but currently none of those are hooked.
} }
} }

View File

@ -28,3 +28,9 @@ internal val experimentalLongFeatureFlagFingerprint = fingerprint {
parameters("J", "J") parameters("J", "J")
} }
internal val experimentalStringFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Ljava/lang/String;")
parameters("J", "Ljava/lang/String;")
}

View File

@ -47,10 +47,17 @@ internal val controlsOverlayVisibilityFingerprint = fingerprint {
parameters("Z", "Z") parameters("Z", "Z")
} }
internal val playerControlsExploderFeatureFlagFingerprint = fingerprint { internal val playerBottomControlsExploderFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z") returns("Z")
parameters() parameters()
literal { 45643739L } literal { 45643739L }
} }
internal val playerTopControlsExperimentalLayoutFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("I")
parameters()
literal { 45629424L }
}

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.playercontrols package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@ -10,6 +11,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater
import app.revanced.util.* import app.revanced.util.*
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -263,12 +265,36 @@ val playerControlsPatch = bytecodePatch(
visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method
// A/B test for a slightly different overlay controls, // A/B test for a slightly different bottom overlay controls,
// that uses layout file youtube_video_exploder_controls_bottom_ui_container.xml // that uses layout file youtube_video_exploder_controls_bottom_ui_container.xml
// The change to support this is simple and only requires adding buttons to both layout files, // The change to support this is simple and only requires adding buttons to both layout files,
// but for now force this different layout off since it's still an experimental test. // but for now force this different layout off since it's still an experimental test.
if (is_19_35_or_greater) { if (is_19_35_or_greater) {
playerControlsExploderFeatureFlagFingerprint.method.returnEarly() playerBottomControlsExploderFeatureFlagFingerprint.method.returnEarly()
}
// A/B test of new top overlay controls. Two different layouts can be used:
// youtube_cf_navigation_improvement_controls_layout.xml
// youtube_cf_minimal_impact_controls_layout.xml
//
// Visually there is no noticeable difference between either of these compared to the default.
// There is additional logic that is active when youtube_cf_navigation_improvement_controls_layout
// is active, but what it does is not entirely clear.
//
// For now force this a/b feature off as it breaks the top player buttons.
if (is_19_25_or_greater) {
playerTopControlsExperimentalLayoutFeatureFlagFingerprint.method.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_OBJECT)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getPlayerTopControlsLayoutResourceName(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$register
"""
)
}
} }
} }
} }

View File

@ -309,6 +309,17 @@ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, filter: Inst
return instructions.indexOfLast(filter) return instructions.indexOfLast(filter)
} }
/**
* Get the index of matching instruction,
* starting from the end of the method and searching down.
*
* @return -1 if the instruction is not found.
*/
fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int =
indexOfFirstInstructionReversed {
opcode == targetOpcode
}
/** /**
* Get the index of matching instruction, * Get the index of matching instruction,
* starting from and [startIndex] and searching down. * starting from and [startIndex] and searching down.
@ -322,6 +333,16 @@ fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targe
opcode == targetOpcode opcode == targetOpcode
} }
/**
* Get the index of matching instruction,
* starting from the end of the method and searching down.
*
* @return -1 if the instruction is not found.
*/
fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int =
indexOfFirstInstructionReversedOrThrow {
opcode == targetOpcode
}
/** /**
* Get the index of matching instruction, * Get the index of matching instruction,
* starting from and [startIndex] and searching down. * starting from and [startIndex] and searching down.