build(Needs bump)!: Update to ReVanced Patcher v22 (#6542)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: ILoveOpenSourceApplications <ILoveOpenSourceApplications@users.noreply.github.com>
Co-authored-by: rospino74 <34315725+rospino74@users.noreply.github.com>
Co-authored-by: drobotk <pawwwll@gmail.com>
Co-authored-by: Sayanth <13906889+SayanthD@users.noreply.github.com>
Co-authored-by: kitadai31 <90122968+kitadai31@users.noreply.github.com>

BREAKING CHANGE: Deprecated APIs have been removed, and various APIs now use the updated ReVanced Patcher v22 APIs.
This commit is contained in:
oSumAtrIX 2026-02-27 02:39:17 +01:00 committed by GitHub
parent 376f2af8d8
commit ab2ac36e30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
842 changed files with 12691 additions and 13203 deletions

View file

@ -1,3 +0,0 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled

View file

@ -34,7 +34,7 @@ jobs:
- name: Build - name: Build
env: env:
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon run: ./gradlew :patches:buildAndroid --no-daemon

View file

@ -11,6 +11,7 @@ import android.widget.Toolbar;
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
import app.revanced.extension.music.settings.search.MusicSearchViewController; import app.revanced.extension.music.settings.search.MusicSearchViewController;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseActivityHook;
@ -46,15 +47,7 @@ public class MusicActivityHook extends BaseActivityHook {
// Override the default YouTube Music theme to increase start padding of list items. // Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml // Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifierOrThrow( activity.setTheme(Utils.getResourceIdentifierOrThrow(
"Theme.ReVanced.YouTubeMusic.Settings", "style")); ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
} }
/** /**

View file

@ -2,3 +2,9 @@ dependencies {
implementation(project(":extensions:shared:library")) implementation(project(":extensions:shared:library"))
compileOnly(libs.okhttp) compileOnly(libs.okhttp)
} }
android {
defaultConfig {
minSdk = 26
}
}

View file

@ -0,0 +1,57 @@
package app.revanced.extension.shared;
import java.util.HashMap;
import java.util.Map;
public enum ResourceType {
ANIM("anim"),
ANIMATOR("animator"),
ARRAY("array"),
ATTR("attr"),
BOOL("bool"),
COLOR("color"),
DIMEN("dimen"),
DRAWABLE("drawable"),
FONT("font"),
FRACTION("fraction"),
ID("id"),
INTEGER("integer"),
INTERPOLATOR("interpolator"),
LAYOUT("layout"),
MENU("menu"),
MIPMAP("mipmap"),
NAVIGATION("navigation"),
PLURALS("plurals"),
RAW("raw"),
STRING("string"),
STYLE("style"),
STYLEABLE("styleable"),
TRANSITION("transition"),
VALUES("values"),
XML("xml");
private static final Map<String, ResourceType> VALUE_MAP;
static {
ResourceType[] values = values();
VALUE_MAP = new HashMap<>(2 * values.length);
for (ResourceType type : values) {
VALUE_MAP.put(type.value, type);
}
}
public final String value;
public static ResourceType fromValue(String value) {
ResourceType type = VALUE_MAP.get(value);
if (type == null) {
throw new IllegalArgumentException("Unknown resource type: " + value);
}
return type;
}
ResourceType(String value) {
this.value = value;
}
}

View file

@ -32,7 +32,11 @@ import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast; import android.widget.Toast;
import android.widget.Toolbar;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -43,8 +47,10 @@ import java.text.Collator;
import java.text.Normalizer; import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -76,6 +82,8 @@ public class Utils {
@Nullable @Nullable
private static Boolean isDarkModeEnabled; private static Boolean isDarkModeEnabled;
private static boolean appIsUsingBoldIcons;
// Cached Collator instance with its locale. // Cached Collator instance with its locale.
@Nullable @Nullable
private static Locale cachedCollatorLocale; private static Locale cachedCollatorLocale;
@ -148,12 +156,12 @@ public class Utils {
/** /**
* Hide a view by setting its layout height and width to 1dp. * Hide a view by setting its layout height and width to 1dp.
* *
* @param condition The setting to check for hiding the view. * @param setting The setting to check for hiding the view.
* @param view The view to hide. * @param view The view to hide.
*/ */
public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) { public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) {
if (hideViewBy0dpUnderCondition(condition.get(), view)) { if (hideViewBy0dpUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
@ -165,22 +173,47 @@ public class Utils {
*/ */
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) { public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
if (condition) { if (condition) {
hideViewByLayoutParams(view); hideViewBy0dp(view);
return true; return true;
} }
return false; return false;
} }
/**
* Hide a view by setting its layout params to 0x0
* @param view The view to hide.
*/
public static void hideViewBy0dp(View view) {
if (view instanceof LinearLayout) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams);
} else if (view instanceof FrameLayout) {
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams2);
} else if (view instanceof RelativeLayout) {
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams3);
} else if (view instanceof Toolbar) {
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
view.setLayoutParams(layoutParams4);
} else {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = 0;
params.height = 0;
view.setLayoutParams(params);
}
}
/** /**
* Hide a view by setting its visibility to GONE. * Hide a view by setting its visibility to GONE.
* *
* @param condition The setting to check for hiding the view. * @param setting The setting to check for hiding the view.
* @param view The view to hide. * @param view The view to hide.
*/ */
public static void hideViewUnderCondition(BooleanSetting condition, View view) { public static void hideViewUnderCondition(BooleanSetting setting, View view) {
if (hideViewUnderCondition(condition.get(), view)) { if (hideViewUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
@ -199,14 +232,14 @@ public class Utils {
return false; return false;
} }
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) { public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) {
if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) { if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) { public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) {
if (setting) { if (condition) {
ViewParent parent = view.getParent(); ViewParent parent = view.getParent();
if (parent instanceof ViewGroup parentGroup) { if (parent instanceof ViewGroup parentGroup) {
parentGroup.removeView(view); parentGroup.removeView(view);
@ -278,12 +311,13 @@ public class Utils {
* @return zero, if the resource is not found. * @return zero, if the resource is not found.
*/ */
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName()); return context.getResources().getIdentifier(resourceIdentifierName,
type == null ? null : type.value, context.getPackageName());
} }
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type); final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName);
if (resourceId == 0) { if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type); + " type: " + type);
@ -293,22 +327,18 @@ public class Utils {
/** /**
* @return zero, if the resource is not found. * @return zero, if the resource is not found.
* @see #getResourceIdentifierOrThrow(String, String) * @see #getResourceIdentifierOrThrow(ResourceType, String)
*/ */
public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) {
return getResourceIdentifier(getContext(), resourceIdentifierName, type); return getResourceIdentifier(getContext(), type, resourceIdentifierName);
} }
/** /**
* @return The resource identifier, or throws an exception if not found. * @return zero, if the resource is not found.
* @see #getResourceIdentifier(ResourceType, String)
*/ */
public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) {
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type); return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName);
if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type);
}
return resourceId;
} }
public static String getResourceString(int id) throws Resources.NotFoundException { public static String getResourceString(int id) throws Resources.NotFoundException {
@ -316,29 +346,29 @@ public class Utils {
} }
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer")); return getContext().getResources().getInteger(getResourceIdentifierOrThrow(ResourceType.INTEGER, resourceIdentifierName));
} }
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException { public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim")); return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName));
} }
@ColorInt @ColorInt
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
//noinspection deprecation //noinspection deprecation
return getContext().getResources().getColor(getResourceIdentifierOrThrow(resourceIdentifierName, "color")); return getContext().getResources().getColor(getResourceIdentifierOrThrow(ResourceType.COLOR, resourceIdentifierName));
} }
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
} }
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException { public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); return getContext().getResources().getDimension(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
} }
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException { public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array")); return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName));
} }
public interface MatchFilter<T> { public interface MatchFilter<T> {
@ -349,7 +379,7 @@ public class Utils {
* Includes sub children. * Includes sub children.
*/ */
public static <R extends View> R getChildViewByResourceName(View view, String str) { public static <R extends View> R getChildViewByResourceName(View view, String str) {
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(str, "id")); var child = view.findViewById(Utils.getResourceIdentifierOrThrow(ResourceType.ID, str));
//noinspection unchecked //noinspection unchecked
return (R) child; return (R) child;
} }
@ -806,6 +836,21 @@ public class Utils {
window.setBackgroundDrawable(null); // Remove default dialog background window.setBackgroundDrawable(null); // Remove default dialog background
} }
/**
* @return If the unpatched app is currently using bold icons.
*/
public static boolean appIsUsingBoldIcons() {
return appIsUsingBoldIcons;
}
/**
* Controls if ReVanced bold icons are shown in various places.
* @param boldIcons If the app is currently using bold icons.
*/
public static void setAppIsUsingBoldIcons(boolean boldIcons) {
appIsUsingBoldIcons = boldIcons;
}
/** /**
* Sets the theme light color used by the app. * Sets the theme light color used by the app.
*/ */
@ -1167,4 +1212,18 @@ public class Utils {
public static float clamp(float value, float lower, float upper) { public static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper)); return Math.max(lower, Math.min(value, upper));
} }
/**
* @param maxSize The maximum number of elements to keep in the map.
* @return A {@link LinkedHashMap} that automatically evicts the oldest entry
* when the size exceeds {@code maxSize}.
*/
public static <T, V> Map<T, V> createSizeRestrictedMap(int maxSize) {
return new LinkedHashMap<>(2 * maxSize) {
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > maxSize;
}
};
}
} }

View file

@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@ -128,7 +129,7 @@ abstract class Check {
// Add icon to the dialog. // Add icon to the dialog.
ImageView iconView = new ImageView(activity); ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifierOrThrow( iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
"revanced_ic_dialog_alert", "drawable")); ResourceType.DRAWABLE, "revanced_ic_dialog_alert"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0); iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(

View file

@ -15,7 +15,6 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
public abstract class BaseFixRedgifsApiPatch implements Interceptor { public abstract class BaseFixRedgifsApiPatch implements Interceptor {
protected static BaseFixRedgifsApiPatch INSTANCE; protected static BaseFixRedgifsApiPatch INSTANCE;
public abstract String getDefaultUserAgent(); public abstract String getDefaultUserAgent();

View file

@ -13,6 +13,7 @@ import java.util.Locale;
import app.revanced.extension.shared.GmsCoreSupport; import app.revanced.extension.shared.GmsCoreSupport;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
@ -65,7 +66,7 @@ public class CustomBrandingPatch {
iconName += "_custom"; iconName += "_custom";
} }
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable"); notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
if (notificationSmallIcon == 0) { if (notificationSmallIcon == 0) {
Logger.printException(() -> "Could not load notification small icon"); Logger.printException(() -> "Could not load notification small icon");
} }

View file

@ -1,4 +1,4 @@
package app.revanced.extension.shared.patches.components; package app.revanced.extension.shared.patches.litho;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
@ -17,7 +17,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.Filter;
/** /**
* Allows custom filtering using a path and optionally a proto buffer string. * Allows custom filtering using a path and optionally a proto buffer string.
@ -148,7 +147,7 @@ public final class CustomFilter extends Filter {
@Override @Override
public boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups. // All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup; CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
if (custom.startsWith && contentIndex != 0) { if (custom.startsWith && contentIndex != 0) {

View file

@ -1,12 +1,12 @@
package app.revanced.extension.shared.patches.litho; package app.revanced.extension.shared.patches.litho;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
/** /**
* Filters litho based components. * Filters litho based components.
* *
@ -33,12 +33,12 @@ public abstract class Filter {
* Identifier callbacks. Do not add to this instance, * Identifier callbacks. Do not add to this instance,
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}. * and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
*/ */
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>(); public final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
/** /**
* Path callbacks. Do not add to this instance, * Path callbacks. Do not add to this instance,
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}. * and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
*/ */
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>(); public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
/** /**
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} * Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}

View file

@ -55,7 +55,7 @@ public abstract class FilterGroup<T> {
} }
protected final BooleanSetting setting; protected final BooleanSetting setting;
protected final T[] filters; public final T[] filters;
/** /**
* Initialize a new filter group. * Initialize a new filter group.

View file

@ -1,15 +1,17 @@
package app.revanced.extension.shared.patches.litho; package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.*;
import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch; import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> { public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>(); private final List<T> filterGroups = new ArrayList<>();

View file

@ -4,14 +4,17 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class LithoFilterPatch { public final class LithoFilterPatch {
@ -34,7 +37,7 @@ public final class LithoFilterPatch {
public String toString() { public String toString() {
// Estimate the percentage of the buffer that are Strings. // Estimate the percentage of the buffer that are Strings.
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2)); StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
builder.append( "ID: "); builder.append("ID: ");
builder.append(identifier); builder.append(identifier);
builder.append(" Path: "); builder.append(" Path: ");
builder.append(path); builder.append(path);
@ -75,6 +78,16 @@ public final class LithoFilterPatch {
} }
} }
/**
* Placeholder for actual filters.
*/
private static final class DummyFilter extends Filter {
}
private static final Filter[] filters = new Filter[]{
new DummyFilter() // Replaced during patching, do not touch.
};
/** /**
* Litho layout fixed thread pool size override. * Litho layout fixed thread pool size override.
* <p> * <p>
@ -92,25 +105,55 @@ public final class LithoFilterPatch {
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1; private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
/** /**
* Placeholder for actual filters. * For YouTube 20.22+, this is set to true by a patch,
* because it cannot use the thread buffer due to the buffer frequently not being correct,
* especially for components that are recreated such as dragging off-screen then back on screen.
* Instead, parse the identifier found near the start of the buffer and use that to
* identify the correct buffer to use when filtering.
* <p>
* <b>This is set during patching, do not change manually.</b>
*/ */
private static final class DummyFilter extends Filter { } private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = false;
private static final Filter[] filters = new Filter[] { /**
new DummyFilter() // Replaced patching, do not touch. * Turns on additional logging, used for development purposes only.
}; */
public static final boolean DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER = false;
private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); /**
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); * String suffix for components.
* Can be any of: ".eml", ".e-b", ".eml-js", "e-js-b"
*/
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
/**
* Used as placeholder for litho id/path filters that do not use a buffer
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** /**
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point, * Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads. * the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
* Used for 20.21 and lower.
*/ */
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>(); private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
/**
* Identifier to protocol buffer mapping. Only used for 20.22+.
* Thread local is needed because filtering is multi-threaded and each thread can load
* a different component with the same identifier.
*/
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
/**
* Global shared buffer. Used only if the buffer is not found in the ThreadLocal.
*/
private static final Map<String, byte[]> identifierToBufferGlobal
= Collections.synchronizedMap(createIdentifierToBufferMap());
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
static { static {
for (Filter filter : filters) { for (Filter filter : filters) {
filterUsingCallbacks(identifierSearchTree, filter, filterUsingCallbacks(identifierSearchTree, filter,
@ -162,16 +205,107 @@ public final class LithoFilterPatch {
} }
} }
private static Map<String, byte[]> createIdentifierToBufferMap() {
// It's unclear how many items should be cached. This is a guess.
return Utils.createSizeRestrictedMap(100);
}
/**
* Helper function that differs from {@link Character#isDigit(char)}
* as this only matches ascii and not unicode numbers.
*/
private static boolean isAsciiNumber(byte character) {
return '0' <= character && character <= '9';
}
private static boolean isAsciiLowerCaseLetter(byte character) {
return 'a' <= character && character <= 'z';
}
/** /**
* Injection point. Called off the main thread. * Injection point. Called off the main thread.
* Targets 20.22+ * Targets 20.22+
*/ */
public static void setProtoBuffer(byte[] buffer) { public static void setProtoBuffer(byte[] buffer) {
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes. if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
// This is intentional, as it appears the buffer can be set once and then filtered multiple times. StringBuilder builder = new StringBuilder();
// The buffer will be cleared from memory after a new buffer is set by the same thread, LithoFilterParameters.findAsciiStrings(builder, buffer);
// or when the calling thread eventually dies. Logger.printDebug(() -> "New buffer: " + builder);
bufferThreadLocal.set(buffer); }
// Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of
// unique characters, but it seems to be slower since the extra overhead of checking the
// bad character array negates any performance gain of skipping a few extra subsearches.
int emlIndex = -1;
final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length;
for (int i = 0, lastStartIndex = buffer.length - emlStringLength; i <= lastStartIndex; i++) {
boolean match = true;
for (int j = 0; j < emlStringLength; j++) {
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
match = false;
break;
}
}
if (match) {
emlIndex = i;
break;
}
}
if (emlIndex < 0) {
// Buffer is not used for creating a new litho component.
return;
}
int startIndex = emlIndex - 1;
while (startIndex > 0) {
final byte character = buffer[startIndex];
int startIndexFinal = startIndex;
if (isAsciiLowerCaseLetter(character) || isAsciiNumber(character) || character == '_') {
// Valid character for the first path element.
startIndex--;
} else {
startIndex++;
break;
}
}
// Strip away any numbers on the start of the identifier, which can
// be from random data in the buffer before the identifier starts.
while (true) {
final byte character = buffer[startIndex];
if (isAsciiNumber(character)) {
startIndex++;
} else {
break;
}
}
// Find the pipe character after the identifier.
int endIndex = -1;
for (int i = emlIndex, length = buffer.length; i < length; i++) {
if (buffer[i] == '|') {
endIndex = i;
break;
}
}
if (endIndex < 0) {
Logger.printException(() -> "Could not find buffer identifier");
return;
}
String identifier = new String(buffer, startIndex, endIndex - startIndex, StandardCharsets.US_ASCII);
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
Logger.printDebug(() -> "Found buffer for identifier: " + identifier);
}
identifierToBufferGlobal.put(identifier, buffer);
Map<String, byte[]> map = identifierToBufferThread.get();
if (map == null) {
map = createIdentifierToBufferMap();
identifierToBufferThread.set(map);
}
map.put(identifier, buffer);
} }
/** /**
@ -179,46 +313,70 @@ public final class LithoFilterPatch {
* Targets 20.21 and lower. * Targets 20.21 and lower.
*/ */
public static void setProtoBuffer(@Nullable ByteBuffer buffer) { public static void setProtoBuffer(@Nullable ByteBuffer buffer) {
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
// The buffer will be cleared from memory after a new buffer is set by the same thread,
// or when the calling thread eventually dies.
if (buffer == null || !buffer.hasArray()) { if (buffer == null || !buffer.hasArray()) {
// It appears the buffer can be cleared out just before the call to #filter() // It appears the buffer can be cleared out just before the call to #filter()
// Ignore this null value and retain the last buffer that was set. // Ignore this null value and retain the last buffer that was set.
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer); Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
} else { } else {
setProtoBuffer(buffer.array()); // Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
// The buffer will be cleared from memory after a new buffer is set by the same thread,
// or when the calling thread eventually dies.
bufferThreadLocal.set(buffer.array());
} }
} }
/** /**
* Injection point. * Injection point.
*/ */
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) { public static boolean isFiltered(String identifier, StringBuilder pathBuilder) {
try { try {
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) { if (identifier.isEmpty() || pathBuilder.length() == 0) {
return false; return false;
} }
byte[] buffer = bufferThreadLocal.get(); byte[] buffer = null;
if (EXTRACT_IDENTIFIER_FROM_BUFFER) {
final int pipeIndex = identifier.indexOf('|');
if (pipeIndex >= 0) {
// If the identifier contains no pipe, then it's not an ".eml" identifier
// and the buffer is not uniquely identified. Typically this only happens
// for subcomponents where buffer filtering is not used.
String identifierKey = identifier.substring(0, pipeIndex);
var map = identifierToBufferThread.get();
if (map != null) {
buffer = map.get(identifierKey);
}
if (buffer == null) {
// Buffer for thread local not found. Use the last buffer found from any thread.
buffer = identifierToBufferGlobal.get(identifierKey);
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER && buffer == null) {
// No buffer is found for some components, such as
// shorts_lockup_cell.eml on channel profiles.
// For now, just ignore this and filter without a buffer.
Logger.printException(() -> "Could not find global buffer for identifier: " + identifier);
}
}
}
} else {
buffer = bufferThreadLocal.get();
}
// Potentially the buffer may have been null or never set up until now. // Potentially the buffer may have been null or never set up until now.
// Use an empty buffer so the litho id/path filters still work correctly. // Use an empty buffer so the litho id/path filters that do not use a buffer still work.
if (buffer == null) { if (buffer == null) {
buffer = EMPTY_BYTE_ARRAY; buffer = EMPTY_BYTE_ARRAY;
} }
LithoFilterParameters parameter = new LithoFilterParameters( String path = pathBuilder.toString();
lithoIdentifier, pathBuilder.toString(), buffer); LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, buffer);
Logger.printDebug(() -> "Searching " + parameter); Logger.printDebug(() -> "Searching " + parameter);
if (identifierSearchTree.matches(parameter.identifier, parameter)) { return identifierSearchTree.matches(identifier, parameter)
return true; || pathSearchTree.matches(path, parameter);
}
if (pathSearchTree.matches(parameter.path, parameter)) {
return true;
}
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "isFiltered failure", ex); Logger.printException(() -> "isFiltered failure", ex);
} }

View file

@ -13,6 +13,7 @@ import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
import app.revanced.extension.shared.ui.Dim; import app.revanced.extension.shared.ui.Dim;
@ -25,13 +26,13 @@ import app.revanced.extension.shared.ui.Dim;
public abstract class BaseActivityHook extends Activity { public abstract class BaseActivityHook extends Activity {
private static final int ID_REVANCED_SETTINGS_FRAGMENTS = private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
getResourceIdentifierOrThrow("revanced_settings_fragments", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_settings_fragments");
private static final int ID_REVANCED_TOOLBAR_PARENT = private static final int ID_REVANCED_TOOLBAR_PARENT =
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent");
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR = public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
getResourceIdentifierOrThrow("revanced_settings_with_toolbar", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_settings_with_toolbar");
private static final int STRING_REVANCED_SETTINGS_TITLE = private static final int STRING_REVANCED_SETTINGS_TITLE =
getResourceIdentifierOrThrow("revanced_settings_title", "string"); getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title");
/** /**
* Layout parameters for the toolbar, extracted from the dummy toolbar. * Layout parameters for the toolbar, extracted from the dummy toolbar.
@ -123,16 +124,18 @@ public abstract class BaseActivityHook extends Activity {
toolBarParent.addView(toolbar, 0); toolBarParent.addView(toolbar, 0);
} }
/**
* Returns the resource ID for the content view layout.
*/
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
}
/** /**
* Customizes the activity's theme. * Customizes the activity's theme.
*/ */
protected abstract void customizeActivityTheme(Activity activity); protected abstract void customizeActivityTheme(Activity activity);
/**
* Returns the resource ID for the content view layout.
*/
protected abstract int getContentViewResourceId();
/** /**
* Returns the background color for the toolbar. * Returns the background color for the toolbar.
*/ */

View file

@ -5,6 +5,8 @@ import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme; import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.Logger;
/** /**
* Settings shared across multiple apps. * Settings shared across multiple apps.
* <p> * <p>
@ -24,10 +26,19 @@ public class BaseSettings {
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing. * Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
*/ */
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true); public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
/**
* Do not use this setting directly. Instead use {@link app.revanced.extension.shared.Utils#appIsUsingBoldIcons()}
*/
public static final BooleanSetting SETTINGS_DISABLE_BOLD_ICONS = new BooleanSetting("revanced_settings_disable_bold_icons", FALSE, true);
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true); public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", ""); public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
/**
* The first time the app was launched with no previous app data (either a clean install, or after wiping app data).
*/
public static final LongSetting FIRST_TIME_APP_LAUNCHED = new LongSetting("revanced_last_time_app_was_launched", -1L, false, false);
public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true); public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true);
// //
@ -46,4 +57,13 @@ public class BaseSettings {
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true); public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG)); public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
static {
final long now = System.currentTimeMillis();
if (FIRST_TIME_APP_LAUNCHED.get() < 0) {
Logger.printInfo(() -> "First launch of installation with no prior app data");
FIRST_TIME_APP_LAUNCHED.save(now);
}
}
} }

View file

@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
@ -103,10 +104,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* so all app specific {@link Setting} instances are loaded before this method returns. * so all app specific {@link Setting} instances are loaded before this method returns.
*/ */
protected void initialize() { protected void initialize() {
String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get() String preferenceResourceName;
? "revanced_prefs_icons" if (BaseSettings.SHOW_MENU_ICONS.get()) {
: "revanced_prefs"; preferenceResourceName = Utils.appIsUsingBoldIcons()
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml"); ? "revanced_prefs_icons_bold"
: "revanced_prefs_icons";
} else {
preferenceResourceName = "revanced_prefs";
}
final var identifier = Utils.getResourceIdentifier(ResourceType.XML, preferenceResourceName);
if (identifier == 0) return; if (identifier == 0) return;
addPreferencesFromResource(identifier); addPreferencesFromResource(identifier);

View file

@ -31,6 +31,7 @@ import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
@ -81,13 +82,13 @@ public class ColorPickerPreference extends EditTextPreference {
private boolean opacitySliderEnabled = false; private boolean opacitySliderEnabled = false;
public static final int ID_REVANCED_COLOR_PICKER_VIEW = public static final int ID_REVANCED_COLOR_PICKER_VIEW =
getResourceIdentifierOrThrow("revanced_color_picker_view", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_color_picker_view");
public static final int ID_PREFERENCE_COLOR_DOT = public static final int ID_PREFERENCE_COLOR_DOT =
getResourceIdentifierOrThrow("preference_color_dot", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot");
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET = public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
getResourceIdentifierOrThrow("revanced_color_dot_widget", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_dot_widget");
public static final int LAYOUT_REVANCED_COLOR_PICKER = public static final int LAYOUT_REVANCED_COLOR_PICKER =
getResourceIdentifierOrThrow("revanced_color_picker", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker");
/** /**
* Removes non valid hex characters, converts to all uppercase, * Removes non valid hex characters, converts to all uppercase,

View file

@ -20,6 +20,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@ -30,14 +31,18 @@ import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class CustomDialogListPreference extends ListPreference { public class CustomDialogListPreference extends ListPreference {
public static final int ID_REVANCED_CHECK_ICON = public static final int ID_REVANCED_CHECK_ICON = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_check_icon", "id"); ResourceType.ID, "revanced_check_icon");
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_check_icon_placeholder", "id"); ResourceType.ID, "revanced_check_icon_placeholder");
public static final int ID_REVANCED_ITEM_TEXT = public static final int ID_REVANCED_ITEM_TEXT = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_item_text", "id"); ResourceType.ID, "revanced_item_text");
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_custom_list_item_checked", "layout"); ResourceType.LAYOUT, "revanced_custom_list_item_checked");
public static final int DRAWABLE_CHECKMARK = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark");
public static final int DRAWABLE_CHECKMARK_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark_bold");
private String staticSummary = null; private String staticSummary = null;
private CharSequence[] highlightedEntriesForDialog = null; private CharSequence[] highlightedEntriesForDialog = null;
@ -125,9 +130,13 @@ public class CustomDialogListPreference extends ListPreference {
LayoutInflater inflater = LayoutInflater.from(getContext()); LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(layoutResourceId, parent, false); view = inflater.inflate(layoutResourceId, parent, false);
holder = new SubViewDataContainer(); holder = new SubViewDataContainer();
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER); holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER);
holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT); holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT);
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
holder.checkIcon.setImageResource(Utils.appIsUsingBoldIcons()
? DRAWABLE_CHECKMARK_BOLD
: DRAWABLE_CHECKMARK
);
view.setTag(holder); view.setTag(holder);
} else { } else {
holder = (SubViewDataContainer) view.getTag(); holder = (SubViewDataContainer) view.getTag();

View file

@ -38,6 +38,7 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.patches.EnableDebuggingPatch; import app.revanced.extension.shared.patches.EnableDebuggingPatch;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
@ -52,25 +53,26 @@ import app.revanced.extension.shared.ui.Dim;
public class FeatureFlagsManagerPreference extends Preference { public class FeatureFlagsManagerPreference extends Preference {
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL = private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
getResourceIdentifierOrThrow("revanced_settings_select_all", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all");
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL = private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
getResourceIdentifierOrThrow("revanced_settings_deselect_all", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all");
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL = private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
getResourceIdentifierOrThrow("revanced_settings_copy_all", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE = private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
getResourceIdentifierOrThrow("revanced_settings_arrow_right_one", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE = private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
getResourceIdentifierOrThrow("revanced_settings_arrow_right_double", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE = private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
getResourceIdentifierOrThrow("revanced_settings_arrow_left_one", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE = private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
getResourceIdentifierOrThrow("revanced_settings_arrow_left_double", "drawable"); getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_double");
/** /**
* Flags to hide from the UI. * Flags to hide from the UI.
*/ */
private static final Set<Long> FLAGS_TO_IGNORE = Set.of( private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
45386834L // 'You' tab settings icon. 45386834L, // 'You' tab settings icon.
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
); );
/** /**

View file

@ -17,9 +17,11 @@ import android.widget.Toolbar;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.shared.ui.Dim; import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings({"deprecation", "NewApi"}) @SuppressWarnings({"deprecation", "NewApi"})
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment { public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
@ -133,8 +135,10 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
*/ */
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() { public static Drawable getBackButtonDrawable() {
final int backButtonResource = Utils.getResourceIdentifierOrThrow( final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE,
"revanced_settings_toolbar_arrow_left", "drawable"); Utils.appIsUsingBoldIcons()
? "revanced_settings_toolbar_arrow_left_bold"
: "revanced_settings_toolbar_arrow_left");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource); Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
customizeBackButtonDrawable(drawable); customizeBackButtonDrawable(drawable);
return drawable; return drawable;

View file

@ -16,6 +16,7 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference; import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
@ -38,18 +39,18 @@ public abstract class BaseSearchResultItem {
// Get the corresponding layout resource ID. // Get the corresponding layout resource ID.
public int getLayoutResourceId() { public int getLayoutResourceId() {
return switch (this) { return switch (this) {
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular"); case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch"); case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
case LIST -> getResourceIdentifier("revanced_preference_search_result_list"); case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color"); case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header"); case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result"); case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
}; };
} }
private static int getResourceIdentifier(String name) { private static int getResourceIdentifier(String name) {
// Placeholder for actual resource identifier retrieval. // Placeholder for actual resource identifier retrieval.
return Utils.getResourceIdentifierOrThrow(name, "layout"); return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name);
} }
} }

View file

@ -1,7 +1,6 @@
package app.revanced.extension.shared.settings.search; package app.revanced.extension.shared.settings.search;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import static app.revanced.extension.shared.settings.search.BaseSearchViewController.DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator; import android.animation.ArgbEvaluator;
@ -33,6 +32,7 @@ import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference; import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
@ -54,15 +54,15 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
protected static final int PAUSE_BETWEEN_BLINKS = 100; protected static final int PAUSE_BETWEEN_BLINKS = 100;
protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow(
"preference_title", "id"); ResourceType.ID, "preference_title");
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
"preference_summary", "id"); ResourceType.ID, "preference_summary");
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
"preference_path", "id"); ResourceType.ID, "preference_path");
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
"preference_switch", "id"); ResourceType.ID, "preference_switch");
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
"preference_color_dot", "id"); ResourceType.ID, "preference_color_dot");
protected static class RegularViewHolder { protected static class RegularViewHolder {
TextView titleView; TextView titleView;
@ -275,7 +275,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
holder.titleView.setText(item.highlightedTitle); holder.titleView.setText(item.highlightedTitle);
holder.summaryView.setText(item.highlightedSummary); holder.summaryView.setText(item.highlightedSummary);
holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE); holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE);
holder.iconView.setImageResource(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON); holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon());
} }
/** /**

View file

@ -14,6 +14,7 @@ import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Gravity; import android.view.Gravity;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -37,6 +38,7 @@ import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
@ -70,14 +72,29 @@ public abstract class BaseSearchViewController {
protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed. protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed.
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow("revanced_search_view", "id"); protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow(
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow("revanced_search_view_container", "id"); ResourceType.ID, "revanced_search_view");
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow("action_search", "id"); protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow(
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow("revanced_settings_fragments", "id"); ResourceType.ID, "revanced_search_view_container");
public static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_settings_search_icon", "drawable"); ResourceType.ID, "action_search");
protected static final int MENU_REVANCED_SEARCH_MENU = protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_search_menu", "menu"); ResourceType.ID, "revanced_settings_fragments");
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_icon");
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_icon_bold");
protected static final int MENU_REVANCED_SEARCH_MENU = getResourceIdentifierOrThrow(
ResourceType.MENU, "revanced_search_menu");
/**
* @return The search icon, either bold or not bold, depending on the ReVanced UI setting.
*/
public static int getSearchIcon() {
return Utils.appIsUsingBoldIcons()
? DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD
: DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
}
/** /**
* Constructs a new BaseSearchViewController instance. * Constructs a new BaseSearchViewController instance.
@ -112,7 +129,7 @@ public abstract class BaseSearchViewController {
// Retrieve SearchView and container from XML. // Retrieve SearchView and container from XML.
searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW); searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW);
EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow( EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow(
"android:id/search_src_text", null)); null, "android:id/search_src_text"));
// Disable fullscreen keyboard mode. // Disable fullscreen keyboard mode.
searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
@ -248,6 +265,10 @@ public abstract class BaseSearchViewController {
} }
return false; return false;
}); });
// Set bold icon if needed.
MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH);
search.setIcon(getSearchIcon());
} }
/** /**
@ -524,7 +545,7 @@ public abstract class BaseSearchViewController {
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query)); noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary")); noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
noResultsPreference.setSelectable(false); noResultsPreference.setSelectable(false);
noResultsPreference.setIcon(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON); noResultsPreference.setIcon(getSearchIcon());
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList())); filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
} }

View file

@ -24,6 +24,8 @@ import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.BulletPointPreference; import app.revanced.extension.shared.settings.preference.BulletPointPreference;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@ -37,25 +39,35 @@ public class SearchHistoryManager {
private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored. private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored.
private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow( private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow(
"clear_history_button", "id"); ResourceType.ID, "clear_history_button");
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow( private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
"history_text", "id"); ResourceType.ID, "history_text");
private static final int ID_HISTORY_ICON = getResourceIdentifierOrThrow(
ResourceType.ID, "history_icon");
private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow( private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow(
"delete_icon", "id"); ResourceType.ID, "delete_icon");
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow( private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
"empty_history_title", "id"); ResourceType.ID, "empty_history_title");
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow( private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
"empty_history_summary", "id"); ResourceType.ID, "empty_history_summary");
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow( private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
"search_history_header", "id"); ResourceType.ID, "search_history_header");
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow( private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
"revanced_settings_search_tips_summary", "id"); ResourceType.ID, "revanced_settings_search_tips_summary");
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow( private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
"revanced_preference_search_history_screen", "layout"); ResourceType.LAYOUT, "revanced_preference_search_history_screen");
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow( private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
"revanced_preference_search_history_item", "layout"); ResourceType.LAYOUT, "revanced_preference_search_history_item");
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow( private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
"search_history_list", "id"); ResourceType.ID, "search_history_list");
private static final int ID_SEARCH_REMOVE_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_remove");
private static final int ID_SEARCH_REMOVE_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_remove_bold");
private static final int ID_SEARCH_ARROW_TIME_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_arrow_time");
private static final int ID_SEARCH_ARROW_TIME_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_arrow_time_bold");
private final Deque<String> searchHistory; private final Deque<String> searchHistory;
private final Activity activity; private final Activity activity;
@ -97,7 +109,8 @@ public class SearchHistoryManager {
// Inflate search history layout. // Inflate search history layout.
LayoutInflater inflater = LayoutInflater.from(activity); LayoutInflater inflater = LayoutInflater.from(activity);
View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN, searchHistoryContainer, false); View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN,
searchHistoryContainer, false);
searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams( searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT)); FrameLayout.LayoutParams.MATCH_PARENT));
@ -320,17 +333,29 @@ public class SearchHistoryManager {
public void notifyDataSetChanged() { public void notifyDataSetChanged() {
container.removeAllViews(); container.removeAllViews();
for (String query : history) { for (String query : history) {
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM, container, false); View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM,
container, false);
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
historyText.setText(query);
// Set click listener for main item (select query). // Set click listener for main item (select query).
view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query)); view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query));
// Set history icon.
ImageView historyIcon = view.findViewById(ID_HISTORY_ICON);
historyIcon.setImageResource(Utils.appIsUsingBoldIcons()
? ID_SEARCH_ARROW_TIME_ICON_BOLD
: ID_SEARCH_ARROW_TIME_ICON
);
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
historyText.setText(query);
// Set click listener for delete icon. // Set click listener for delete icon.
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
deleteIcon.setImageResource(Utils.appIsUsingBoldIcons()
? ID_SEARCH_REMOVE_ICON_BOLD
: ID_SEARCH_REMOVE_ICON
);
deleteIcon.setOnClickListener(v -> createAndShowDialog( deleteIcon.setOnClickListener(v -> createAndShowDialog(
query, query,
str("revanced_settings_search_remove_message"), str("revanced_settings_search_remove_message"),

View file

@ -16,7 +16,6 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -83,22 +82,15 @@ public class StreamingDataRequest {
*/ */
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap( private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(100) { Utils.createSizeRestrictedMap(50));
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private static final int CACHE_LIMIT = 50;
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
}
});
/** /**
* Strings found in the response if the video is a livestream. * Strings found in the response if the video is a livestream.

View file

@ -1,14 +1,7 @@
plugins {
alias(libs.plugins.protobuf)
}
dependencies { dependencies {
compileOnly(project(":extensions:shared:library")) compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:spotify:stub")) compileOnly(project(":extensions:spotify:stub"))
compileOnly(libs.annotation) compileOnly(libs.annotation)
implementation(libs.nanohttpd)
implementation(libs.protobuf.javalite)
} }
android { android {
@ -21,19 +14,3 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
} }
protobuf {
protoc {
artifact = libs.protobuf.protoc.get().toString()
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("java") {
option("lite")
}
}
}
}
}

View file

@ -1,6 +1,7 @@
package app.revanced.extension.spotify.layout.hide.createbutton; package app.revanced.extension.spotify.layout.hide.createbutton;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
@ -16,7 +17,7 @@ public final class HideCreateButtonPatch {
* The main approach used is matching the resource id for the Create button title. * The main approach used is matching the resource id for the Create button title.
*/ */
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of( private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"), new ResourceIdComponentFilter(ResourceType.STRING, "navigationbar_musicappitems_create_title"),
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded, // Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
// and thus getting the resource identifier for the Create button title always return 0. // and thus getting the resource identifier for the Create button title always return 0.
// FIXME: Remove this once the above issue is no longer relevant. // FIXME: Remove this once the above issue is no longer relevant.
@ -28,7 +29,7 @@ public final class HideCreateButtonPatch {
* Used in older versions of the app. * Used in older versions of the app.
*/ */
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER = private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string"); new ResourceIdComponentFilter(ResourceType.STRING, "bottom_navigation_bar_create_tab_title");
/** /**
* Injection point. This method is called on every navigation bar item to check whether it is the Create button. * Injection point. This method is called on every navigation bar item to check whether it is the Create button.

View file

@ -1,115 +0,0 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import static app.revanced.extension.spotify.misc.fix.Constants.*;
class ClientTokenService {
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
private static final String IOS_USER_AGENT;
static {
String clientVersion = getClientVersion();
int commitHashIndex = clientVersion.lastIndexOf(".");
String version = clientVersion.substring(
clientVersion.indexOf("-") + 1,
clientVersion.lastIndexOf(".", commitHashIndex - 1)
);
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
}
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
ConnectivitySdkData.newBuilder()
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
.setIos(NativeIOSData.newBuilder()
.setHwMachine(getHardwareMachine())
.setSystemVersion(getSystemVersion())
)
);
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
ClientDataRequest.newBuilder()
.setClientVersion(getClientVersion())
.setClientId(IOS_CLIENT_ID);
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
ClientTokenRequest.newBuilder()
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
@NonNull
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
return IOS_CLIENT_TOKEN_REQUEST
.setClientData(IOS_CLIENT_DATA_REQUEST
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
.setDeviceId(deviceId)
)
)
.build();
}
@Nullable
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
Logger.printInfo(() -> "Requesting iOS client token");
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
request = newIOSClientTokenRequest(deviceId);
}
ClientTokenResponse response;
try {
response = requestClientToken(request);
} catch (IOException ex) {
Logger.printException(() -> "Failed to handle request", ex);
return null;
}
return response;
}
@NonNull
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
byte[] requestArray = request.toByteArray();
urlConnection.setFixedLengthStreamingMode(requestArray.length);
urlConnection.getOutputStream().write(requestArray);
try (InputStream inputStream = urlConnection.getInputStream()) {
return ClientTokenResponse.parseFrom(inputStream);
}
}
@Nullable
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
ClientTokenRequest request;
try {
request = ClientTokenRequest.parseFrom(inputStream);
} catch (IOException ex) {
Logger.printException(() -> "Failed to parse request from input stream", ex);
return null;
}
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
ClientTokenResponse response = getClientTokenResponse(request);
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
return response;
}
}

View file

@ -1,26 +0,0 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
class Constants {
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
// Modified by a patch. Do not touch.
@NonNull
static String getClientVersion() {
return "";
}
// Modified by a patch. Do not touch.
@NonNull
static String getSystemVersion() {
return "";
}
// Modified by a patch. Do not touch.
@NonNull
static String getHardwareMachine() {
return "";
}
}

View file

@ -1,94 +0,0 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
import com.google.protobuf.MessageLite;
import fi.iki.elonen.NanoHTTPD;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
class RequestListener extends NanoHTTPD {
RequestListener(int port) {
super(port);
try {
start();
} catch (IOException ex) {
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
throw new RuntimeException(ex);
}
}
@NonNull
@Override
public Response serve(@NonNull IHTTPSession session) {
String uri = session.getUri();
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
Logger.printInfo(() -> "Serving request for URI: " + uri);
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
if (response != null) return newResponse(Response.Status.OK, response);
Logger.printException(() -> "Failed to serve client token request");
return INTERNAL_ERROR_RESPONSE;
}
@NonNull
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
return new FilterInputStream(inputStream) {
private long remaining = contentLength;
@Override
public int read() throws IOException {
if (remaining <= 0) return -1;
int result = super.read();
if (result != -1) remaining--;
return result;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (remaining <= 0) return -1;
len = (int) Math.min(len, remaining);
int result = super.read(b, off, len);
if (result != -1) remaining -= result;
return result;
}
};
}
@NonNull
private static InputStream getInputStream(@NonNull IHTTPSession session) {
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
return newLimitedInputStream(session.getInputStream(), requestContentLength);
}
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
@SuppressWarnings("SameParameterValue")
@NonNull
private static Response newResponse(Response.Status status) {
return newResponse(status, null);
}
@NonNull
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
if (messageLite == null) {
return newFixedLengthResponse(status, "application/x-protobuf", null);
}
byte[] messageBytes = messageLite.toByteArray();
InputStream stream = new ByteArrayInputStream(messageBytes);
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
}
}

View file

@ -1,25 +0,0 @@
package app.revanced.extension.spotify.misc.fix;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class SpoofClientPatch {
private static RequestListener listener;
/**
* Injection point. Launch requests listener server.
*/
public synchronized static void launchListener(int port) {
if (listener != null) {
Logger.printInfo(() -> "Listener already running on port " + port);
return;
}
try {
Logger.printInfo(() -> "Launching listener on port " + port);
listener = new RequestListener(port);
} catch (Exception ex) {
Logger.printException(() -> "launchListener failure", ex);
}
}
}

View file

@ -3,6 +3,7 @@ package app.revanced.extension.spotify.shared;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
public final class ComponentFilters { public final class ComponentFilters {
@ -19,21 +20,26 @@ public final class ComponentFilters {
public static final class ResourceIdComponentFilter implements ComponentFilter { public static final class ResourceIdComponentFilter implements ComponentFilter {
public final String resourceName; public final String resourceName;
public final String resourceType; public final ResourceType resourceType;
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded. // Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
// 0 is returned when a resource has not been found. // 0 is returned when a resource has not been found.
private int resourceId = -1; private int resourceId = -1;
@Nullable @Nullable
private String stringfiedResourceId; private String stringfiedResourceId;
@Deprecated
public ResourceIdComponentFilter(String resourceName, String resourceType) { public ResourceIdComponentFilter(String resourceName, String resourceType) {
this(ResourceType.valueOf(resourceType), resourceName);
}
public ResourceIdComponentFilter(ResourceType resourceType, String resourceName) {
this.resourceName = resourceName; this.resourceName = resourceName;
this.resourceType = resourceType; this.resourceType = resourceType;
} }
public int getResourceId() { public int getResourceId() {
if (resourceId == -1) { if (resourceId == -1) {
resourceId = Utils.getResourceIdentifier(resourceName, resourceType); resourceId = Utils.getResourceIdentifier(resourceType, resourceName);
} }
return resourceId; return resourceId;
} }

View file

@ -1,73 +0,0 @@
syntax = "proto3";
package spotify.clienttoken.data.v0;
option optimize_for = LITE_RUNTIME;
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
message ClientTokenRequest {
ClientTokenRequestType request_type = 1;
oneof request {
ClientDataRequest client_data = 2;
}
}
enum ClientTokenRequestType {
REQUEST_UNKNOWN = 0;
REQUEST_CLIENT_DATA_REQUEST = 1;
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
}
message ClientDataRequest {
string client_version = 1;
string client_id = 2;
oneof data {
ConnectivitySdkData connectivity_sdk_data = 3;
}
}
message ConnectivitySdkData {
PlatformSpecificData platform_specific_data = 1;
string device_id = 2;
}
message PlatformSpecificData {
oneof data {
NativeIOSData ios = 2;
}
}
message NativeIOSData {
int32 user_interface_idiom = 1;
bool target_iphone_simulator = 2;
string hw_machine = 3;
string system_version = 4;
string simulator_model_identifier = 5;
}
message ClientTokenResponse {
ClientTokenResponseType response_type = 1;
oneof response {
GrantedTokenResponse granted_token = 2;
}
}
enum ClientTokenResponseType {
RESPONSE_UNKNOWN = 0;
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
RESPONSE_CHALLENGES_RESPONSE = 2;
}
message GrantedTokenResponse {
string token = 1;
int32 expires_after_seconds = 2;
int32 refresh_after_seconds = 3;
repeated TokenDomain domains = 4;
}
message TokenDomain {
string domain = 1;
}

View file

@ -10,6 +10,7 @@ import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import app.revanced.extension.shared.ResourceType;
import com.strava.mediamodels.data.MediaType; import com.strava.mediamodels.data.MediaType;
import com.strava.photos.data.Media; import com.strava.photos.data.Media;
@ -166,7 +167,7 @@ public final class AddMediaDownloadPatch {
} }
private static String getString(String name, String fallback) { private static String getString(String name, String fallback) {
int id = Utils.getResourceIdentifier(name, "string"); int id = Utils.getResourceIdentifier(ResourceType.STRING, name);
return id != 0 return id != 0
? Utils.getResourceString(id) ? Utils.getResourceString(id)
: fallback; : fallback;

View file

@ -1,14 +1,18 @@
package app.revanced.extension.twitch; package app.revanced.extension.twitch;
import app.revanced.extension.shared.ResourceType;
public class Utils { public class Utils {
/* Called from SettingsPatch smali */ /* Called from SettingsPatch smali */
public static int getStringId(String name) { public static int getStringId(String name) {
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "string"); return app.revanced.extension.shared.Utils.getResourceIdentifier(
ResourceType.STRING, name);
} }
/* Called from SettingsPatch smali */ /* Called from SettingsPatch smali */
public static int getDrawableId(String name) { public static int getDrawableId(String name) {
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "drawable"); return app.revanced.extension.shared.Utils.getResourceIdentifier(
ResourceType.DRAWABLE, name);
} }
} }

View file

@ -4,19 +4,21 @@ import static app.revanced.extension.twitch.Utils.getStringId;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.twitch.settings.preference.TwitchPreferenceFragment; import app.revanced.extension.twitch.settings.preference.TwitchPreferenceFragment;
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup; import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
import tv.twitch.android.settings.SettingsActivity; import tv.twitch.android.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.List;
/** /**
* Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}. * Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}.
*/ */
@ -108,7 +110,7 @@ public class TwitchActivityHook {
base.getFragmentManager() base.getFragmentManager()
.beginTransaction() .beginTransaction()
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment) .replace(Utils.getResourceIdentifier(ResourceType.ID, "fragment_container"), fragment)
.commit(); .commit();
return true; return true;
} }

View file

@ -6,6 +6,6 @@ dependencies {
android { android {
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 23
} }
} }

View file

@ -528,14 +528,8 @@ public final class AlternativeThumbnailsPatch {
* Cache used to verify if an alternative thumbnails exists for a given video id. * Cache used to verify if an alternative thumbnails exists for a given video id.
*/ */
@GuardedBy("itself") @GuardedBy("itself")
private static final Map<String, VerifiedQualities> altVideoIdLookup = new LinkedHashMap<>(100) { private static final Map<String, VerifiedQualities> altVideoIdLookup =
private static final int CACHE_LIMIT = 1000; Utils.createSizeRestrictedMap(1000);
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
}
};
private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) { private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) {
synchronized (altVideoIdLookup) { synchronized (altVideoIdLookup) {

View file

@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -50,7 +51,7 @@ public class ChangeHeaderPatch {
return null; return null;
} }
final int identifier = Utils.getResourceIdentifier(attributeName, "attr"); final int identifier = Utils.getResourceIdentifier(ResourceType.ATTR, attributeName);
if (identifier == 0) { if (identifier == 0) {
// Should never happen. // Should never happen.
Logger.printException(() -> "Could not find attribute: " + drawableName); Logger.printException(() -> "Could not find attribute: " + drawableName);
@ -71,7 +72,7 @@ public class ChangeHeaderPatch {
? "_dark" ? "_dark"
: "_light"); : "_light");
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable"); final int identifier = Utils.getResourceIdentifier(ResourceType.DRAWABLE, drawableFullName);
if (identifier != 0) { if (identifier != 0) {
return Utils.getContext().getDrawable(identifier); return Utils.getContext().getDrawable(identifier);
} }

View file

@ -21,7 +21,7 @@ public final class DownloadsPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void activityCreated(Activity mainActivity) { public static void setMainActivity(Activity mainActivity) {
activityRef = new WeakReference<>(mainActivity); activityRef = new WeakReference<>(mainActivity);
} }

View file

@ -0,0 +1,24 @@
package app.revanced.extension.youtube.patches;
import java.util.Map;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class FixContentProviderPatch {
/**
* Injection point.
*/
public static void removeNullMapEntries(Map<?, ?> map) {
map.entrySet().removeIf(entry -> {
Object value = entry.getValue();
if (value == null) {
Logger.printDebug(() -> "Removing content provider key with null value: " + entry.getKey());
return true;
}
return false;
});
}
}

View file

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -29,6 +30,15 @@ public final class HidePlayerOverlayButtonsPatch {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original; return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
} }
/**
* Injection point.
*/
public static boolean getCastButtonOverrideV2(boolean original) {
if (Settings.HIDE_CAST_BUTTON.get()) return false;
return original;
}
/** /**
* Injection point. * Injection point.
*/ */
@ -40,10 +50,10 @@ public final class HidePlayerOverlayButtonsPatch {
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get(); = Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow( private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
"player_control_previous_button_touch_area", "id"); ResourceType.ID, "player_control_previous_button_touch_area");
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow( private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
"player_control_next_button_touch_area", "id"); ResourceType.ID, "player_control_next_button_touch_area");
/** /**
* Injection point. * Injection point.

View file

@ -4,7 +4,17 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class HideSeekbarPatch { public class HideSeekbarPatch {
/**
* Injection point.
*/
public static boolean hideSeekbar() { public static boolean hideSeekbar() {
return Settings.HIDE_SEEKBAR.get(); return Settings.HIDE_SEEKBAR.get();
} }
/**
* Injection point.
*/
public static boolean useFullscreenLargeSeekbar(boolean original) {
return Settings.FULLSCREEN_LARGE_SEEKBAR.get();
}
} }

View file

@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -115,7 +116,7 @@ public final class MiniplayerPatch {
* Resource is not present in older targets, and this field will be zero. * Resource is not present in older targets, and this field will be zero.
*/ */
private static final int MODERN_OVERLAY_SUBTITLE_TEXT private static final int MODERN_OVERLAY_SUBTITLE_TEXT
= Utils.getResourceIdentifier("modern_miniplayer_subtitle_text", "id"); = Utils.getResourceIdentifier(ResourceType.ID, "modern_miniplayer_subtitle_text");
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get(); private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
@ -378,6 +379,19 @@ public final class MiniplayerPatch {
return original; return original;
} }
/**
* Injection point.
*/
public static boolean allowBoldIcons(boolean original) {
if (CURRENT_TYPE == MINIMAL) {
// Minimal player does not have the correct pause/play icon (it's too large).
// Use the non bold icons instead.
return false;
}
return original;
}
/** /**
* Injection point. * Injection point.
*/ */

View file

@ -5,12 +5,11 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
import android.os.Build; import android.os.Build;
import android.view.View; import android.view.View;
import android.widget.TextView;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import android.widget.TextView;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -30,13 +29,13 @@ public final class NavigationButtonsPatch {
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get(); = Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
private static final Boolean DISABLE_TRANSLUCENT_STATUS_BAR private static final boolean DISABLE_TRANSLUCENT_STATUS_BAR
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get(); = Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get(); = Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get(); = Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
/** /**
@ -62,6 +61,13 @@ public final class NavigationButtonsPatch {
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView); hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
} }
/**
* Injection point.
*/
public static boolean useAnimatedNavigationButtons(boolean original) {
return Settings.NAVIGATION_BAR_ANIMATIONS.get();
}
/** /**
* Injection point. * Injection point.
*/ */

View file

@ -20,15 +20,6 @@ public class OpenShortsInRegularPlayerPatch {
REGULAR_PLAYER_FULLSCREEN REGULAR_PLAYER_FULLSCREEN
} }
static {
if (!VersionCheckPatch.IS_19_46_OR_GREATER
&& Settings.SHORTS_PLAYER_TYPE.get() == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN) {
// User imported newer settings to an older app target.
Logger.printInfo(() -> "Resetting " + Settings.SHORTS_PLAYER_TYPE);
Settings.SHORTS_PLAYER_TYPE.resetToDefault();
}
}
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null); private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
private static volatile boolean overrideBackPressToExit; private static volatile boolean overrideBackPressToExit;

View file

@ -24,18 +24,20 @@ public class OpenVideosFullscreenHookPatch {
/** /**
* Injection point. * Injection point.
*
* Returns negated value.
*/ */
public static boolean openVideoFullscreenPortrait(boolean original) { public static boolean doNotOpenVideoFullscreenPortrait(boolean original) {
Boolean openFullscreen = openNextVideoFullscreen; Boolean openFullscreen = openNextVideoFullscreen;
if (openFullscreen != null) { if (openFullscreen != null) {
openNextVideoFullscreen = null; openNextVideoFullscreen = null;
return openFullscreen; return !openFullscreen;
} }
if (!isFullScreenPatchIncluded()) { if (!isFullScreenPatchIncluded()) {
return original; return original;
} }
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get(); return !Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
} }
} }

View file

@ -42,7 +42,7 @@ public class PlayerControlsPatch {
Logger.printDebug(() -> "fullscreen button visibility: " Logger.printDebug(() -> "fullscreen button visibility: "
+ (visibility == View.VISIBLE ? "VISIBLE" : + (visibility == View.VISIBLE ? "VISIBLE" :
visibility == View.GONE ? "GONE" : "INVISIBLE")); visibility == View.GONE ? "GONE" : "INVISIBLE"));
fullscreenButtonVisibilityChanged(visibility == View.VISIBLE); fullscreenButtonVisibilityChanged(visibility == View.VISIBLE);
} }

View file

@ -1,19 +1,29 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.app.AlertDialog; import android.app.AlertDialog;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** @noinspection unused*/ @SuppressWarnings("unused")
public class RemoveViewerDiscretionDialogPatch { public class RemoveViewerDiscretionDialogPatch {
/**
* Injection point.
*/
public static void confirmDialog(AlertDialog dialog) { public static void confirmDialog(AlertDialog dialog) {
if (!Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) { if (Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here. Logger.printDebug(() -> "Clicking alert dialog dismiss button");
dialog.show();
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setSoundEffectsEnabled(false);
button.performClick();
return; return;
} }
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE); // Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
button.setSoundEffectsEnabled(false); Logger.printDebug(() -> "Showing alert dialog");
button.performClick(); dialog.show();
} }
} }

View file

@ -16,7 +16,7 @@ import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter; import app.revanced.extension.youtube.patches.litho.ReturnYouTubeDislikeFilter;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@ -131,6 +131,10 @@ public class ReturnYouTubeDislikePatch {
String conversionContextString = conversionContext.toString(); String conversionContextString = conversionContext.toString();
if (Settings.RYD_ENABLED.get()) { // FIXME: Remove this.
Logger.printDebug(() -> "RYD conversion context: " + conversionContext);
}
if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) { if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) {
return original; return original;
} }

View file

@ -2,8 +2,6 @@ package app.revanced.extension.youtube.patches;
import android.app.Activity; import android.app.Activity;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Objects; import java.util.Objects;
@ -78,7 +76,7 @@ public class ShortsAutoplayPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) { public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
try { try {
final boolean autoplay; final boolean autoplay;
@ -95,19 +93,19 @@ public class ShortsAutoplayPatch {
autoplay = Settings.SHORTS_AUTOPLAY.get(); autoplay = Settings.SHORTS_AUTOPLAY.get();
} }
final ShortsLoopBehavior behavior = autoplay Enum<?> overrideBehavior = (autoplay
? ShortsLoopBehavior.SINGLE_PLAY ? ShortsLoopBehavior.SINGLE_PLAY
: ShortsLoopBehavior.REPEAT; : ShortsLoopBehavior.REPEAT).ytEnumValue;
if (behavior.ytEnumValue != null) { if (overrideBehavior != null) {
Logger.printDebug(() -> { Logger.printDebug(() -> {
String name = (original == null ? "unknown (null)" : original.name()); String name = (original == null ? "unknown (null)" : original.name());
return behavior == original return overrideBehavior == original
? "Behavior setting is same as original. Using original: " + name ? "Behavior setting is same as original. Using original: " + name
: "Changing Shorts repeat behavior from: " + name + " to: " + behavior.name(); : "Changing Shorts repeat behavior from: " + name + " to: " + overrideBehavior.name();
}); });
return behavior.ytEnumValue; return overrideBehavior;
} }
if (original == null) { if (original == null) {
@ -118,13 +116,12 @@ public class ShortsAutoplayPatch {
return unknown; return unknown;
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "changeShortsRepeatBehavior failure", ex); Logger.printException(() -> "changeShortsRepeatState failure", ex);
} }
return original; return original;
} }
/** /**
* Injection point. * Injection point.
*/ */

View file

@ -19,5 +19,12 @@ public class VersionCheckPatch {
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00"); public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
@Deprecated @Deprecated
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00"); public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
public static final boolean IS_20_21_OR_GREATER = isVersionOrGreater("20.21.00");
public static final boolean IS_20_22_OR_GREATER = isVersionOrGreater("20.22.00");
public static final boolean IS_20_31_OR_GREATER = isVersionOrGreater("20.31.00");
public static final boolean IS_20_37_OR_GREATER = isVersionOrGreater("20.37.00");
} }

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
@ -8,10 +8,10 @@ import android.view.View;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;

View file

@ -1,7 +1,8 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -42,7 +43,6 @@ public final class ButtonsFilter extends Filter {
addPathCallbacks( addPathCallbacks(
likeSubscribeGlow, likeSubscribeGlow,
bufferFilterPathGroup,
new StringFilterGroup( new StringFilterGroup(
Settings.HIDE_LIKE_DISLIKE_BUTTON, Settings.HIDE_LIKE_DISLIKE_BUTTON,
"|segmented_like_dislike_button" "|segmented_like_dislike_button"
@ -61,6 +61,12 @@ public final class ButtonsFilter extends Filter {
) )
); );
// FIXME: 20.22+ filtering of the action buttons doesn't work because
// the buffer is the same for all buttons.
if (!VersionCheckPatch.IS_20_22_OR_GREATER) {
addPathCallbacks(bufferFilterPathGroup);
}
bufferButtonsGroupList.addAll( bufferButtonsGroupList.addAll(
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_REPORT_BUTTON, Settings.HIDE_REPORT_BUTTON,
@ -112,11 +118,13 @@ public final class ButtonsFilter extends Filter {
} }
private boolean isEveryFilterGroupEnabled() { private boolean isEveryFilterGroupEnabled() {
for (var group : pathCallbacks) for (var group : pathCallbacks) {
if (!group.isEnabled()) return false; if (!group.isEnabled()) return false;
}
for (var group : bufferButtonsGroupList) for (var group : bufferButtonsGroupList) {
if (!group.isEnabled()) return false; if (!group.isEnabled()) return false;
}
return true; return true;
} }

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.*;

View file

@ -1,9 +1,9 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;

View file

@ -1,8 +1,8 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class HideInfoCardsFilter extends Filter { public final class HideInfoCardsFilter extends Filter {

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;

View file

@ -1,5 +1,6 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_20_21_OR_GREATER;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -7,16 +8,17 @@ import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.youtube.patches.ChangeHeaderPatch; import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
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.NavigationBar;
@ -51,7 +53,7 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup compactChannelBarInnerButton; private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton; private final ByteArrayFilterGroup joinMembershipButton;
private final StringFilterGroup horizontalShelves; private final StringFilterGroup horizontalShelves;
private final ByteArrayFilterGroup ticketShelf; private final ByteArrayFilterGroup ticketShelfBuffer;
private final StringFilterGroup chipBar; private final StringFilterGroup chipBar;
private final StringFilterGroup channelProfile; private final StringFilterGroup channelProfile;
private final ByteArrayFilterGroupList channelProfileBuffer; private final ByteArrayFilterGroupList channelProfileBuffer;
@ -210,7 +212,7 @@ public final class LayoutComponentsFilter extends Filter {
// Playable horizontal shelf header. // Playable horizontal shelf header.
playablesBuffer = new ByteArrayFilterGroup( playablesBuffer = new ByteArrayFilterGroup(
Settings.HIDE_PLAYABLES, null,
"FEmini_app_destination" "FEmini_app_destination"
); );
@ -296,15 +298,15 @@ public final class LayoutComponentsFilter extends Filter {
); );
horizontalShelves = new StringFilterGroup( horizontalShelves = new StringFilterGroup(
Settings.HIDE_HORIZONTAL_SHELVES, null, // Setting is checked in isFiltered()
"horizontal_video_shelf.e", "horizontal_video_shelf.e",
"horizontal_shelf.e", "horizontal_shelf.e",
"horizontal_shelf_inline.e", "horizontal_shelf_inline.e",
"horizontal_tile_shelf.e" "horizontal_tile_shelf.e"
); );
ticketShelf = new ByteArrayFilterGroup( ticketShelfBuffer = new ByteArrayFilterGroup(
Settings.HIDE_TICKET_SHELF, null,
"ticket_item.e" "ticket_item.e"
); );
@ -346,7 +348,7 @@ public final class LayoutComponentsFilter extends Filter {
@Override @Override
public boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// This identifier is used not only in players but also in search results: // This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245 // https://github.com/ReVanced/revanced-patches/issues/3245
// Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results. // Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results.
@ -386,9 +388,19 @@ public final class LayoutComponentsFilter extends Filter {
} }
if (matchedGroup == horizontalShelves) { if (matchedGroup == horizontalShelves) {
return contentIndex == 0 && (hideShelves() if (contentIndex != 0) return false;
|| ticketShelf.check(buffer).isFiltered() final boolean hideShelves = Settings.HIDE_HORIZONTAL_SHELVES.get();
|| playablesBuffer.check(buffer).isFiltered()); final boolean hideTickets = Settings.HIDE_TICKET_SHELF.get();
final boolean hidePlayables = Settings.HIDE_PLAYABLES.get();
if (!hideShelves && !hideTickets && !hidePlayables) return false;
// Must always check other buffers first, to prevent incorrectly hiding them
// if they are set to show but hide horizontal shelves is set to hidden.
if (ticketShelfBuffer.check(buffer).isFiltered()) return hideTickets;
if (playablesBuffer.check(buffer).isFiltered()) return hidePlayables;
return hideShelves && hideShelves();
} }
if (matchedGroup == chipBar) { if (matchedGroup == chipBar) {
@ -402,20 +414,22 @@ public final class LayoutComponentsFilter extends Filter {
* Injection point. * Injection point.
* Called from a different place then the other filters. * Called from a different place then the other filters.
*/ */
public static boolean filterMixPlaylists(Object conversionContext, @Nullable final byte[] bytes) { public static boolean filterMixPlaylists(Object conversionContext, @Nullable byte[] buffer) {
// Edit: This hook may no longer be needed, and mix playlist filtering
// might be possible using the existing litho filters.
try { try {
if (!Settings.HIDE_MIX_PLAYLISTS.get()) { if (!Settings.HIDE_MIX_PLAYLISTS.get()) {
return false; return false;
} }
if (bytes == null) { if (buffer == null) {
Logger.printDebug(() -> "bytes is null"); Logger.printDebug(() -> "buffer is null");
return false; return false;
} }
if (mixPlaylists.check(bytes).isFiltered() if (mixPlaylists.check(buffer).isFiltered()
// Prevent hiding the description of some videos accidentally. // Prevent hiding the description of some videos accidentally.
&& !mixPlaylistsBufferExceptions.check(bytes).isFiltered() && !mixPlaylistsBufferExceptions.check(buffer).isFiltered()
// Prevent playlist items being hidden, if a mix playlist is present in it. // Prevent playlist items being hidden, if a mix playlist is present in it.
// Check last since it requires creating a context string. // Check last since it requires creating a context string.
// //
@ -478,11 +492,23 @@ public final class LayoutComponentsFilter extends Filter {
: height; : height;
} }
private static final boolean HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED
= Settings.HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS.get();
/** /**
* Injection point. * Injection point.
*/ */
public static void hideInRelatedVideos(View chipView) { public static void hideInRelatedVideos(View chipView) {
Utils.hideViewBy0dpUnderCondition(Settings.HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS, chipView); // Cannot use 0dp hide with later targets, otherwise the suggested videos
// can be shown in full screen mode.
// This behavior may also be present in earlier app targets.
if (IS_20_21_OR_GREATER) {
// FIXME: The filter bar is still briefly shown when dragging the suggested videos
// below the video player.
Utils.hideViewUnderCondition(HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED, chipView);
} else {
Utils.hideViewBy0dpUnderCondition(HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED, chipView);
}
} }
private static final boolean HIDE_DOODLES_ENABLED = Settings.HIDE_DOODLES.get(); private static final boolean HIDE_DOODLES_ENABLED = Settings.HIDE_DOODLES.get();
@ -507,6 +533,8 @@ public final class LayoutComponentsFilter extends Filter {
&& NavigationBar.isSearchBarActive() && NavigationBar.isSearchBarActive()
// Search bar can be active but behind the player. // Search bar can be active but behind the player.
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) { && !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
// FIXME: "Show more" button is visible hidden,
// but an empty space remains that can be clicked.
Utils.hideViewByLayoutParams(view); Utils.hideViewByLayoutParams(view);
} }
} }

View file

@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.shared.patches.litho.FilterGroup.*;

View file

@ -1,11 +1,12 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState; import app.revanced.extension.youtube.shared.ShortsPlayerState;

View file

@ -1,21 +1,21 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch; import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
/** /**
* Searches for video id's in the proto buffer of Shorts dislike. * Searches for video id's in the proto buffer of Shorts dislike.
@ -36,18 +36,7 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry(). * Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
*/ */
@GuardedBy("itself") @GuardedBy("itself")
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() { private static final Map<String, Boolean> lastVideoIds = Utils.createSizeRestrictedMap(5);
/**
* Number of video id's to keep track of for searching thru the buffer.
* A minimum value of 3 should be sufficient, but check a few more just in case.
*/
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
}
};
/** /**
* Injection point. * Injection point.
@ -88,7 +77,7 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
@Override @Override
public boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
return false; return false;
} }

View file

@ -1,19 +1,24 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.litho;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.view.View; import android.view.View;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.shared.settings.BooleanSetting;
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.patches.litho.Filter; import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
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.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@ -21,12 +26,32 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class ShortsFilter extends Filter { public final class ShortsFilter extends Filter {
private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get(); private static final boolean HIDE_SHORTS_NAVIGATION_BAR = Settings.HIDE_SHORTS_NAVIGATION_BAR.get();
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.e"; private static final String COMPONENT_TYPE = "ComponentType";
private static final String[] REEL_ACTION_BAR_PATHS = {
"reel_action_bar.", // Regular Shorts.
"reels_player_overlay_layout." // Shorts ads.
};
private static final Map<Integer, BooleanSetting> REEL_ACTION_BUTTONS_MAP = new HashMap<>() {
{
// Like button and Dislike button can be hidden with Litho filter.
// put(0, Settings.HIDE_SHORTS_LIKE_BUTTON);
// put(1, Settings.HIDE_SHORTS_DISLIKE_BUTTON);
put(2, Settings.HIDE_SHORTS_COMMENTS_BUTTON);
put(3, Settings.HIDE_SHORTS_SHARE_BUTTON);
put(4, Settings.HIDE_SHORTS_REMIX_BUTTON);
}
};
private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.e";
/** /**
* For paid promotion label and subscribe button that appears in the channel bar. * For paid promotion label and subscribe button that appears in the channel bar.
*/ */
private static final String REEL_METAPANEL_PATH = "reel_metapanel.e"; private final String REEL_METAPANEL_PATH = "reel_metapanel.e";
/**
* For paid promotion label and subscribe button that appears in the channel bar.
*/
private final String REEL_PLAYER_OVERLAY_PATH = "reel_player_overlay.e";
/** /**
* Tags that appears when opening the Shorts player. * Tags that appears when opening the Shorts player.
@ -46,6 +71,8 @@ public final class ShortsFilter extends Filter {
private final ByteArrayFilterGroup useSoundButtonBuffer; private final ByteArrayFilterGroup useSoundButtonBuffer;
private final StringFilterGroup useTemplateButton; private final StringFilterGroup useTemplateButton;
private final ByteArrayFilterGroup useTemplateButtonBuffer; private final ByteArrayFilterGroup useTemplateButtonBuffer;
private final StringFilterGroup reelCarousel;
private final ByteArrayFilterGroup reelCarouselBuffer;
private final StringFilterGroup autoDubbedLabel; private final StringFilterGroup autoDubbedLabel;
private final StringFilterGroup subscribeButton; private final StringFilterGroup subscribeButton;
@ -149,13 +176,15 @@ public final class ShortsFilter extends Filter {
StringFilterGroup likeButton = new StringFilterGroup( StringFilterGroup likeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_LIKE_BUTTON, Settings.HIDE_SHORTS_LIKE_BUTTON,
"shorts_like_button.e", "shorts_like_button.e",
"reel_like_button.e" "reel_like_button.e",
"reel_like_toggled_button.e"
); );
StringFilterGroup dislikeButton = new StringFilterGroup( StringFilterGroup dislikeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_DISLIKE_BUTTON, Settings.HIDE_SHORTS_DISLIKE_BUTTON,
"shorts_dislike_button.e", "shorts_dislike_button.e",
"reel_dislike_button.e" "reel_dislike_button.e",
"reel_dislike_toggled_button.e"
); );
StringFilterGroup previewComment = new StringFilterGroup( StringFilterGroup previewComment = new StringFilterGroup(
@ -174,7 +203,7 @@ public final class ShortsFilter extends Filter {
autoDubbedLabel = new StringFilterGroup( autoDubbedLabel = new StringFilterGroup(
Settings.HIDE_SHORTS_AUTO_DUBBED_LABEL, Settings.HIDE_SHORTS_AUTO_DUBBED_LABEL,
"badge." "badge.e"
); );
joinButton = new StringFilterGroup( joinButton = new StringFilterGroup(
@ -199,6 +228,16 @@ public final class ShortsFilter extends Filter {
"reel_action_bar.e" "reel_action_bar.e"
); );
reelCarousel = new StringFilterGroup(
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
"reel_carousel.e"
);
reelCarouselBuffer = new ByteArrayFilterGroup(
null,
"FEsfv_audio_pivot"
);
useSoundButton = new StringFilterGroup( useSoundButton = new StringFilterGroup(
Settings.HIDE_SHORTS_USE_SOUND_BUTTON, Settings.HIDE_SHORTS_USE_SOUND_BUTTON,
// First filter needed for "Use this sound" that can appear when viewing Shorts // First filter needed for "Use this sound" that can appear when viewing Shorts
@ -226,7 +265,11 @@ public final class ShortsFilter extends Filter {
videoActionButton = new StringFilterGroup( videoActionButton = new StringFilterGroup(
null, null,
// Can be simply 'button.e', 'shorts_video_action_button.e' or 'reel_action_button.e' // Can be any of:
// button.eml
// shorts_video_action_button.eml
// reel_action_button.eml
// reel_pivot_button.eml
"button.e" "button.e"
); );
@ -236,32 +279,39 @@ public final class ShortsFilter extends Filter {
); );
addPathCallbacks( addPathCallbacks(
shortsCompactFeedVideo, joinButton, subscribeButton, paidPromotionLabel, autoDubbedLabel, shortsCompactFeedVideo, joinButton, subscribeButton, paidPromotionLabel, livePreview,
shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar, previewComment, suggestedAction, pausedOverlayButtons, channelBar, previewComment, autoDubbedLabel,
fullVideoLinkLabel, videoTitle, useSoundButton, reelSoundMetadata, soundButton, infoPanel, fullVideoLinkLabel, videoTitle, useSoundButton, reelSoundMetadata, soundButton, reelCarousel,
stickers, likeFountain, likeButton, dislikeButton, livePreview infoPanel, stickers, likeFountain, likeButton, dislikeButton
); );
// // Legacy hiding of Shorts action buttons. Because of 20.31+ buffer changes
// All other action buttons. // it's currently not possible to hide these using buffer filtering.
// // See alternative hiding strategy in hideActionButtons().
videoActionButtonBuffer.addAll( if (!VersionCheckPatch.IS_20_22_OR_GREATER) {
new ByteArrayFilterGroup( addPathCallbacks(shortsActionBar);
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button", //
"youtube_shorts_comment_outline" // All other action buttons.
), //
new ByteArrayFilterGroup( videoActionButtonBuffer.addAll(
Settings.HIDE_SHORTS_SHARE_BUTTON, new ByteArrayFilterGroup(
"reel_share_button", Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"youtube_shorts_share_outline" "reel_comment_button",
), "youtube_shorts_comment_outline"
new ByteArrayFilterGroup( ),
Settings.HIDE_SHORTS_REMIX_BUTTON, new ByteArrayFilterGroup(
"reel_remix_button", Settings.HIDE_SHORTS_SHARE_BUTTON,
"youtube_shorts_remix_outline" "reel_share_button",
) "youtube_shorts_share_outline"
); ),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_REMIX_BUTTON,
"reel_remix_button",
"youtube_shorts_remix_outline"
)
);
}
// //
// Suggested actions. // Suggested actions.
@ -275,7 +325,8 @@ public final class ShortsFilter extends Filter {
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SHOP_BUTTON, Settings.HIDE_SHORTS_SHOP_BUTTON,
"yt_outline_bag_" "yt_outline_bag_",
"yt_outline_experimental_bag_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_TAGGED_PRODUCTS, Settings.HIDE_SHORTS_TAGGED_PRODUCTS,
@ -285,31 +336,38 @@ public final class ShortsFilter extends Filter {
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_LOCATION_LABEL, Settings.HIDE_SHORTS_LOCATION_LABEL,
"yt_outline_location_point_" "yt_outline_location_point_",
"yt_outline_experimental_location_point_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SAVE_SOUND_BUTTON, Settings.HIDE_SHORTS_SAVE_SOUND_BUTTON,
"yt_outline_bookmark_", "yt_outline_bookmark_",
// 'Save sound' button. It seems this has been removed and only 'Save music' is used. // 'Save sound' button. It seems this has been removed and only 'Save music' is used.
// Still hide this in case it's still present. // Still hide this in case it's still present.
"yt_outline_list_add_" "yt_outline_list_add_",
"yt_outline_experimental_list_add_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS, Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS,
"yt_outline_search_" "yt_outline_search_",
"yt_outline_experimental_search_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SUPER_THANKS_BUTTON, Settings.HIDE_SHORTS_SUPER_THANKS_BUTTON,
"yt_outline_dollar_sign_heart_" "yt_outline_dollar_sign_heart_",
"yt_outline_experimental_dollar_sign_heart_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON, Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON,
// "Use this template" can appear in two different places. // "Use this template" can appear in two different places.
"yt_outline_template_add_" "yt_outline_template_add_",
"yt_outline_experimental_template_add_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_UPCOMING_BUTTON, Settings.HIDE_SHORTS_UPCOMING_BUTTON,
"yt_outline_bell_" "yt_outline_bell_",
"yt_outline_experimental_bell_"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_EFFECT_BUTTON, Settings.HIDE_SHORTS_EFFECT_BUTTON,
@ -322,11 +380,13 @@ public final class ShortsFilter extends Filter {
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_NEW_POSTS_BUTTON, Settings.HIDE_SHORTS_NEW_POSTS_BUTTON,
"yt_outline_box_pencil" "yt_outline_box_pencil",
"yt_outline_experimental_box_pencil"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_HASHTAG_BUTTON, Settings.HIDE_SHORTS_HASHTAG_BUTTON,
"yt_outline_hashtag_" "yt_outline_hashtag_",
"yt_outline_experimental_hashtag_"
) )
); );
} }
@ -343,12 +403,17 @@ public final class ShortsFilter extends Filter {
@Override @Override
public boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentType == FilterContentType.PATH) { if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton if (matchedGroup == subscribeButton || matchedGroup == joinButton
|| matchedGroup == paidPromotionLabel || matchedGroup == autoDubbedLabel) { || matchedGroup == paidPromotionLabel || matchedGroup == autoDubbedLabel) {
// Selectively filter to avoid false positive filtering of other subscribe/join buttons. // Selectively filter to avoid false positive filtering of other subscribe/join buttons.
return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH); return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH)
|| path.startsWith(REEL_PLAYER_OVERLAY_PATH);
}
if (matchedGroup == reelCarousel) {
return reelCarouselBuffer.check(buffer).isFiltered();
} }
if (matchedGroup == useSoundButton) { if (matchedGroup == useSoundButton) {
@ -444,6 +509,9 @@ public final class ShortsFilter extends Filter {
}; };
} }
/**
* Injection point.
*/
public static int getSoundButtonSize(int original) { public static int getSoundButtonSize(int original) {
if (Settings.HIDE_SHORTS_SOUND_BUTTON.get()) { if (Settings.HIDE_SHORTS_SOUND_BUTTON.get()) {
return 0; return 0;
@ -452,10 +520,16 @@ public final class ShortsFilter extends Filter {
return original; return original;
} }
/**
* Injection point.
*/
public static void setNavigationBar(PivotBar view) { public static void setNavigationBar(PivotBar view) {
pivotBarRef = new WeakReference<>(view); pivotBarRef = new WeakReference<>(view);
} }
/**
* Injection point.
*/
public static void hideNavigationBar(String tag) { public static void hideNavigationBar(String tag) {
if (HIDE_SHORTS_NAVIGATION_BAR) { if (HIDE_SHORTS_NAVIGATION_BAR) {
if (REEL_WATCH_FRAGMENT_INIT_PLAYBACK.contains(tag)) { if (REEL_WATCH_FRAGMENT_INIT_PLAYBACK.contains(tag)) {
@ -470,6 +544,9 @@ public final class ShortsFilter extends Filter {
} }
} }
/**
* Injection point.
*/
public static int getNavigationBarHeight(int original) { public static int getNavigationBarHeight(int original) {
if (HIDE_SHORTS_NAVIGATION_BAR) { if (HIDE_SHORTS_NAVIGATION_BAR) {
return HIDDEN_NAVIGATION_BAR_VERTICAL_HEIGHT; return HIDDEN_NAVIGATION_BAR_VERTICAL_HEIGHT;
@ -477,4 +554,4 @@ public final class ShortsFilter extends Filter {
return original; return original;
} }
} }

View file

@ -8,7 +8,7 @@ import android.widget.ListView;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.AdvancedVideoQualityMenuFilter; import app.revanced.extension.youtube.patches.litho.AdvancedVideoQualityMenuFilter;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**

View file

@ -21,7 +21,6 @@ public class RememberVideoQualityPatch {
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI; private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE; private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
public static boolean shouldRememberVideoQuality() { public static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen() BooleanSetting preference = ShortsPlayerState.isOpen()
? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED ? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED

View file

@ -32,7 +32,7 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ui.Dim; import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.ui.SheetBottomDialog; import app.revanced.extension.shared.ui.SheetBottomDialog;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter; import app.revanced.extension.youtube.patches.litho.PlaybackSpeedMenuFilter;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
import kotlin.Unit; import kotlin.Unit;

View file

@ -0,0 +1,85 @@
package app.revanced.extension.youtube.patches.theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Dynamic drawable that is either the regular or bolded ReVanced preference icon.
*
* This is needed because the YouTube ReVanced preference intent is an AndroidX preference,
* and AndroidX classes are not built into Android which makes programmatically changing
* the preference thru patching overly complex. This solves the problem by using a drawable
* wrapper to dynamically pick which icon drawable to use at runtime.
*/
@SuppressWarnings("unused")
public class ReVancedSettingsIconDynamicDrawable extends Drawable {
private final Drawable icon;
public ReVancedSettingsIconDynamicDrawable() {
final int resId = Utils.getResourceIdentifier(ResourceType.DRAWABLE,
Utils.appIsUsingBoldIcons()
? "revanced_settings_icon_bold"
: "revanced_settings_icon"
);
icon = Utils.getContext().getDrawable(resId);
}
@Override
public void draw(@NonNull Canvas canvas) {
icon.draw(canvas);
}
@Override
public void setAlpha(int alpha) {
icon.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
icon.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return icon.getOpacity();
}
@Override
public int getIntrinsicWidth() {
return icon.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return icon.getIntrinsicHeight();
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
icon.setBounds(left, top, right, bottom);
}
@Override
public void setBounds(@NonNull Rect bounds) {
super.setBounds(bounds);
icon.setBounds(bounds);
}
@Override
public void onBoundsChange(@NonNull Rect bounds) {
super.onBoundsChange(bounds);
icon.setBounds(bounds);
}
}

View file

@ -16,6 +16,7 @@ import java.util.Arrays;
import java.util.Scanner; import java.util.Scanner;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -101,16 +102,6 @@ public final class SeekbarColorPatch {
return customSeekbarColor; return customSeekbarColor;
} }
/**
* injection point.
*/
public static boolean useLotteLaunchSplashScreen(boolean original) {
// This method is only used for development purposes to force the old style launch screen.
// Forcing this off on some devices can cause unexplained startup crashes,
// where the lottie animation is still used even though this condition appears to bypass it.
return original; // false = drawable style, true = lottie style.
}
/** /**
* Injection point. * Injection point.
* Modern Lottie style animation. * Modern Lottie style animation.

View file

@ -260,7 +260,8 @@ public class ReturnYouTubeDislike {
// middle separator // middle separator
String middleSeparatorString = compactLayout String middleSeparatorString = compactLayout
? " " + MIDDLE_SEPARATOR_CHARACTER + " " ? " " + MIDDLE_SEPARATOR_CHARACTER + " "
: " \u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009 "; // u2009 = 'narrow space' character : " \u2009\u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009\u2009 "; // u2009 = 'narrow space'
final int shapeInsertionIndex = middleSeparatorString.length() / 2; final int shapeInsertionIndex = middleSeparatorString.length() / 2;
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString); Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
@ -555,7 +556,8 @@ public class ReturnYouTubeDislike {
if (originalDislikeSpan != null && replacementLikeDislikeSpan != null if (originalDislikeSpan != null && replacementLikeDislikeSpan != null
&& spansHaveEqualTextAndColor(original, originalDislikeSpan)) { && spansHaveEqualTextAndColor(original, originalDislikeSpan)) {
Logger.printDebug(() -> "Replacing span with previously created dislike span of data: " + videoId); Logger.printDebug(() -> "Replacing span: " + original + " with " +
"previously created dislike span of data: " + videoId);
return replacementLikeDislikeSpan; return replacementLikeDislikeSpan;
} }

View file

@ -19,7 +19,7 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerH
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType; import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability; import static app.revanced.extension.youtube.patches.litho.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
import static app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch.SpoofClientAv1Availability; import static app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch.SpoofClientAv1Availability;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle; import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration; import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
@ -46,7 +46,7 @@ import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrow
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch; import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle; import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
@ -188,7 +188,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerAnyModernAvailability()); public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerHideOverlayButtonsAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerHideSubtextsAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerHideSubtextsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerHideRewindOrOverlayOpacityAvailability());
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerAnyModernAvailability()); public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerAnyModernAvailability());
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerHideRewindOrOverlayOpacityAvailability()); public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerHideRewindOrOverlayOpacityAvailability());
@ -284,6 +284,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true); public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true, public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
"revanced_switch_create_with_notifications_button_user_dialog_message"); "revanced_switch_create_with_notifications_button_user_dialog_message");
public static final BooleanSetting NAVIGATION_BAR_ANIMATIONS = new BooleanSetting("revanced_navigation_bar_animations", FALSE);
public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true, public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true,
"revanced_disable_translucent_status_bar_user_dialog_message"); "revanced_disable_translucent_status_bar_user_dialog_message");
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true); public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
@ -337,6 +338,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", FALSE); public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", FALSE);
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true); public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true); public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
public static final BooleanSetting FULLSCREEN_LARGE_SEEKBAR = new BooleanSetting("revanced_fullscreen_large_seekbar", FALSE);
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE); public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE); public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", FALSE); public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", FALSE);
@ -472,6 +474,13 @@ public class Settings extends YouTubeAndMusicSettings {
static { static {
// region Migration // region Migration
// 20.37+ YT removed parts of the code for the legacy tablet miniplayer.
// This check must remain until the Tablet type is eventually removed.
if (VersionCheckPatch.IS_20_37_OR_GREATER && MINIPLAYER_TYPE.get() == MiniplayerType.TABLET) {
Logger.printInfo(() -> "Resetting miniplayer tablet type");
MINIPLAYER_TYPE.resetToDefault();
}
// Migrate renamed change header enums. // Migrate renamed change header enums.
if (HEADER_LOGO.get() == HeaderLogo.REVANCED) { if (HEADER_LOGO.get() == HeaderLogo.REVANCED) {
HEADER_LOGO.save(HeaderLogo.ROUNDED); HEADER_LOGO.save(HeaderLogo.ROUNDED);
@ -514,6 +523,14 @@ public class Settings extends YouTubeAndMusicSettings {
SPOOF_APP_VERSION.resetToDefault(); SPOOF_APP_VERSION.resetToDefault();
} }
if (!BaseSettings.SETTINGS_DISABLE_BOLD_ICONS.get() && SPOOF_APP_VERSION.get()
&& SPOOF_APP_VERSION_TARGET.get().compareTo("19.35.00") <= 0) {
Logger.printInfo(() -> "Temporarily disabling bold icons that don't work with old spoof targets");
// Don't save and only temporarily overwrite the value so
// if spoofing is turned off the old setting value is used.
BooleanSetting.privateSetValue(BaseSettings.SETTINGS_DISABLE_BOLD_ICONS, false);
}
// VR 1.61 is not selectable in the settings, and it's selected by spoof stream patch if needed. // VR 1.61 is not selectable in the settings, and it's selected by spoof stream patch if needed.
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_1_61_48) { if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_1_61_48) {
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault(); SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();

View file

@ -7,6 +7,7 @@ import android.preference.PreferenceFragment;
import android.view.View; import android.view.View;
import android.widget.Toolbar; import android.widget.Toolbar;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.patches.VersionCheckPatch;
@ -15,11 +16,28 @@ import app.revanced.extension.youtube.settings.preference.YouTubePreferenceFragm
import app.revanced.extension.youtube.settings.search.YouTubeSearchViewController; import app.revanced.extension.youtube.settings.search.YouTubeSearchViewController;
/** /**
* Hooks LicenseActivity to inject a custom {@link YouTubePreferenceFragment} with a toolbar and search functionality. * Hooks LicenseActivity to inject a custom {@link YouTubePreferenceFragment}
* with a toolbar and search functionality.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class YouTubeActivityHook extends BaseActivityHook { public class YouTubeActivityHook extends BaseActivityHook {
/**
* How much time has passed since the first launch of the app. Simple check to prevent
* forcing bold icons on first launch where the settings menu is partially broken
* due to missing icon resources the client has not yet received.
*/
private static final long MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS = 30 * 1000; // 30 seconds.
private static final boolean USE_BOLD_ICONS = VersionCheckPatch.IS_20_31_OR_GREATER
&& !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
&& (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get())
> MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS;
static {
Utils.setAppIsUsingBoldIcons(USE_BOLD_ICONS);
}
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value. private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
/** /**
@ -44,15 +62,7 @@ public class YouTubeActivityHook extends BaseActivityHook {
final var theme = Utils.isDarkModeEnabled() final var theme = Utils.isDarkModeEnabled()
? "Theme.YouTube.Settings.Dark" ? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings"; : "Theme.YouTube.Settings";
activity.setTheme(Utils.getResourceIdentifierOrThrow(theme, "style")); activity.setTheme(Utils.getResourceIdentifierOrThrow(ResourceType.STYLE, theme));
}
/**
* Returns the resource ID for the YouTube settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
} }
/** /**
@ -155,4 +165,12 @@ public class YouTubeActivityHook extends BaseActivityHook {
public static boolean handleBackPress() { public static boolean handleBackPress() {
return YouTubeSearchViewController.handleFinish(searchViewController); return YouTubeSearchViewController.handleFinish(searchViewController);
} }
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static boolean useBoldIcons(boolean original) {
return USE_BOLD_ICONS;
}
} }

View file

@ -19,8 +19,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -72,7 +74,7 @@ public final class NavigationBar {
*/ */
public static boolean isSearchBarActive() { public static boolean isSearchBarActive() {
View searchbarResults = searchBarResultsRef.get(); View searchbarResults = searchBarResultsRef.get();
return searchbarResults != null && searchbarResults.getParent() != null; return searchbarResults != null && searchbarResults.isShown();
} }
public static boolean isBackButtonVisible() { public static boolean isBackButtonVisible() {
@ -277,12 +279,14 @@ public final class NavigationBar {
} }
/** /**
* Use the bundled non cairo filled icon instead of a custom icon. * Custom cairo notification filled icon to fix unpatched app missing resource.
* Use the old non cairo filled icon, which is almost identical to
* the what would be the filled cairo icon.
*/ */
private static final int fillBellCairoBlack = Utils.getResourceIdentifier( private static final int fillBellCairoBlack = Utils.getResourceIdentifier(ResourceType.DRAWABLE,
"yt_fill_bell_black_24", "drawable"); // The bold cairo notification filled icon is present,
// but YT still has not fixed the icon not associated to the enum.
VersionCheckPatch.IS_20_31_OR_GREATER && !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
? "yt_fill_experimental_bell_vd_theme_24"
: "revanced_fill_bell_cairo_black_24");
/** /**
* Injection point. * Injection point.
@ -290,13 +294,12 @@ public final class NavigationBar {
*/ */
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) { public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
if (fillBellCairoBlack != 0) { // Show a popup informing this fix is no longer needed to those who might care.
// Show a popup informing this fix is no longer needed to those who might care. if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) { Logger.printException(() -> "YouTube fixed the notification icons");
Logger.printException(() -> "YouTube fixed the cairo notification icons");
}
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
} }
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
} }
public enum NavigationButton { public enum NavigationButton {

View file

@ -3,6 +3,7 @@ package app.revanced.extension.youtube.shared
import android.app.Activity import android.app.Activity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -19,13 +20,13 @@ class PlayerControlsVisibilityObserverImpl(
* id of the direct parent of controls_layout, R.id.youtube_controls_overlay * id of the direct parent of controls_layout, R.id.youtube_controls_overlay
*/ */
private val controlsLayoutParentId = private val controlsLayoutParentId =
Utils.getResourceIdentifier(activity, "youtube_controls_overlay", "id") Utils.getResourceIdentifier(activity, ResourceType.ID, "youtube_controls_overlay")
/** /**
* id of R.id.controls_layout * id of R.id.controls_layout
*/ */
private val controlsLayoutId = private val controlsLayoutId =
Utils.getResourceIdentifier(activity, "controls_layout", "id") Utils.getResourceIdentifier(activity, ResourceType.ID, "controls_layout")
/** /**
* reference to the controls layout view * reference to the controls layout view

View file

@ -2,7 +2,6 @@ package app.revanced.extension.youtube.shared
import app.revanced.extension.shared.Logger import app.revanced.extension.shared.Logger
import app.revanced.extension.youtube.Event import app.revanced.extension.youtube.Event
import app.revanced.extension.youtube.patches.VideoInformation
/** /**
* Regular player type. * Regular player type.

View file

@ -24,7 +24,6 @@ import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -150,9 +149,9 @@ public class SegmentPlaybackController {
private static long skipSegmentButtonEndTime; private static long skipSegmentButtonEndTime;
@Nullable @Nullable
private static String timeWithoutSegments; private static String timeWithoutSegments;
private static int sponsorBarAbsoluteLeft; private static int seekbarAbsoluteLeft;
private static int sponsorAbsoluteBarRight; private static int seekbarAbsoluteRight;
private static int sponsorBarThickness; private static int seekbarThickness;
@Nullable @Nullable
private static SponsorSegment lastSegmentSkipped; private static SponsorSegment lastSegmentSkipped;
@ -908,31 +907,13 @@ public class SegmentPlaybackController {
* injection point. * injection point.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static void setSponsorBarRect(Object self) { public static void setSeekbarRectangle(Rect seekbarRect) {
try { final int left = seekbarRect.left;
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect"); final int right = seekbarRect.right;
field.setAccessible(true); if (seekbarAbsoluteLeft != left || seekbarAbsoluteRight != right) {
Rect rect = (Rect) Objects.requireNonNull(field.get(self)); Logger.printDebug(() -> "setSeekbarRectangle left: " + left + " right: " + right);
setSponsorBarAbsoluteLeft(rect); seekbarAbsoluteLeft = left;
setSponsorBarAbsoluteRight(rect); seekbarAbsoluteRight = right;
} catch (Exception ex) {
Logger.printException(() -> "setSponsorBarRect failure", ex);
}
}
private static void setSponsorBarAbsoluteLeft(Rect rect) {
final int left = rect.left;
if (sponsorBarAbsoluteLeft != left) {
Logger.printDebug(() -> "setSponsorBarAbsoluteLeft: " + left);
sponsorBarAbsoluteLeft = left;
}
}
private static void setSponsorBarAbsoluteRight(Rect rect) {
final int right = rect.right;
if (sponsorAbsoluteBarRight != right) {
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
sponsorAbsoluteBarRight = right;
} }
} }
@ -940,8 +921,8 @@ public class SegmentPlaybackController {
* injection point. * injection point.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static void setSponsorBarThickness(int thickness) { public static void setSeekbarThickness(int thickness) {
sponsorBarThickness = thickness; seekbarThickness = thickness;
} }
/** /**
@ -951,8 +932,7 @@ public class SegmentPlaybackController {
public static String appendTimeWithoutSegments(String totalTime) { public static String appendTimeWithoutSegments(String totalTime) {
try { try {
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get() if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments) && !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
&& !isAdProgressTextVisible()) {
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages // Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
} }
@ -980,6 +960,7 @@ public class SegmentPlaybackController {
continue; continue;
} }
foundNonhighlightSegments = true; foundNonhighlightSegments = true;
long start = segment.start; long start = segment.start;
final long end = segment.end; final long end = segment.end;
// To prevent nested segments from incorrectly counting additional time, // To prevent nested segments from incorrectly counting additional time,
@ -1011,17 +992,17 @@ public class SegmentPlaybackController {
* Injection point. * Injection point.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) { public static void drawSegmentTimeBars(final Canvas canvas, final float posY) {
try { try {
if (segments == null || isAdProgressTextVisible()) return; if (segments == null) return;
final long videoLength = VideoInformation.getVideoLength(); final long videoLength = VideoInformation.getVideoLength();
if (videoLength <= 0) return; if (videoLength <= 0) return;
final int thicknessDiv2 = sponsorBarThickness / 2; // rounds down final int thicknessDiv2 = seekbarThickness / 2; // Rounds down.
final float top = posY - (sponsorBarThickness - thicknessDiv2); final float top = posY - (seekbarThickness - thicknessDiv2);
final float bottom = posY + thicknessDiv2; final float bottom = posY + thicknessDiv2;
final float videoMillisecondsToPixels = (1f / videoLength) * (sponsorAbsoluteBarRight - sponsorBarAbsoluteLeft); final float videoMillisecondsToPixels = (1f / videoLength) * (seekbarAbsoluteRight - seekbarAbsoluteLeft);
final float leftPadding = sponsorBarAbsoluteLeft; final float leftPadding = seekbarAbsoluteLeft;
for (SponsorSegment segment : segments) { for (SponsorSegment segment : segments) {
final float left = leftPadding + segment.start * videoMillisecondsToPixels; final float left = leftPadding + segment.start * videoMillisecondsToPixels;

View file

@ -1,16 +1,26 @@
package app.revanced.extension.youtube.sponsorblock.ui; package app.revanced.extension.youtube.sponsorblock.ui;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController; import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton; import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class CreateSegmentButton { public class CreateSegmentButton {
private static final int DRAWABLE_SB_LOGO = Utils.getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, Utils.appIsUsingBoldIcons()
? "revanced_sb_logo_bold"
: "revanced_sb_logo"
);
@Nullable @Nullable
private static PlayerControlButton instance; private static PlayerControlButton instance;
@ -31,6 +41,14 @@ public class CreateSegmentButton {
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(), v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null null
); );
// FIXME: Bold YT player icons are currently forced off.
// Enable this logic when the new player icons are not forced off.
ImageView icon = Utils.getChildViewByResourceName(controlsView,
"revanced_sb_create_segment_button");
if (false) {
icon.setImageResource(DRAWABLE_SB_LOGO);
}
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex); Logger.printException(() -> "initialize failure", ex);
} }

View file

@ -16,6 +16,7 @@ import android.widget.ImageButton;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ui.Dim; import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils; import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
@ -45,8 +46,8 @@ public final class NewSegmentLayout extends FrameLayout {
final int defStyleAttr, final int defStyleRes) { final int defStyleAttr, final int defStyleRes) {
super(context, attributeSet, defStyleAttr, defStyleRes); super(context, attributeSet, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate( LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context,
getResourceIdentifierOrThrow(context, "revanced_sb_new_segment", "layout"), this, true ResourceType.LAYOUT, "revanced_sb_new_segment"), this, true
); );
initializeButton( initializeButton(
@ -105,7 +106,7 @@ public final class NewSegmentLayout extends FrameLayout {
*/ */
private void initializeButton(final Context context, final String resourceIdentifierName, private void initializeButton(final Context context, final String resourceIdentifierName,
final ButtonOnClickHandlerFunction handler, final String debugMessage) { final ButtonOnClickHandlerFunction handler, final String debugMessage) {
ImageButton button = findViewById(getResourceIdentifierOrThrow(context, resourceIdentifierName, "id")); ImageButton button = findViewById(getResourceIdentifierOrThrow(context, ResourceType.ID, resourceIdentifierName));
// Add ripple effect // Add ripple effect
RippleDrawable rippleDrawable = new RippleDrawable( RippleDrawable rippleDrawable = new RippleDrawable(

View file

@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController; import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment; import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
@ -57,11 +58,10 @@ public class SkipSponsorButton extends FrameLayout {
public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) { public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
super(context, attributeSet, defStyleAttr, defStyleRes); super(context, attributeSet, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context, LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context, ResourceType.LAYOUT, "revanced_sb_skip_sponsor_button"), this, true); // layout:skip_ad_button
"revanced_sb_skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
setMinimumHeight(getResourceDimensionPixelSize("ad_skip_ad_button_min_height")); // dimen:ad_skip_ad_button_min_height setMinimumHeight(getResourceDimensionPixelSize("ad_skip_ad_button_min_height")); // dimen:ad_skip_ad_button_min_height
skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifierOrThrow( skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifierOrThrow(
context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container context, ResourceType.ID, "revanced_sb_skip_sponsor_button_container"))); // id:skip_ad_button_container
background = new Paint(); background = new Paint();
background.setColor(getResourceColor("skip_ad_button_background_color")); // color:skip_ad_button_background_color); background.setColor(getResourceColor("skip_ad_button_background_color")); // color:skip_ad_button_background_color);
@ -72,7 +72,7 @@ public class SkipSponsorButton extends FrameLayout {
border.setStrokeWidth(getResourceDimension("ad_skip_ad_button_border_width")); // dimen:ad_skip_ad_button_border_width); border.setStrokeWidth(getResourceDimension("ad_skip_ad_button_border_width")); // dimen:ad_skip_ad_button_border_width);
border.setStyle(Paint.Style.STROKE); border.setStyle(Paint.Style.STROKE);
skipSponsorTextView = Objects.requireNonNull(findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text; skipSponsorTextView = Objects.requireNonNull(findViewById(getResourceIdentifier(context, ResourceType.ID, "revanced_sb_skip_sponsor_button_text"))); // id:skip_ad_button_text;
defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin
ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin

View file

@ -1,5 +1,6 @@
package app.revanced.extension.youtube.sponsorblock.ui; package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import android.content.Context; import android.content.Context;
@ -15,6 +16,7 @@ import java.lang.ref.WeakReference;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment; import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
@ -62,16 +64,17 @@ public class SponsorBlockViewController {
Context context = Utils.getContext(); Context context = Utils.getContext();
RelativeLayout layout = new RelativeLayout(context); RelativeLayout layout = new RelativeLayout(context);
layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT)); layout.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow( LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(
"revanced_sb_inline_sponsor_overlay", "layout"), layout); ResourceType.LAYOUT, "revanced_sb_inline_sponsor_overlay"), layout);
inlineSponsorOverlayRef = new WeakReference<>(layout); inlineSponsorOverlayRef = new WeakReference<>(layout);
viewGroup.addView(layout); viewGroup.addView(layout);
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override @Override
public void onChildViewAdded(View parent, View child) { public void onChildViewAdded(View parent, View child) {
// ensure SB buttons and controls are always on top, otherwise the endscreen cards can cover the skip button // Ensure SB buttons and controls are always on top, otherwise the end-screen cards can cover the skip button.
RelativeLayout layout = inlineSponsorOverlayRef.get(); RelativeLayout layout = inlineSponsorOverlayRef.get();
if (layout != null) { if (layout != null) {
layout.bringToFront(); layout.bringToFront();
@ -83,14 +86,14 @@ public class SponsorBlockViewController {
}); });
youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup); youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup);
skipHighlightButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow( skipHighlightButtonRef = new WeakReference<>(Objects.requireNonNull(layout.findViewById(
"revanced_sb_skip_highlight_button", "id"))); getResourceIdentifier(ResourceType.ID, "revanced_sb_skip_highlight_button"))));
skipSponsorButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow( skipSponsorButtonRef = new WeakReference<>(Objects.requireNonNull(layout.findViewById(
"revanced_sb_skip_sponsor_button", "id"))); getResourceIdentifier(ResourceType.ID, "revanced_sb_skip_sponsor_button"))));
NewSegmentLayout newSegmentLayout = layout.findViewById(getResourceIdentifierOrThrow( NewSegmentLayout newSegmentLayout = Objects.requireNonNull(layout.findViewById(
"revanced_sb_new_segment_view", "id")); getResourceIdentifier(ResourceType.ID, "revanced_sb_new_segment_view")));
newSegmentLayoutRef = new WeakReference<>(newSegmentLayout); newSegmentLayoutRef = new WeakReference<>(newSegmentLayout);
newSegmentLayout.updateLayout(); newSegmentLayout.updateLayout();

View file

@ -8,6 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import app.revanced.extension.shared.Logger.printDebug import app.revanced.extension.shared.Logger.printDebug
import app.revanced.extension.shared.Logger.printException import app.revanced.extension.shared.Logger.printException
import app.revanced.extension.youtube.patches.VersionCheckPatch
import app.revanced.extension.youtube.settings.Settings import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType import app.revanced.extension.youtube.shared.PlayerType
import app.revanced.extension.youtube.swipecontrols.controller.AudioVolumeController import app.revanced.extension.youtube.swipecontrols.controller.AudioVolumeController
@ -237,6 +238,8 @@ class SwipeControlsHostActivity : Activity() {
*/ */
@Suppress("unused") @Suppress("unused")
@JvmStatic @JvmStatic
fun allowSwipeChangeVideo(original: Boolean): Boolean = Settings.SWIPE_CHANGE_VIDEO.get() fun allowSwipeChangeVideo(original: Boolean): Boolean =
// Feature can cause crashing if forced in newer targets.
!VersionCheckPatch.IS_20_22_OR_GREATER && Settings.SWIPE_CHANGE_VIDEO.get()
} }
} }

View file

@ -3,6 +3,7 @@ package app.revanced.extension.youtube.swipecontrols.controller
import android.app.Activity import android.app.Activity
import android.util.TypedValue import android.util.TypedValue
import android.view.ViewGroup import android.view.ViewGroup
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.misc.Rectangle import app.revanced.extension.youtube.swipecontrols.misc.Rectangle
import app.revanced.extension.youtube.swipecontrols.misc.applyDimension import app.revanced.extension.youtube.swipecontrols.misc.applyDimension
@ -56,7 +57,8 @@ class SwipeZonesController(
/** /**
* id for R.id.player_view * id for R.id.player_view
*/ */
private val playerViewId = Utils.getResourceIdentifier(host, "player_view", "id") private val playerViewId = Utils.getResourceIdentifier(
host, ResourceType.ID, "player_view")
/** /**
* current bounding rectangle of the player * current bounding rectangle of the player

View file

@ -14,12 +14,13 @@ import android.util.AttributeSet
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.View import android.view.View
import android.widget.RelativeLayout import android.widget.RelativeLayout
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.StringRef.str import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay
import kotlin.math.min
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
import kotlin.math.round import kotlin.math.round
/** /**
@ -53,7 +54,7 @@ class SwipeControlsOverlayLayout(
// Function to retrieve drawable resources by name. // Function to retrieve drawable resources by name.
private fun getDrawable(name: String): Drawable { private fun getDrawable(name: String): Drawable {
val drawable = resources.getDrawable( val drawable = resources.getDrawable(
Utils.getResourceIdentifier(context, name, "drawable"), Utils.getResourceIdentifier(context, ResourceType.DRAWABLE, name),
context.theme, context.theme,
) )
drawable.setTint(config.overlayTextColor) drawable.setTint(config.overlayTextColor)
@ -86,7 +87,7 @@ class SwipeControlsOverlayLayout(
// Initialize horizontal progress bar. // Initialize horizontal progress bar.
val screenWidth = resources.displayMetrics.widthPixels val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp. val layoutWidth = (screenWidth * 4 / 5) // Cap at ~360dp.
horizontalProgressView = HorizontalProgressView( horizontalProgressView = HorizontalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
@ -630,7 +631,7 @@ class VerticalProgressView(
if (isMinimalStyle) { if (isMinimalStyle) {
canvas.drawText(displayText, textX, textStartY, textPaint) canvas.drawText(displayText, textX, textStartY, textPaint)
} else { } else {
val progressStartY = (iconEndY + padding).toFloat() val progressStartY = (iconEndY + padding)
val progressEndY = textStartY - textPaint.textSize - padding val progressEndY = textStartY - textPaint.textSize - padding
val progressHeight = progressEndY - progressStartY val progressHeight = progressEndY - progressStartY

View file

@ -3,8 +3,11 @@ package app.revanced.extension.youtube.videoplayer;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.view.View; import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@ -14,9 +17,9 @@ public class LoopVideoButton {
private static PlayerControlButton instance; private static PlayerControlButton instance;
private static final int LOOP_VIDEO_ON = Utils.getResourceIdentifierOrThrow( private static final int LOOP_VIDEO_ON = Utils.getResourceIdentifierOrThrow(
"revanced_loop_video_button_on", "drawable"); ResourceType.DRAWABLE, "revanced_loop_video_button_on");
private static final int LOOP_VIDEO_OFF = Utils.getResourceIdentifierOrThrow( private static final int LOOP_VIDEO_OFF = Utils.getResourceIdentifierOrThrow(
"revanced_loop_video_button_off", "drawable"); ResourceType.DRAWABLE,"revanced_loop_video_button_off");
/** /**
* Injection point. * Injection point.

View file

@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch; import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch;

View file

@ -2,5 +2,6 @@ org.gradle.caching = true
org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
android.uniquePackageNames=false
kotlin.code.style = official kotlin.code.style = official
version = 5.51.0-dev.2 version = 5.51.0-dev.2

View file

@ -1,35 +1,23 @@
[versions] [versions]
revanced-patcher = "21.0.0" revanced-patcher = "22.0.0"
# Tracking https://github.com/google/smali/issues/64. # Tracking https://github.com/google/smali/issues/64.
#noinspection GradleDependency #noinspection GradleDependency
smali = "3.0.5" smali = "3.0.8"
# 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818.
#noinspection GradleDependency
agp = "8.2.2" agp = "8.2.2"
annotation = "1.9.1" annotation = "1.9.1"
appcompat = "1.7.0" appcompat = "1.7.1"
okhttp = "5.0.0-alpha.14" okhttp = "5.3.2"
retrofit = "2.11.0" retrofit = "3.0.0"
guava = "33.5.0-jre" guava = "33.5.0-jre"
protobuf-javalite = "4.32.0" apksig = "9.0.1"
protoc = "4.32.0"
protobuf = "0.9.5"
antlr4 = "4.13.2"
nanohttpd = "2.3.1"
apksig = "8.10.1"
[libraries] [libraries]
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
antlr4 = { module = "org.antlr:antlr4", version.ref = "antlr4" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf-javalite" }
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protoc" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
guava = { module = "com.google.guava:guava", version.ref = "guava" } guava = { module = "com.google.guava:guava", version.ref = "guava" }
apksig = { group = "com.android.tools.build", name = "apksig", version.ref = "apksig" } apksig = { group = "com.android.tools.build", name = "apksig", version.ref = "apksig" }
[plugins] [plugins]
android-library = { id = "com.android.library" } android-library = { id = "com.android.library" }
protobuf = { id = "com.google.protobuf", version.ref = "protobuf" }

View file

@ -1,6 +1,7 @@
#Mon Jun 16 14:39:32 CEST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,10 @@ dependencies {
kotlin { kotlin {
compilerOptions { compilerOptions {
freeCompilerArgs = listOf("-Xcontext-receivers") freeCompilerArgs.addAll(
"-Xexplicit-backing-fields",
"-Xcontext-parameters"
)
} }
} }

View file

@ -8,7 +8,7 @@ val exportAllActivitiesPatch = resourcePatch(
description = "Makes all app activities exportable.", description = "Makes all app activities exportable.",
use = false, use = false,
) { ) {
execute { apply {
val exportedFlag = "android:exported" val exportedFlag = "android:exported"
document("AndroidManifest.xml").use { document -> document("AndroidManifest.xml").use { document ->

View file

@ -1,6 +1,6 @@
package app.revanced.patches.all.misc.adb package app.revanced.patches.all.misc.adb
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.util.getReference import app.revanced.util.getReference
@ -13,27 +13,26 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/all/misc/hide/adb/HideAdbPatch;" private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/all/misc/hide/adb/HideAdbPatch;"
private val SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE = ImmutableMethodReference( private val SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE = ImmutableMethodReference(
"Landroid/provider/Settings\$Global;", $$"Landroid/provider/Settings$Global;",
"getInt", "getInt",
listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;"), listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;"),
"I" "I",
) )
private val SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE = ImmutableMethodReference( private val SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE = ImmutableMethodReference(
"Landroid/provider/Settings\$Global;", $$"Landroid/provider/Settings$Global;",
"getInt", "getInt",
listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;", "I"), listOf("Landroid/content/ContentResolver;", "Ljava/lang/String;", "I"),
"I" "I",
) )
private fun MethodReference.anyMethodSignatureMatches(vararg anyOf: MethodReference): Boolean { private val getIntMethodReferences = listOf(
return anyOf.any { SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE,
MethodUtil.methodSignaturesMatch(it, this) SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE,
} )
}
@Suppress("unused") @Suppress("unused")
val hideAdbStatusPatch = bytecodePatch( val hideADBStatusPatch = bytecodePatch(
name = "Hide ADB status", name = "Hide ADB status",
description = "Hides enabled development settings and/or ADB.", description = "Hides enabled development settings and/or ADB.",
use = false, use = false,
@ -46,11 +45,8 @@ val hideAdbStatusPatch = bytecodePatch(
val reference = instruction val reference = instruction
.takeIf { it.opcode == Opcode.INVOKE_STATIC } .takeIf { it.opcode == Opcode.INVOKE_STATIC }
?.getReference<MethodReference>() ?.getReference<MethodReference>()
?.takeIf { ?.takeIf { reference ->
it.anyMethodSignatureMatches( getIntMethodReferences.any { MethodUtil.methodSignaturesMatch(it, reference) }
SETTINGS_GLOBAL_GET_INT_OR_THROW_METHOD_REFERENCE,
SETTINGS_GLOBAL_GET_INT_OR_DEFAULT_METHOD_REFERENCE
)
} }
?: return@filterMap null ?: return@filterMap null
@ -67,9 +63,9 @@ val hideAdbStatusPatch = bytecodePatch(
method.replaceInstruction( method.replaceInstruction(
index, index,
"invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->getInt($parameterString)I" "invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->getInt($parameterString)I",
) )
} },
) ),
) )
} }

View file

@ -3,8 +3,8 @@ package app.revanced.patches.all.misc.appicon
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence import app.revanced.util.asSequence
import app.revanced.util.childElementsSequence import app.revanced.util.childElementsSequence
import java.util.logging.Logger
import org.w3c.dom.Element import org.w3c.dom.Element
import java.util.logging.Logger
@Suppress("unused") @Suppress("unused")
val hideAppIconPatch = resourcePatch( val hideAppIconPatch = resourcePatch(
@ -12,7 +12,7 @@ val hideAppIconPatch = resourcePatch(
description = "Hides the app icon from the Android launcher.", description = "Hides the app icon from the Android launcher.",
use = false, use = false,
) { ) {
execute { apply {
document("AndroidManifest.xml").use { document -> document("AndroidManifest.xml").use { document ->
var changed = false var changed = false
@ -26,6 +26,7 @@ val hideAppIconPatch = resourcePatch(
"action" -> if (child.getAttribute("android:name") == "android.intent.action.MAIN") { "action" -> if (child.getAttribute("android:name") == "android.intent.action.MAIN") {
hasMainAction = true hasMainAction = true
} }
"category" -> if (child.getAttribute("android:name") == "android.intent.category.LAUNCHER") { "category" -> if (child.getAttribute("android:name") == "android.intent.category.LAUNCHER") {
launcherCategory = child launcherCategory = child
} }
@ -45,4 +46,3 @@ val hideAppIconPatch = resourcePatch(
} }
} }
} }

View file

@ -1,7 +1,7 @@
package app.revanced.patches.all.misc.build package app.revanced.patches.all.misc.build
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.util.getReference import app.revanced.util.getReference

View file

@ -11,172 +11,148 @@ val spoofBuildInfoPatch = bytecodePatch(
use = false, use = false,
) { ) {
val board by stringOption( val board by stringOption(
key = "board",
default = null, default = null,
title = "Board", name = "Board",
description = "The name of the underlying board, like \"goldfish\".", description = "The name of the underlying board, like \"goldfish\".",
) )
val bootloader by stringOption( val bootloader by stringOption(
key = "bootloader",
default = null, default = null,
title = "Bootloader", name = "Bootloader",
description = "The system bootloader version number.", description = "The system bootloader version number.",
) )
val brand by stringOption( val brand by stringOption(
key = "brand",
default = null, default = null,
title = "Brand", name = "Brand",
description = "The consumer-visible brand with which the product/hardware will be associated, if any.", description = "The consumer-visible brand with which the product/hardware will be associated, if any.",
) )
val cpuAbi by stringOption( val cpuAbi by stringOption(
key = "cpu-abi",
default = null, default = null,
title = "CPU ABI", name = "CPU ABI",
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.", description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.",
) )
val cpuAbi2 by stringOption( val cpuAbi2 by stringOption(
key = "cpu-abi-2",
default = null, default = null,
title = "CPU ABI 2", name = "CPU ABI 2",
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.", description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.",
) )
val device by stringOption( val device by stringOption(
key = "device",
default = null, default = null,
title = "Device", name = "Device",
description = "The name of the industrial design.", description = "The name of the industrial design.",
) )
val display by stringOption( val display by stringOption(
key = "display",
default = null, default = null,
title = "Display", name = "Display",
description = "A build ID string meant for displaying to the user.", description = "A build ID string meant for displaying to the user.",
) )
val fingerprint by stringOption( val fingerprint by stringOption(
key = "fingerprint",
default = null, default = null,
title = "Fingerprint", name = "Fingerprint",
description = "A string that uniquely identifies this build.", description = "A string that uniquely identifies this build.",
) )
val hardware by stringOption( val hardware by stringOption(
key = "hardware",
default = null, default = null,
title = "Hardware", name = "Hardware",
description = "The name of the hardware (from the kernel command line or /proc).", description = "The name of the hardware (from the kernel command line or /proc).",
) )
val host by stringOption( val host by stringOption(
key = "host",
default = null, default = null,
title = "Host", name = "Host",
description = "The host.", description = "The host.",
) )
val id by stringOption( val id by stringOption(
key = "id",
default = null, default = null,
title = "ID", name = "ID",
description = "Either a changelist number, or a label like \"M4-rc20\".", description = "Either a changelist number, or a label like \"M4-rc20\".",
) )
val manufacturer by stringOption( val manufacturer by stringOption(
key = "manufacturer",
default = null, default = null,
title = "Manufacturer", name = "Manufacturer",
description = "The manufacturer of the product/hardware.", description = "The manufacturer of the product/hardware.",
) )
val model by stringOption( val model by stringOption(
key = "model",
default = null, default = null,
title = "Model", name = "Model",
description = "The end-user-visible name for the end product.", description = "The end-user-visible name for the end product.",
) )
val odmSku by stringOption( val odmSku by stringOption(
key = "odm-sku",
default = null, default = null,
title = "ODM SKU", name = "ODM SKU",
description = "The SKU of the device as set by the original design manufacturer (ODM).", description = "The SKU of the device as set by the original design manufacturer (ODM).",
) )
val product by stringOption( val product by stringOption(
key = "product",
default = null, default = null,
title = "Product", name = "Product",
description = "The name of the overall product.", description = "The name of the overall product.",
) )
val radio by stringOption( val radio by stringOption(
key = "radio",
default = null, default = null,
title = "Radio", name = "Radio",
description = "This field was deprecated in API level 15. " + description = "This field was deprecated in API level 15. " +
"The radio firmware version is frequently not available when this class is initialized, " + "The radio firmware version is frequently not available when this class is initialized, " +
"leading to a blank or \"unknown\" value for this string. Use getRadioVersion() instead.", "leading to a blank or \"unknown\" value for this string. Use getRadioVersion() instead.",
) )
val serial by stringOption( val serial by stringOption(
key = "serial",
default = null, default = null,
title = "Serial", name = "Serial",
description = "This field was deprecated in API level 26. Use getSerial() instead.", description = "This field was deprecated in API level 26. Use getSerial() instead.",
) )
val sku by stringOption( val sku by stringOption(
key = "sku",
default = null, default = null,
title = "SKU", name = "SKU",
description = "The SKU of the hardware (from the kernel command line).", description = "The SKU of the hardware (from the kernel command line).",
) )
val socManufacturer by stringOption( val socManufacturer by stringOption(
key = "soc-manufacturer",
default = null, default = null,
title = "SOC manufacturer", name = "SOC manufacturer",
description = "The manufacturer of the device's primary system-on-chip.", description = "The manufacturer of the device's primary system-on-chip.",
) )
val socModel by stringOption( val socModel by stringOption(
key = "soc-model",
default = null, default = null,
title = "SOC model", name = "SOC model",
description = "The model name of the device's primary system-on-chip.", description = "The model name of the device's primary system-on-chip.",
) )
val tags by stringOption( val tags by stringOption(
key = "tags",
default = null, default = null,
title = "Tags", name = "Tags",
description = "Comma-separated tags describing the build, like \"unsigned,debug\".", description = "Comma-separated tags describing the build, like \"unsigned,debug\".",
) )
val time by longOption( val time by longOption(
key = "time",
default = null, default = null,
title = "Time", name = "Time",
description = "The time at which the build was produced, given in milliseconds since the UNIX epoch.", description = "The time at which the build was produced, given in milliseconds since the UNIX epoch.",
) )
val type by stringOption( val type by stringOption(
key = "type",
default = null, default = null,
title = "Type", name = "Type",
description = "The type of build, like \"user\" or \"eng\".", description = "The type of build, like \"user\" or \"eng\".",
) )
val user by stringOption( val user by stringOption(
key = "user",
default = null, default = null,
title = "User", name = "User",
description = "The user.", description = "The user.",
) )
@ -209,6 +185,5 @@ val spoofBuildInfoPatch = bytecodePatch(
user, user,
) )
}, },
) )
} }

View file

@ -1,8 +1,6 @@
@file:Suppress("unused")
package app.revanced.patches.all.misc.connectivity.location.hide package app.revanced.patches.all.misc.connectivity.location.hide
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.IMethodCall import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.fromMethodReference import app.revanced.patches.all.misc.transformation.fromMethodReference

View file

@ -1,9 +0,0 @@
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch was renamed", ReplaceWith("spoofSimProviderPatch"))
@Suppress("unused")
val spoofSimCountryPatch = bytecodePatch {
dependsOn(spoofSimProviderPatch)
}

View file

@ -1,21 +1,21 @@
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.intOption import app.revanced.patcher.patch.intOption
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
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.immutable.reference.ImmutableMethodReference import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.Locale import java.util.*
@Suppress("unused") @Suppress("unused")
val spoofSimProviderPatch = bytecodePatch( val spoofSIMProviderPatch = bytecodePatch(
name = "Spoof SIM provider", name = "Spoof SIM provider",
description = "Spoofs information about the SIM card provider.", description = "Spoofs information about the SIM card provider.",
use = false, use = false,
@ -23,13 +23,11 @@ val spoofSimProviderPatch = bytecodePatch(
val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry } val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
fun isoCountryPatchOption( fun isoCountryPatchOption(
key: String, name: String,
title: String,
) = stringOption( ) = stringOption(
key, name,
null, null,
countries, countries,
title,
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.", "ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
false, false,
validator = { it: String? -> it == null || it.uppercase() in countries.values }, validator = { it: String? -> it == null || it.uppercase() in countries.values },
@ -37,39 +35,29 @@ val spoofSimProviderPatch = bytecodePatch(
fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999) fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999)
val networkCountryIso by isoCountryPatchOption( val networkCountryIso by isoCountryPatchOption("Network ISO country code")
"networkCountryIso",
"Network ISO country code",
)
val networkOperator by intOption( val networkOperator by intOption(
key = "networkOperator", name = "MCC+MNC network operator code",
title = "MCC+MNC network operator code",
description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the network operator.", description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the network operator.",
validator = { isMccMncValid(it) } validator = { isMccMncValid(it) },
) )
val networkOperatorName by stringOption( val networkOperatorName by stringOption(
key = "networkOperatorName", name = "Network operator name",
title = "Network operator name",
description = "The full name of the network operator.", description = "The full name of the network operator.",
) )
val simCountryIso by isoCountryPatchOption( val simCountryIso by isoCountryPatchOption("SIM ISO country code")
"simCountryIso",
"SIM ISO country code",
)
val simOperator by intOption( val simOperator by intOption(
key = "simOperator", name = "MCC+MNC SIM operator code",
title = "MCC+MNC SIM operator code",
description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the SIM operator.", description = "The 5 or 6 digits MCC+MNC (Mobile Country Code + Mobile Network Code) of the SIM operator.",
validator = { isMccMncValid(it) } validator = { isMccMncValid(it) },
) )
val simOperatorName by stringOption( val simOperatorName by stringOption(
key = "simOperatorName", name = "SIM operator name",
title = "SIM operator name",
description = "The full name of the SIM operator.", description = "The full name of the SIM operator.",
) )

View file

@ -11,7 +11,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;" private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
@Suppress("unused") @Suppress("unused")
val spoofWifiPatch = bytecodePatch( val spoofWiFiConnectionPatch = bytecodePatch(
name = "Spoof Wi-Fi connection", name = "Spoof Wi-Fi connection",
description = "Spoofs an existing Wi-Fi connection.", description = "Spoofs an existing Wi-Fi connection.",
use = false, use = false,

Some files were not shown because too many files have changed in this diff Show more