mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-30 06:34:28 +02:00
fix(YouTube - Change form factor): Restore Automotive form factor watch history menu, channel pages, and community posts (#4541)
This commit is contained in:
parent
5bd2e86afe
commit
aa5c001968
@ -1,9 +1,17 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ChangeFormFactorPatch {
|
public class ChangeFormFactorPatch {
|
||||||
@ -41,14 +49,57 @@ public class ChangeFormFactorPatch {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
||||||
|
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
|
||||||
|
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getFormFactor(int original) {
|
public static int getFormFactor(int original) {
|
||||||
return FORM_FACTOR_TYPE == null
|
if (FORM_FACTOR_TYPE == null) return original;
|
||||||
? original
|
|
||||||
: FORM_FACTOR_TYPE;
|
if (USING_AUTOMOTIVE_TYPE) {
|
||||||
|
// Do not change if the player is opening or is opened,
|
||||||
|
// otherwise the video description cannot be opened.
|
||||||
|
PlayerType current = PlayerType.getCurrent();
|
||||||
|
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
|
||||||
|
Logger.printDebug(() -> "Using original form factor for player");
|
||||||
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!NavigationBar.isSearchBarActive()) {
|
||||||
|
// Automotive type shows error 400 when opening a channel page and using some explore tab.
|
||||||
|
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
|
||||||
|
// Work around the issue by using the original form factor if not in search and the
|
||||||
|
// navigation back button is present.
|
||||||
|
if (NavigationBar.isBackButtonVisible()) {
|
||||||
|
Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not change library tab otherwise watch history is hidden.
|
||||||
|
// Do this check last since the current navigation button is required.
|
||||||
|
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FORM_FACTOR_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||||
|
// On first startup of the app the navigation buttons are fetched and updated.
|
||||||
|
// If the user immediately opens the 'You' or opens a video, then the call to
|
||||||
|
// update the navigtation buttons will use the non automotive form factor
|
||||||
|
// and the explore tab is missing.
|
||||||
|
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
||||||
|
// For now, always hide the explore tab.
|
||||||
|
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
|
||||||
|
tabView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,7 +3,9 @@ package app.revanced.extension.youtube.shared;
|
|||||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@ -24,12 +26,22 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class NavigationBar {
|
public final class NavigationBar {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to call obfuscated methods in AppCompat Toolbar class.
|
||||||
|
*/
|
||||||
|
public interface AppCompatToolbarPatchInterface {
|
||||||
|
Drawable patch_getNavigationIcon();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Search bar
|
// Search and toolbar.
|
||||||
//
|
//
|
||||||
|
|
||||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
private static volatile WeakReference<AppCompatToolbarPatchInterface> toolbarResultsRef
|
||||||
|
= new WeakReference<>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@ -37,6 +49,22 @@ public final class NavigationBar {
|
|||||||
searchBarResultsRef = new WeakReference<>(searchbarResults);
|
searchBarResultsRef = new WeakReference<>(searchbarResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setToolbar(FrameLayout layout) {
|
||||||
|
AppCompatToolbarPatchInterface toolbar = Utils.getChildView(layout, false, (view) ->
|
||||||
|
view instanceof AppCompatToolbarPatchInterface
|
||||||
|
);
|
||||||
|
|
||||||
|
if (toolbar == null) {
|
||||||
|
Logger.printException(() -> "Could not find navigation toolbar");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbarResultsRef = new WeakReference<>(toolbar);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the search bar is on screen. This includes if the player
|
* @return If the search bar is on screen. This includes if the player
|
||||||
* is on screen and the search results are behind the player (and not visible).
|
* is on screen and the search results are behind the player (and not visible).
|
||||||
@ -47,8 +75,13 @@ public final class NavigationBar {
|
|||||||
return searchbarResults != null && searchbarResults.getParent() != null;
|
return searchbarResults != null && searchbarResults.getParent() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBackButtonVisible() {
|
||||||
|
AppCompatToolbarPatchInterface toolbar = toolbarResultsRef.get();
|
||||||
|
return toolbar != null && toolbar.patch_getNavigationIcon() != null;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Navigation bar buttons
|
// Navigation bar buttons.
|
||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,7 +6,9 @@ import app.revanced.patcher.patch.bytecodePatch
|
|||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||||
|
import app.revanced.patches.youtube.layout.buttons.navigation.navigationButtonsPatch
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
|
||||||
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.getReference
|
import app.revanced.util.getReference
|
||||||
@ -15,7 +17,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
|||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
|
||||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val changeFormFactorPatch = bytecodePatch(
|
val changeFormFactorPatch = bytecodePatch(
|
||||||
@ -26,6 +28,7 @@ val changeFormFactorPatch = bytecodePatch(
|
|||||||
sharedExtensionPatch,
|
sharedExtensionPatch,
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
|
navigationButtonsPatch
|
||||||
)
|
)
|
||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
@ -50,6 +53,8 @@ val changeFormFactorPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
|
||||||
|
|
||||||
createPlayerRequestBodyWithModelFingerprint.method.apply {
|
createPlayerRequestBodyWithModelFingerprint.method.apply {
|
||||||
val formFactorEnumClass = formFactorEnumConstructorFingerprint.originalClassDef.type
|
val formFactorEnumClass = formFactorEnumConstructorFingerprint.originalClassDef.type
|
||||||
|
|
||||||
|
@ -16,6 +16,23 @@ internal val actionBarSearchResultsFingerprint = fingerprint {
|
|||||||
literal { actionBarSearchResultsViewMicId }
|
literal { actionBarSearchResultsViewMicId }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val toolbarLayoutFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PROTECTED, AccessFlags.CONSTRUCTOR)
|
||||||
|
literal { toolbarContainerId }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches to https://android.googlesource.com/platform/frameworks/support/+/9eee6ba/v7/appcompat/src/android/support/v7/widget/Toolbar.java#963
|
||||||
|
*/
|
||||||
|
internal val appCompatToolbarBackButtonFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("Landroid/graphics/drawable/Drawable;")
|
||||||
|
parameters()
|
||||||
|
custom { methodDef, classDef ->
|
||||||
|
classDef.type == "Landroid/support/v7/widget/Toolbar;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches to the class found in [pivotBarConstructorFingerprint].
|
* Matches to the class found in [pivotBarConstructorFingerprint].
|
||||||
*/
|
*/
|
||||||
|
@ -8,6 +8,7 @@ import app.revanced.patcher.patch.PatchException
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
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
|
||||||
@ -18,12 +19,16 @@ import app.revanced.util.getReference
|
|||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
|
||||||
internal var imageOnlyTabResourceId = -1L
|
internal var imageOnlyTabResourceId = -1L
|
||||||
@ -32,6 +37,8 @@ internal var actionBarSearchResultsViewMicId = -1L
|
|||||||
private set
|
private set
|
||||||
internal var ytFillBellId = -1L
|
internal var ytFillBellId = -1L
|
||||||
private set
|
private set
|
||||||
|
internal var toolbarContainerId = -1L
|
||||||
|
private set
|
||||||
|
|
||||||
private val navigationBarHookResourcePatch = resourcePatch {
|
private val navigationBarHookResourcePatch = resourcePatch {
|
||||||
dependsOn(resourceMappingPatch)
|
dependsOn(resourceMappingPatch)
|
||||||
@ -40,6 +47,7 @@ private val navigationBarHookResourcePatch = resourcePatch {
|
|||||||
imageOnlyTabResourceId = resourceMappings["layout", "image_only_tab"]
|
imageOnlyTabResourceId = resourceMappings["layout", "image_only_tab"]
|
||||||
actionBarSearchResultsViewMicId = resourceMappings["layout", "action_bar_search_results_view_mic"]
|
actionBarSearchResultsViewMicId = resourceMappings["layout", "action_bar_search_results_view_mic"]
|
||||||
ytFillBellId = resourceMappings["drawable", "yt_fill_bell_black_24"]
|
ytFillBellId = resourceMappings["drawable", "yt_fill_bell_black_24"]
|
||||||
|
toolbarContainerId = resourceMappings["id", "toolbar_container"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +55,8 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
|
|||||||
"Lapp/revanced/extension/youtube/shared/NavigationBar;"
|
"Lapp/revanced/extension/youtube/shared/NavigationBar;"
|
||||||
internal const val EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR =
|
internal const val EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/shared/NavigationBar\$NavigationButton;"
|
"Lapp/revanced/extension/youtube/shared/NavigationBar\$NavigationButton;"
|
||||||
|
private const val EXTENSION_TOOLBAR_INTERFACE =
|
||||||
|
"Lapp/revanced/extension/youtube/shared/NavigationBar${'$'}AppCompatToolbarPatchInterface;"
|
||||||
|
|
||||||
lateinit var hookNavigationButtonCreated: (String) -> Unit
|
lateinit var hookNavigationButtonCreated: (String) -> Unit
|
||||||
|
|
||||||
@ -143,11 +153,58 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hook the back button visibility.
|
||||||
|
|
||||||
|
toolbarLayoutFingerprint.method.apply {
|
||||||
|
val index = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.CHECK_CAST && getReference<TypeReference>()?.type ==
|
||||||
|
"Lcom/google/android/apps/youtube/app/ui/actionbar/MainCollapsingToolbarLayout;"
|
||||||
|
}
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
addInstruction(
|
||||||
|
index + 1,
|
||||||
|
"invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->setToolbar(Landroid/widget/FrameLayout;)V"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add interface for extensions code to call obfuscated methods.
|
||||||
|
appCompatToolbarBackButtonFingerprint.let {
|
||||||
|
it.classDef.apply {
|
||||||
|
interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
|
||||||
|
|
||||||
|
val definingClass = type
|
||||||
|
val obfuscatedMethodName = it.originalMethod.name
|
||||||
|
val returnType = "Landroid/graphics/drawable/Drawable;"
|
||||||
|
|
||||||
|
methods.add(
|
||||||
|
ImmutableMethod(
|
||||||
|
definingClass,
|
||||||
|
"patch_getNavigationIcon",
|
||||||
|
listOf(),
|
||||||
|
returnType,
|
||||||
|
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
MutableMethodImplementation(2),
|
||||||
|
).toMutable().apply {
|
||||||
|
addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hookNavigationButtonCreated = { extensionClassDescriptor ->
|
hookNavigationButtonCreated = { extensionClassDescriptor ->
|
||||||
navigationBarHookCallbackFingerprint.method.addInstruction(
|
navigationBarHookCallbackFingerprint.method.addInstruction(
|
||||||
0,
|
0,
|
||||||
"invoke-static { p0, p1 }, " +
|
"invoke-static { p0, p1 }, $extensionClassDescriptor->navigationTabCreated" +
|
||||||
"$extensionClassDescriptor->navigationTabCreated" +
|
|
||||||
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
|
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1132,10 +1132,8 @@ Tablet layout
|
|||||||
• Community posts are hidden
|
• Community posts are hidden
|
||||||
|
|
||||||
Automotive layout
|
Automotive layout
|
||||||
• Watch history menu is hidden
|
|
||||||
• Explore tab is restored
|
|
||||||
• Shorts open in the regular player
|
• Shorts open in the regular player
|
||||||
• Feed is organized by topics and channel"</string>
|
• Feed is organized by topics and channels"</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||||
<string name="revanced_spoof_app_version_title">Spoof app version</string>
|
<string name="revanced_spoof_app_version_title">Spoof app version</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user