feat: Update YouTube & YouTube Music patches (#6571)
This commit is a squash of multiple commits, authored by the individuals referenced below. To see the exact commits by each author, see the unsquashed tree at https://github.com/ReVanced/revanced-patches/pull/6571 or with commit 03940665d27a42ed08992757dfe4534bd8243356. Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de> Co-authored-by: hoodles <207470673+hoo-dles@users.noreply.github.com> Co-authored-by: ILoveOpenSourceApplications <117499019+iloveopensourceapplications@users.noreply.github.com> Co-authored-by: LisoUseInAIKyrios <118716522+lisouseinaikyrios@users.noreply.github.com> Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> Co-authored-by: OxrxL <108184954+oxrxl@users.noreply.github.com>
This commit is contained in:
parent
db5e0fe587
commit
88d33b847d
300 changed files with 8226 additions and 3643 deletions
|
|
@ -16,8 +16,8 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
|||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||
|
||||
/**
|
||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||
* and additionally accessible thru {@link LogBufferManager}.
|
||||
* ReVanced specific logger. Logging is done to standard device log (accessible through ADB),
|
||||
* and additionally accessible through {@link LogBufferManager}.
|
||||
*
|
||||
* All methods are thread safe, and are safe to call even
|
||||
* if {@link Utils#getContext()} is not available.
|
||||
|
|
@ -202,7 +202,7 @@ public class Logger {
|
|||
/**
|
||||
* Logs exceptions under the outer class name of the code calling this method.
|
||||
* <p>
|
||||
* If the calling code is showing it's own error toast,
|
||||
* If the calling code is showing its own error toast,
|
||||
* instead use {@link #printInfo(LogMessage, Exception)}
|
||||
*
|
||||
* @param message log message
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class StringRef {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a StringRef object that'll not change it's value
|
||||
* Creates a StringRef object that'll not change its value
|
||||
*
|
||||
* @param value value which toString() method returns when invoked on returned object
|
||||
* @return Unique StringRef instance, its value will never change
|
||||
|
|
@ -102,7 +102,7 @@ public class StringRef {
|
|||
public String toString() {
|
||||
if (!resolved) {
|
||||
if (resources == null || packageName == null) {
|
||||
Context context = Utils.getContext();
|
||||
var context = Utils.getContext();
|
||||
resources = context.getResources();
|
||||
packageName = context.getPackageName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,14 +106,18 @@ public abstract class TrieSearch<T> {
|
|||
* Elements not contained can collide with elements the array does contain,
|
||||
* so must compare the nodes character value.
|
||||
*
|
||||
* Alternatively this array could be a sorted and densely packed array,
|
||||
* and lookup is done using binary search.
|
||||
* That would save a small amount of memory because there's no null children entries,
|
||||
* but would give a worst case search of O(nlog(m)) where n is the number of
|
||||
* characters in the searched text and m is the maximum size of the sorted character arrays.
|
||||
* Using a hash table array always gives O(n) search time.
|
||||
* The memory usage here is very small (all Litho filters use ~10KB of memory),
|
||||
* so the more performant hash implementation is chosen.
|
||||
/*
|
||||
* Alternatively, this could be implemented as a sorted, densely packed array
|
||||
* with lookups performed via binary search.
|
||||
* This approach would save a small amount of memory by eliminating null
|
||||
* child entries. However, it would result in a worst-case lookup time of
|
||||
* O(n log m), where:
|
||||
* - n is the number of characters in the input text, and
|
||||
* - m is the maximum size of the sorted character arrays.
|
||||
* In contrast, using a hash-based array guarantees O(n) lookup time.
|
||||
* Given that the total memory usage is already very small (all Litho filters
|
||||
* together use approximately 10KB), the hash-based implementation is preferred
|
||||
* for its superior performance.
|
||||
*/
|
||||
@Nullable
|
||||
private TrieNode<T>[] children;
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its visibility to GONE.
|
||||
* Hide a view by setting its visibility as GONE.
|
||||
*
|
||||
* @param setting The setting to check for hiding the view.
|
||||
* @param view The view to hide.
|
||||
|
|
@ -218,7 +218,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its visibility to GONE.
|
||||
* Hide a view by setting its visibility as GONE.
|
||||
*
|
||||
* @param condition The setting to check for hiding the view.
|
||||
* @param view The view to hide.
|
||||
|
|
@ -288,7 +288,7 @@ public class Utils {
|
|||
// Could do a thread sleep, but that will trigger an exception if the thread is interrupted.
|
||||
meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random()));
|
||||
}
|
||||
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work,
|
||||
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time-wasting work,
|
||||
// leaving an empty loop that hammers on the System.currentTimeMillis native call.
|
||||
return meaninglessValue;
|
||||
}
|
||||
|
|
@ -298,10 +298,12 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static int indexOfFirstFound(String value, String... targets) {
|
||||
for (String string : targets) {
|
||||
if (!string.isEmpty()) {
|
||||
final int indexOf = value.indexOf(string);
|
||||
if (indexOf >= 0) return indexOf;
|
||||
if (isNotEmpty(value)) {
|
||||
for (String string : targets) {
|
||||
if (!string.isEmpty()) {
|
||||
final int indexOf = value.indexOf(string);
|
||||
if (indexOf >= 0) return indexOf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
@ -473,6 +475,10 @@ public class Utils {
|
|||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
public static boolean isNotEmpty(@Nullable String str) {
|
||||
return str != null && !str.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isTablet() {
|
||||
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
||||
}
|
||||
|
|
@ -481,7 +487,7 @@ public class Utils {
|
|||
private static Boolean isRightToLeftTextLayout;
|
||||
|
||||
/**
|
||||
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc).
|
||||
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc.).
|
||||
* If this should match any ReVanced language override then instead use
|
||||
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
|
||||
* This is the default locale of the device, which may differ if
|
||||
|
|
@ -495,7 +501,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc).
|
||||
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc.).
|
||||
*/
|
||||
public static boolean isRightToLeftLocale(Locale locale) {
|
||||
String displayLanguage = locale.getDisplayLanguage();
|
||||
|
|
@ -524,7 +530,7 @@ public class Utils {
|
|||
|
||||
/**
|
||||
* @return if the text contains at least 1 number character,
|
||||
* including any unicode numbers such as Arabic.
|
||||
* including any Unicode numbers such as Arabic.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public static boolean containsNumber(CharSequence text) {
|
||||
|
|
@ -1156,7 +1162,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
|
||||
* Uses {@link #adjustColorBrightness(int, float)} depending on if light or dark mode is active.
|
||||
*/
|
||||
@ColorInt
|
||||
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
|
||||
|
|
|
|||
|
|
@ -288,8 +288,8 @@ public final class CheckEnvironmentPatch {
|
|||
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
||||
Boolean timeCheckPassed = nearPatchTime.check();
|
||||
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
// Allow installing recently patched apks,
|
||||
// even if the install source is not Manager or ADB.
|
||||
// Allow installing recently patched APKs,
|
||||
// even if the installation source is not Manager or ADB.
|
||||
Check.disableForever();
|
||||
return;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import android.content.pm.PackageManager;
|
|||
import android.graphics.Color;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
|
@ -53,24 +55,35 @@ public class CustomBrandingPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private static final int notificationSmallIcon;
|
||||
@Nullable
|
||||
private static Integer notificationSmallIcon;
|
||||
|
||||
static {
|
||||
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
||||
if (branding == BrandingTheme.ORIGINAL) {
|
||||
notificationSmallIcon = 0;
|
||||
} else {
|
||||
// Original icon is quantum_ic_video_youtube_white_24
|
||||
String iconName = "revanced_notification_icon";
|
||||
if (branding == BrandingTheme.CUSTOM) {
|
||||
iconName += "_custom";
|
||||
private static int getNotificationSmallIcon() {
|
||||
// Cannot use static initialization block otherwise cyclic references exist
|
||||
// between Settings initialization and this class.
|
||||
if (notificationSmallIcon == null) {
|
||||
if (GmsCoreSupport.isPackageNameOriginal()) {
|
||||
Logger.printDebug(() -> "App is root mounted. Not overriding small notification icon");
|
||||
return notificationSmallIcon = 0;
|
||||
}
|
||||
|
||||
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
||||
if (notificationSmallIcon == 0) {
|
||||
Logger.printException(() -> "Could not load notification small icon");
|
||||
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
||||
if (branding == BrandingTheme.ORIGINAL) {
|
||||
notificationSmallIcon = 0;
|
||||
} else {
|
||||
// Original icon is quantum_ic_video_youtube_white_24
|
||||
String iconName = "revanced_notification_icon";
|
||||
if (branding == BrandingTheme.CUSTOM) {
|
||||
iconName += "_custom";
|
||||
}
|
||||
|
||||
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
||||
if (notificationSmallIcon == 0) {
|
||||
Logger.printException(() -> "Could not load notification small icon");
|
||||
}
|
||||
}
|
||||
}
|
||||
return notificationSmallIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -89,8 +102,9 @@ public class CustomBrandingPatch {
|
|||
*/
|
||||
public static void setNotificationIcon(Notification.Builder builder) {
|
||||
try {
|
||||
if (notificationSmallIcon != 0) {
|
||||
builder.setSmallIcon(notificationSmallIcon)
|
||||
final int smallIcon = getNotificationSmallIcon();
|
||||
if (smallIcon != 0) {
|
||||
builder.setSmallIcon(smallIcon)
|
||||
.setColor(Color.TRANSPARENT); // Remove YT red tint.
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
|
@ -104,8 +118,41 @@ public class CustomBrandingPatch {
|
|||
* The total number of app name aliases, including dummy aliases.
|
||||
*/
|
||||
private static int numberOfPresetAppNames() {
|
||||
// Modified during patching.
|
||||
throw new IllegalStateException();
|
||||
// Modified during patching, but requires a default if custom branding is excluded.
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* If a custom icon was provided during patching.
|
||||
*/
|
||||
private static boolean userProvidedCustomIcon() {
|
||||
// Modified during patching, but requires a default if custom branding is excluded.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* If a custom name was provided during patching.
|
||||
*/
|
||||
private static boolean userProvidedCustomName() {
|
||||
// Modified during patching, but requires a default if custom branding is excluded..
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getDefaultAppNameIndex() {
|
||||
return userProvidedCustomName()
|
||||
? numberOfPresetAppNames()
|
||||
: 1;
|
||||
}
|
||||
|
||||
public static BrandingTheme getDefaultIconStyle() {
|
||||
return userProvidedCustomIcon()
|
||||
? BrandingTheme.CUSTOM
|
||||
: BrandingTheme.ORIGINAL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -41,12 +41,13 @@ public final class EnableDebuggingPatch {
|
|||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
||||
if (LOG_FEATURE_FLAGS && value) {
|
||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) {
|
||||
Long flagObj = flag;
|
||||
if (DISABLED_FEATURE_FLAGS.contains(flagObj)) {
|
||||
return false;
|
||||
}
|
||||
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
|
||||
if (featureFlags.putIfAbsent(flagObj, TRUE) == null) {
|
||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +60,8 @@ public final class EnableDebuggingPatch {
|
|||
*/
|
||||
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
||||
|
||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||
// Align the log outputs to make post processing easier.
|
||||
Logger.printDebug(() -> " double feature is enabled: " + flag
|
||||
|
|
@ -74,6 +77,8 @@ public final class EnableDebuggingPatch {
|
|||
*/
|
||||
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
||||
|
||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||
Logger.printDebug(() -> " long feature is enabled: " + flag
|
||||
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ public final class SanitizeSharingLinksPatch {
|
|||
* Injection point.
|
||||
*/
|
||||
public static String sanitize(String url) {
|
||||
if (BaseSettings.SANITIZE_SHARED_LINKS.get()) {
|
||||
url = sanitizer.sanitizeUrlString(url);
|
||||
if (BaseSettings.SANITIZE_SHARING_LINKS.get()) {
|
||||
url = sanitizer.sanitizeURLString(url);
|
||||
}
|
||||
|
||||
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
|
|
@ -24,7 +25,7 @@ import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
|||
@SuppressWarnings("unused")
|
||||
public final class CustomFilter extends Filter {
|
||||
|
||||
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
||||
private static void showInvalidSyntaxToast(String expression) {
|
||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +37,12 @@ public final class CustomFilter extends Filter {
|
|||
public static final String SYNTAX_STARTS_WITH = "^";
|
||||
|
||||
/**
|
||||
* Optional character that separates the path from a proto buffer string pattern.
|
||||
* Optional character that separates the path from an accessibility string pattern.
|
||||
*/
|
||||
public static final String SYNTAX_ACCESSIBILITY_SYMBOL = "#";
|
||||
|
||||
/**
|
||||
* Optional character that separates the path/accessibility from a proto buffer string pattern.
|
||||
*/
|
||||
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
||||
|
||||
|
|
@ -51,15 +57,21 @@ public final class CustomFilter extends Filter {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Map key is the path including optional special characters (^ and/or $)
|
||||
// Map key is the full path including optional special characters (^, #, $),
|
||||
// and any accessibility pattern, but does not contain any buffer patterns.
|
||||
Map<String, CustomFilterGroup> result = new HashMap<>();
|
||||
|
||||
Pattern pattern = Pattern.compile(
|
||||
"(" // map key group
|
||||
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)" // optional starts with
|
||||
+ "([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*)" // path
|
||||
+ "(\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E?)" // optional buffer symbol
|
||||
+ ")" // end map key group
|
||||
+ "(.*)"); // optional buffer string
|
||||
"(" // Map key group.
|
||||
// Optional starts with.
|
||||
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)"
|
||||
// Path string.
|
||||
+ "([^\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + SYNTAX_BUFFER_SYMBOL + "\\E]*)"
|
||||
// Optional accessibility string.
|
||||
+ "(?:\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + "\\E([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*))?"
|
||||
// Optional buffer string.
|
||||
+ "(?:\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E(.*))?"
|
||||
+ ")"); // end map key group
|
||||
|
||||
for (String expression : rawCustomFilterText.split("\n")) {
|
||||
if (expression.isBlank()) continue;
|
||||
|
|
@ -73,10 +85,12 @@ public final class CustomFilter extends Filter {
|
|||
final String mapKey = matcher.group(1);
|
||||
final boolean pathStartsWith = !matcher.group(2).isEmpty();
|
||||
final String path = matcher.group(3);
|
||||
final boolean hasBufferSymbol = !matcher.group(4).isEmpty();
|
||||
final String bufferString = matcher.group(5);
|
||||
final String accessibility = matcher.group(4); // null if not present
|
||||
final String buffer = matcher.group(5); // null if not present
|
||||
|
||||
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
||||
if (path.isBlank()
|
||||
|| (accessibility != null && accessibility.isEmpty())
|
||||
|| (buffer != null && buffer.isEmpty())) {
|
||||
showInvalidSyntaxToast(expression);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -89,8 +103,13 @@ public final class CustomFilter extends Filter {
|
|||
group = new CustomFilterGroup(pathStartsWith, path);
|
||||
result.put(mapKey, group);
|
||||
}
|
||||
if (hasBufferSymbol) {
|
||||
group.addBufferString(bufferString);
|
||||
|
||||
if (accessibility != null) {
|
||||
group.addAccessibilityString(accessibility);
|
||||
}
|
||||
|
||||
if (buffer != null) {
|
||||
group.addBufferString(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,14 +117,22 @@ public final class CustomFilter extends Filter {
|
|||
}
|
||||
|
||||
final boolean startsWith;
|
||||
StringTrieSearch accessibilitySearch;
|
||||
ByteTrieSearch bufferSearch;
|
||||
|
||||
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
||||
CustomFilterGroup(boolean startsWith, String path) {
|
||||
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||
this.startsWith = startsWith;
|
||||
}
|
||||
|
||||
void addBufferString(@NonNull String bufferString) {
|
||||
void addAccessibilityString(String accessibilityString) {
|
||||
if (accessibilitySearch == null) {
|
||||
accessibilitySearch = new StringTrieSearch();
|
||||
}
|
||||
accessibilitySearch.addPattern(accessibilityString);
|
||||
}
|
||||
|
||||
void addBufferString(String bufferString) {
|
||||
if (bufferSearch == null) {
|
||||
bufferSearch = new ByteTrieSearch();
|
||||
}
|
||||
|
|
@ -117,6 +144,11 @@ public final class CustomFilter extends Filter {
|
|||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("CustomFilterGroup{");
|
||||
if (accessibilitySearch != null) {
|
||||
builder.append(", accessibility=");
|
||||
builder.append(accessibilitySearch.getPatterns());
|
||||
}
|
||||
|
||||
builder.append("path=");
|
||||
if (startsWith) builder.append(SYNTAX_STARTS_WITH);
|
||||
builder.append(filters[0]);
|
||||
|
|
@ -146,18 +178,26 @@ public final class CustomFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// All callbacks are custom filter groups.
|
||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||
|
||||
// Check path start requirement.
|
||||
if (custom.startsWith && contentIndex != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (custom.bufferSearch == null) {
|
||||
return true; // No buffer filter, only path filtering.
|
||||
// Check accessibility string if specified.
|
||||
if (custom.accessibilitySearch != null && !custom.accessibilitySearch.matches(accessibility)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return custom.bufferSearch.matches(buffer);
|
||||
// Check buffer if specified.
|
||||
if (custom.bufferSearch != null && !custom.bufferSearch.matches(buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // All custom filter conditions passed.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import java.util.List;
|
|||
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
||||
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||
*
|
||||
* To filter {@link FilterContentType#PROTOBUFFER}, first add a callback to
|
||||
* To filter {@link FilterContentType#PROTOBUFFER} or {@link FilterContentType#ACCESSIBILITY}, first add a callback to
|
||||
* either an identifier or a path.
|
||||
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* Then inside {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
||||
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||
*
|
||||
|
|
@ -26,6 +26,7 @@ public abstract class Filter {
|
|||
public enum FilterContentType {
|
||||
IDENTIFIER,
|
||||
PATH,
|
||||
ACCESSIBILITY,
|
||||
PROTOBUFFER
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ public abstract class Filter {
|
|||
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, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* if any of the groups are found.
|
||||
*/
|
||||
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
||||
|
|
@ -49,7 +50,7 @@ public abstract class Filter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* if any of the groups are found.
|
||||
*/
|
||||
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
||||
|
|
@ -63,12 +64,15 @@ public abstract class Filter {
|
|||
* <p>
|
||||
* Method is called off the main thread.
|
||||
*
|
||||
* @param identifier Litho identifier.
|
||||
* @param accessibility Accessibility string, or an empty string if not present for the component.
|
||||
* @param buffer Protocol buffer.
|
||||
* @param matchedGroup The actual filter that matched.
|
||||
* @param contentType The type of content matched.
|
||||
* @param contentIndex Matched index of the identifier or path.
|
||||
* @return True if the litho component should be filtered out.
|
||||
*/
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ public abstract class FilterGroup<T> {
|
|||
|
||||
/**
|
||||
* If you have more than 1 filter patterns, then all instances of
|
||||
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||
* this class should be filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||
* which uses a prefix tree to give better performance.
|
||||
*/
|
||||
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||
|
|
@ -149,7 +149,7 @@ public abstract class FilterGroup<T> {
|
|||
}
|
||||
|
||||
private static int[] createFailurePattern(byte[] pattern) {
|
||||
// Computes the failure function using a boot-strapping process,
|
||||
// Computes the failure function using a bootstrapping process,
|
||||
// where the pattern is matched against itself.
|
||||
final int patternLength = pattern.length;
|
||||
final int[] failure = new int[patternLength];
|
||||
|
|
|
|||
|
|
@ -24,11 +24,14 @@ public final class LithoFilterPatch {
|
|||
private static final class LithoFilterParameters {
|
||||
final String identifier;
|
||||
final String path;
|
||||
final String accessibility;
|
||||
final byte[] buffer;
|
||||
|
||||
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
|
||||
LithoFilterParameters(String lithoIdentifier, String lithoPath,
|
||||
String accessibility, byte[] buffer) {
|
||||
this.identifier = lithoIdentifier;
|
||||
this.path = lithoPath;
|
||||
this.accessibility = accessibility;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +42,11 @@ public final class LithoFilterPatch {
|
|||
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
||||
builder.append("ID: ");
|
||||
builder.append(identifier);
|
||||
if (!accessibility.isEmpty()) {
|
||||
// AccessibilityId and AccessibilityText are pieces of BufferStrings.
|
||||
builder.append(" Accessibility: ");
|
||||
builder.append(accessibility);
|
||||
}
|
||||
builder.append(" Path: ");
|
||||
builder.append(path);
|
||||
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||
|
|
@ -122,7 +130,7 @@ public final class LithoFilterPatch {
|
|||
|
||||
/**
|
||||
* String suffix for components.
|
||||
* Can be any of: ".eml", ".e-b", ".eml-js", "e-js-b"
|
||||
* Can be any of: ".eml", ".eml-fe", ".e-b", ".eml-js", "e-js-b"
|
||||
*/
|
||||
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
|
|
@ -132,7 +140,7 @@ public final class LithoFilterPatch {
|
|||
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 multithreaded 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.
|
||||
* Used for 20.21 and lower.
|
||||
*/
|
||||
|
|
@ -140,7 +148,7 @@ public final class LithoFilterPatch {
|
|||
|
||||
/**
|
||||
* Identifier to protocol buffer mapping. Only used for 20.22+.
|
||||
* Thread local is needed because filtering is multi-threaded and each thread can load
|
||||
* Thread local is needed because filtering is multithreaded and each thread can load
|
||||
* a different component with the same identifier.
|
||||
*/
|
||||
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
|
||||
|
|
@ -155,6 +163,7 @@ public final class LithoFilterPatch {
|
|||
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||
|
||||
static {
|
||||
|
||||
for (Filter filter : filters) {
|
||||
filterUsingCallbacks(identifierSearchTree, filter,
|
||||
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
||||
|
|
@ -186,16 +195,13 @@ public final class LithoFilterPatch {
|
|||
|
||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||
parameters.path, parameters.buffer, group, type, matchedStartIndex);
|
||||
parameters.accessibility, parameters.path, parameters.buffer,
|
||||
group, type, matchedStartIndex);
|
||||
|
||||
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||
if (type == Filter.FilterContentType.IDENTIFIER) {
|
||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||
+ " identifier: " + parameters.identifier);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||
+ " path: " + parameters.path);
|
||||
}
|
||||
Logger.printDebug(() -> type == Filter.FilterContentType.IDENTIFIER
|
||||
? filterSimpleName + " filtered identifier: " + parameters.identifier
|
||||
: filterSimpleName + " filtered path: " + parameters.path);
|
||||
}
|
||||
|
||||
return isFiltered;
|
||||
|
|
@ -212,7 +218,7 @@ public final class LithoFilterPatch {
|
|||
|
||||
/**
|
||||
* Helper function that differs from {@link Character#isDigit(char)}
|
||||
* as this only matches ascii and not unicode numbers.
|
||||
* as this only matches ascii and not Unicode numbers.
|
||||
*/
|
||||
private static boolean isAsciiNumber(byte character) {
|
||||
return '0' <= character && character <= '9';
|
||||
|
|
@ -233,12 +239,19 @@ public final class LithoFilterPatch {
|
|||
Logger.printDebug(() -> "New buffer: " + builder);
|
||||
}
|
||||
|
||||
// The identifier always seems to start very close to the buffer start.
|
||||
// Highest identifier start index ever observed is 50, with most around 30 to 40.
|
||||
// The buffer can be very large with up to 200kb has been observed,
|
||||
// so the search is restricted to only the start.
|
||||
final int maxBufferStartIndex = 500; // 10x expected upper bound.
|
||||
|
||||
// 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++) {
|
||||
final int lastBufferIndexToCheckFrom = Math.min(maxBufferStartIndex, buffer.length - emlStringLength);
|
||||
for (int i = 0; i < lastBufferIndexToCheckFrom; i++) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < emlStringLength; j++) {
|
||||
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
|
||||
|
|
@ -254,6 +267,9 @@ public final class LithoFilterPatch {
|
|||
|
||||
if (emlIndex < 0) {
|
||||
// Buffer is not used for creating a new litho component.
|
||||
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
||||
Logger.printDebug(() -> "Could not find eml index");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +306,9 @@ public final class LithoFilterPatch {
|
|||
}
|
||||
}
|
||||
if (endIndex < 0) {
|
||||
Logger.printException(() -> "Could not find buffer identifier");
|
||||
if (BaseSettings.DEBUG.get()) {
|
||||
Logger.printException(() -> "Debug: Could not find buffer identifier");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +347,8 @@ public final class LithoFilterPatch {
|
|||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isFiltered(String identifier, StringBuilder pathBuilder) {
|
||||
public static boolean isFiltered(String identifier, @Nullable String accessibilityId,
|
||||
@Nullable String accessibilityText, StringBuilder pathBuilder) {
|
||||
try {
|
||||
if (identifier.isEmpty() || pathBuilder.length() == 0) {
|
||||
return false;
|
||||
|
|
@ -340,7 +359,7 @@ public final class LithoFilterPatch {
|
|||
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
|
||||
// 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);
|
||||
|
||||
|
|
@ -357,7 +376,9 @@ public final class LithoFilterPatch {
|
|||
// 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);
|
||||
if (BaseSettings.DEBUG.get()) {
|
||||
Logger.printException(() -> "Debug: Could not find buffer for identifier: " + identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -372,7 +393,15 @@ public final class LithoFilterPatch {
|
|||
}
|
||||
|
||||
String path = pathBuilder.toString();
|
||||
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, buffer);
|
||||
|
||||
String accessibility = "";
|
||||
if (accessibilityId != null && !accessibilityId.isBlank()) {
|
||||
accessibility = accessibilityId;
|
||||
}
|
||||
if (accessibilityText != null && !accessibilityText.isBlank()) {
|
||||
accessibility = accessibilityId + '|' + accessibilityText;
|
||||
}
|
||||
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, accessibility, buffer);
|
||||
Logger.printDebug(() -> "Searching " + parameter);
|
||||
|
||||
return identifierSearchTree.matches(identifier, parameter)
|
||||
|
|
|
|||
|
|
@ -24,23 +24,23 @@ public class LinkSanitizer {
|
|||
: List.of(parametersToRemove);
|
||||
}
|
||||
|
||||
public String sanitizeUrlString(String url) {
|
||||
public String sanitizeURLString(String url) {
|
||||
try {
|
||||
return sanitizeUri(Uri.parse(url)).toString();
|
||||
return sanitizeURI(Uri.parse(url)).toString();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "sanitizeUrlString failure: " + url, ex);
|
||||
Logger.printException(() -> "sanitizeURLString failure: " + url, ex);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
public Uri sanitizeUri(Uri uri) {
|
||||
public Uri sanitizeURI(Uri uri) {
|
||||
try {
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
||||
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
||||
// Checking !uri.isHierarchical() works for all cases, except if the
|
||||
// video title starts with / and then it's hierarchical but still an invalid URI.
|
||||
Logger.printDebug(() -> "Ignoring uri: " + uri);
|
||||
Logger.printDebug(() -> "Ignoring URI: " + uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
|
@ -56,12 +56,12 @@ public class LinkSanitizer {
|
|||
}
|
||||
}
|
||||
|
||||
Uri sanitizedUrl = builder.build();
|
||||
Logger.printInfo(() -> "Sanitized url: " + uri + " to: " + sanitizedUrl);
|
||||
Uri sanitizedURL = builder.build();
|
||||
Logger.printInfo(() -> "Sanitized URL: " + uri + " to: " + sanitizedURL);
|
||||
|
||||
return sanitizedUrl;
|
||||
return sanitizedURL;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "sanitizeUri failure: " + uri, ex);
|
||||
Logger.printException(() -> "sanitizeURI failure: " + uri, ex);
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ public class Requester {
|
|||
public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException {
|
||||
String url = apiUrl + route.getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
// Request data is in the URL parameters and no body is sent.
|
||||
// The calling code must set a length if using a request body.
|
||||
// This request sends data via URL query parameters. No request body is included.
|
||||
// If a request body is added, the caller must set the appropriate Content-Length header.
|
||||
connection.setFixedLengthStreamingMode(0);
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
String agentString = System.getProperty("http.agent")
|
||||
|
|
|
|||
|
|
@ -96,15 +96,15 @@ public abstract class BaseActivityHook extends Activity {
|
|||
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
|
||||
// Replace dummy placeholder toolbar.
|
||||
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
||||
ViewGroup toolBarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
|
||||
ViewGroup toolbarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolbarParent, "revanced_toolbar");
|
||||
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||
toolBarParent.removeView(dummyToolbar);
|
||||
toolbarParent.removeView(dummyToolbar);
|
||||
|
||||
// Sets appropriate system navigation bar color for the activity.
|
||||
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
|
||||
|
||||
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||
Toolbar toolbar = new Toolbar(toolbarParent.getContext());
|
||||
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
||||
toolbar.setNavigationIcon(getNavigationIcon());
|
||||
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
||||
|
|
@ -121,7 +121,7 @@ public abstract class BaseActivityHook extends Activity {
|
|||
|
||||
onPostToolbarSetup(activity, toolbar, fragment);
|
||||
|
||||
toolBarParent.addView(toolbar, 0);
|
||||
toolbarParent.addView(toolbar, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import static app.revanced.extension.shared.patches.CustomBrandingPatch.Branding
|
|||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.patches.CustomBrandingPatch;
|
||||
|
||||
/**
|
||||
* Settings shared across multiple apps.
|
||||
|
|
@ -48,13 +49,13 @@ public class BaseSettings {
|
|||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||
public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||
|
||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||
|
||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
|
||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
|
||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", CustomBrandingPatch.getDefaultIconStyle(), true);
|
||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", CustomBrandingPatch.getDefaultAppNameIndex(), true);
|
||||
|
||||
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||
* This method is only to be used by the Settings preference code.
|
||||
*
|
||||
* This intentionally is a static method to deter
|
||||
* accidental usage when {@link #save(Boolean)} was intnded.
|
||||
* accidental usage when {@link #save(Boolean)} was intended.
|
||||
*/
|
||||
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||
setting.value = Objects.requireNonNull(newValue);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param enumName Enum name. Casing does not matter.
|
||||
* @param enumName Enum name. Casing does not matter.
|
||||
* @return Enum of this type with the same declared name.
|
||||
* @throws IllegalArgumentException if the name is not a valid enum of this type.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -274,60 +274,6 @@ public abstract class Setting<T> {
|
|||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
||||
*/
|
||||
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
|
||||
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
||||
|
||||
if (!oldSetting.isSetToDefault()) {
|
||||
Logger.printInfo(() -> "Migrating old setting value: " + oldSetting + " into replacement setting: " + newSetting);
|
||||
newSetting.save(oldSetting.value);
|
||||
oldSetting.resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate an old Setting value previously stored in a different SharedPreference.
|
||||
* <p>
|
||||
* This method will be deleted in the future.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "NewApi"})
|
||||
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
|
||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
Object newValue = setting.get();
|
||||
final Object migratedValue;
|
||||
if (setting instanceof BooleanSetting) {
|
||||
migratedValue = oldPrefs.getBoolean(settingKey, (Boolean) newValue);
|
||||
} else if (setting instanceof IntegerSetting) {
|
||||
migratedValue = oldPrefs.getIntegerString(settingKey, (Integer) newValue);
|
||||
} else if (setting instanceof LongSetting) {
|
||||
migratedValue = oldPrefs.getLongString(settingKey, (Long) newValue);
|
||||
} else if (setting instanceof FloatSetting) {
|
||||
migratedValue = oldPrefs.getFloatString(settingKey, (Float) newValue);
|
||||
} else if (setting instanceof StringSetting) {
|
||||
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
||||
} else {
|
||||
Logger.printException(() -> "Unknown setting: " + setting);
|
||||
// Remove otherwise it'll show a toast on every launch.
|
||||
oldPrefs.preferences.edit().remove(settingKey).apply();
|
||||
return;
|
||||
}
|
||||
|
||||
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
||||
if (migratedValue.equals(newValue)) {
|
||||
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
||||
return; // Old value is already equal to the new setting value.
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
||||
//noinspection unchecked
|
||||
setting.save(migratedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets, but does _not_ persistently save the value.
|
||||
* This method is only to be used by the Settings preference code.
|
||||
|
|
@ -419,7 +365,7 @@ public abstract class Setting<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return if the currently set value is the same as {@link #defaultValue}
|
||||
* @return if the currently set value is the same as {@link #defaultValue}.
|
||||
*/
|
||||
public boolean isSetToDefault() {
|
||||
return value.equals(defaultValue);
|
||||
|
|
@ -450,7 +396,7 @@ public abstract class Setting<T> {
|
|||
|
||||
/**
|
||||
* @param importExportKey The JSON key. The JSONObject parameter will contain data for this key.
|
||||
* @return the value stored using the import/export key. Do not set any values in this method.
|
||||
* @return the value stored using the import/export key. Do not set any values in this method.
|
||||
*/
|
||||
protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException;
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||
boolean syncSettingValue,
|
||||
boolean applySettingToPreference) {
|
||||
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||
// Alternatively this could iterate through all Settings and check for any matching Preferences,
|
||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||
// the Preferences.
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class FeatureFlagsManagerPreference extends Preference {
|
|||
*/
|
||||
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
||||
45386834L, // 'You' tab settings icon.
|
||||
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
|
||||
45532100L // Cairo flag. Turning this off with all other flags causes the settings menu to be a mix of old/new.
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -131,9 +131,10 @@ public class FeatureFlagsManagerPreference extends Preference {
|
|||
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
||||
|
||||
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
||||
// String does not need to be localized because it's basically impossible
|
||||
// to reach the settings menu without encountering at least 1 flag.
|
||||
Utils.showToastShort("No feature flags logged yet");
|
||||
// It's impossible to reach the settings menu without reaching at least one flag.
|
||||
// So if theres no flags, then that means the user has just enabled debugging
|
||||
// but has not restarted the app yet.
|
||||
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_no_flags"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,18 +36,18 @@ public class SharedPrefCategory {
|
|||
}
|
||||
|
||||
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
||||
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
||||
preferences.edit().putString(key, (value == null ? null : value.toString())).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any preference data type that has the specified key.
|
||||
*/
|
||||
public void removeKey(@NonNull String key) {
|
||||
preferences.edit().remove(Objects.requireNonNull(key)).apply();
|
||||
preferences.edit().remove(Objects.requireNonNull(key)).commit();
|
||||
}
|
||||
|
||||
public void saveBoolean(@NonNull String key, boolean value) {
|
||||
preferences.edit().putBoolean(key, value).apply();
|
||||
preferences.edit().putBoolean(key, value).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,36 +9,36 @@ import android.util.AttributeSet;
|
|||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
/**
|
||||
* Simple preference that opens a url when clicked.
|
||||
* Simple preference that opens a URL when clicked.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class UrlLinkPreference extends Preference {
|
||||
public class URLLinkPreference extends Preference {
|
||||
|
||||
protected String externalUrl;
|
||||
protected String externalURL;
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
if (externalUrl == null) {
|
||||
if (externalURL == null) {
|
||||
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
||||
return false;
|
||||
}
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(externalUrl));
|
||||
i.setData(Uri.parse(externalURL));
|
||||
pref.getContext().startActivity(i);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public UrlLinkPreference(Context context, AttributeSet attrs) {
|
||||
public URLLinkPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public UrlLinkPreference(Context context) {
|
||||
public URLLinkPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import app.revanced.extension.shared.ResourceType;
|
|||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||
|
||||
/**
|
||||
* Abstract base class for search result items, defining common fields and behavior.
|
||||
|
|
@ -167,7 +167,7 @@ public abstract class BaseSearchResultItem {
|
|||
if (pref instanceof SwitchPreference) return ViewType.SWITCH;
|
||||
if (pref instanceof ListPreference) return ViewType.LIST;
|
||||
if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER;
|
||||
if (pref instanceof UrlLinkPreference) return ViewType.URL_LINK;
|
||||
if (pref instanceof URLLinkPreference) return ViewType.URL_LINK;
|
||||
if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS;
|
||||
return ViewType.REGULAR;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import app.revanced.extension.shared.ResourceType;
|
|||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
||||
import app.revanced.extension.shared.ui.ColorDot;
|
||||
|
||||
/**
|
||||
|
|
@ -436,7 +436,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
|||
}
|
||||
|
||||
/**
|
||||
* Normalizes string for comparison (removes extra characters, spaces etc).
|
||||
* Normalizes string for comparison (removes extra characters, spaces etc.).
|
||||
*/
|
||||
protected String normalizeString(String input) {
|
||||
if (TextUtils.isEmpty(input)) return "";
|
||||
|
|
@ -609,8 +609,8 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
|||
boolean hasNavigationCapability(Preference preference) {
|
||||
// PreferenceScreen always allows navigation.
|
||||
if (preference instanceof PreferenceScreen) return true;
|
||||
// UrlLinkPreference does not navigate to a new screen, it opens an external URL.
|
||||
if (preference instanceof UrlLinkPreference) return false;
|
||||
// URLLinkPreference does not navigate to a new screen, it opens an external URL.
|
||||
if (preference instanceof URLLinkPreference) return false;
|
||||
// Other group types that might have their own screens.
|
||||
if (preference instanceof PreferenceGroup) {
|
||||
// Check if it has its own fragment or intent.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
|||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
||||
/**
|
||||
* Video streaming data. Fetching is tied to the behavior YT uses,
|
||||
* Video streaming data. Fetching is tied to the behavior YT uses,
|
||||
* where this class fetches the streams only when YT fetches.
|
||||
* <p>
|
||||
* Effectively the cache expiration of these fetches is the same as the stock app,
|
||||
|
|
@ -86,7 +86,7 @@ public class StreamingDataRequest {
|
|||
* 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
|
||||
* 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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue