diff --git a/integrations/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java b/integrations/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java new file mode 100644 index 000000000..c0d013e54 --- /dev/null +++ b/integrations/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java @@ -0,0 +1,18 @@ +package app.revanced.integrations.patches; + +import android.app.AlertDialog; +import app.revanced.integrations.settings.SettingsEnum; + +public class RemoveViewerDiscretionDialogPatch { + public static void confirmDialog(AlertDialog dialog) { + if (!SettingsEnum.REMOVE_VIEWER_DISCRETION_DIALOG.getBoolean()) { + // Since the patch replaces the AlertDialog#show() method, we need to call the original method here. + dialog.show(); + return; + } + + final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + button.setSoundEffectsEnabled(false); + button.performClick(); + } +} diff --git a/integrations/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/integrations/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index 9a14366ea..9c8caa455 100644 --- a/integrations/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/integrations/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -570,7 +570,7 @@ public class ReturnYouTubeDislikePatch { return; } - final boolean videoIdIsShort = VideoInformation.lastVideoIdIsShort(); + final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort(); // Shorts shelf in home and subscription feed causes player response hook to be called, // and the 'is opening/playing' parameter will be false. // This hook will be called again when the Short is actually opened. diff --git a/integrations/java/app/revanced/integrations/patches/VideoInformation.java b/integrations/java/app/revanced/integrations/patches/VideoInformation.java index a6ba5649c..15c97fb5e 100644 --- a/integrations/java/app/revanced/integrations/patches/VideoInformation.java +++ b/integrations/java/app/revanced/integrations/patches/VideoInformation.java @@ -32,6 +32,7 @@ public final class VideoInformation { @NonNull private static volatile String playerResponseVideoId = ""; + private static volatile boolean playerResponseVideoIdIsShort; private static volatile boolean videoIdIsShort; /** @@ -82,6 +83,7 @@ public final class VideoInformation { */ public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) { final boolean isShort = playerParametersAreShort(signature); + playerResponseVideoIdIsShort = isShort; if (!isShort || isShortAndOpeningOrPlaying) { if (videoIdIsShort != isShort) { videoIdIsShort = isShort; @@ -155,20 +157,29 @@ public final class VideoInformation { * Caution: If called from a videoTimeHook() callback, * this will cause a recursive call into the same videoTimeHook() callback. * - * @param millisecond The millisecond to seek the video to. + * @param seekTime The seekTime to seek the video to. * @return true if the seek was successful. */ - public static boolean seekTo(final long millisecond) { - final long videoLength = getVideoLength(); - - // Prevent issues such as play/ pause button or autoplay not working. - final long seekToMilliseconds = Math.min(millisecond, VideoInformation.getVideoLength() - 250); - + public static boolean seekTo(final long seekTime) { ReVancedUtils.verifyOnMainThread(); try { - LogHelper.printDebug(() -> "Seeking to " + seekToMilliseconds); + final long videoTime = getVideoTime(); + final long videoLength = getVideoLength(); + + // Prevent issues such as play/ pause button or autoplay not working. + final long adjustedSeekTime = Math.min(seekTime, videoLength - 250); + if (videoTime <= seekTime && videoTime >= adjustedSeekTime) { + // Both the current video time and the seekTo are in the last 250ms of the video. + // Ignore this seek call, otherwise if a video ends with multiple closely timed segments + // then seeking here can create an infinite loop of skip attempts. + LogHelper.printDebug(() -> "Ignoring seekTo call as video playback is almost finished. " + + " videoTime: " + videoTime + " videoLength: " + videoLength + " seekTo: " + seekTime); + return false; + } + + LogHelper.printDebug(() -> "Seeking to " + adjustedSeekTime); //noinspection DataFlowIssue - return (Boolean) seekMethod.invoke(playerControllerRef.get(), seekToMilliseconds); + return (Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime); } catch (Exception ex) { LogHelper.printException(() -> "Failed to seek", ex); return false; @@ -206,11 +217,17 @@ public final class VideoInformation { return playerResponseVideoId; } + /** + * @return If the last player response video id was a Short. + * Includes Shorts shelf items appearing in the feed that are not opened. + * @see #lastVideoIdIsShort() + */ + public static boolean lastPlayerResponseIsShort() { + return playerResponseVideoIdIsShort; + } + /** * @return If the last player response video id _that was opened_ was a Short. - *

- * Note: This value returned may not match the status of {@link #getPlayerResponseVideoId()} - * since that includes player responses for videos not opened. */ public static boolean lastVideoIdIsShort() { return videoIdIsShort; diff --git a/integrations/java/app/revanced/integrations/patches/components/AdsFilter.java b/integrations/java/app/revanced/integrations/patches/components/AdsFilter.java index 5868596dc..3b0cb0ec5 100644 --- a/integrations/java/app/revanced/integrations/patches/components/AdsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/AdsFilter.java @@ -1,16 +1,25 @@ package app.revanced.integrations.patches.components; - +import android.app.Instrumentation; +import android.view.KeyEvent; import android.view.View; import androidx.annotation.Nullable; import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; import app.revanced.integrations.utils.StringTrieSearch; - +@SuppressWarnings("unused") public final class AdsFilter extends Filter { + // region Fullscreen ad + private static long lastTimeClosedFullscreenAd = 0; + private static final Instrumentation instrumentation = new Instrumentation(); + private final StringFilterGroup fullscreenAd; + + // endregion + private final StringTrieSearch exceptions = new StringTrieSearch(); private final StringFilterGroup shoppingLinks; @@ -23,6 +32,22 @@ public final class AdsFilter extends Filter { "library_recent_shelf" ); + // Identifiers. + + + final var carouselAd = new StringFilterGroup( + SettingsEnum.HIDE_GENERAL_ADS, + "carousel_ad" + ); + addIdentifierCallbacks(carouselAd); + + // Paths. + + fullscreenAd = new StringFilterGroup( + SettingsEnum.HIDE_FULLSCREEN_ADS, + "_interstitial" + ); + final var buttonedAd = new StringFilterGroup( SettingsEnum.HIDE_BUTTONED_ADS, "_buttoned_layout", @@ -30,7 +55,8 @@ public final class AdsFilter extends Filter { "_ad_with", "text_image_button_group_layout", "video_display_button_group_layout", - "landscape_image_wide_button_layout" + "landscape_image_wide_button_layout", + "video_display_carousel_button_group_layout" ); final var generalAds = new StringFilterGroup( @@ -61,11 +87,6 @@ public final class AdsFilter extends Filter { "offer_module_root" ); - final var carouselAd = new StringFilterGroup( - SettingsEnum.HIDE_GENERAL_ADS, - "carousel_ad" - ); - final var viewProducts = new StringFilterGroup( SettingsEnum.HIDE_PRODUCTS_BANNER, "product_item", @@ -92,30 +113,34 @@ public final class AdsFilter extends Filter { "cta_shelf_card" ); - this.pathFilterGroupList.addAll( + addPathCallbacks( generalAds, buttonedAd, merchandise, viewProducts, selfSponsor, + fullscreenAd, webLinkPanel, shoppingLinks, movieAds ); - this.identifierFilterGroupList.addAll(carouselAd); } @Override public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (exceptions.matches(path)) - return false; - - // Check for the index because of likelihood of false positives. - if (matchedGroup == shoppingLinks && matchedIndex != 0) return false; - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + if (matchedGroup == fullscreenAd && path.contains("|ImageType|")) { + closeFullscreenAd(); + } + + // Check for the index because of likelihood of false positives. + if (matchedGroup == shoppingLinks && contentIndex != 0) + return false; + + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } /** @@ -126,4 +151,21 @@ public final class AdsFilter extends Filter { public static void hideAdAttributionView(View view) { ReVancedUtils.hideViewBy1dpUnderCondition(SettingsEnum.HIDE_GENERAL_ADS, view); } + + /** + * Close the fullscreen ad. + *

+ * The strategy is to send a back button event to the app to close the fullscreen ad using the back button event. + */ + private static void closeFullscreenAd() { + final var currentTime = System.currentTimeMillis(); + + // Prevent spamming the back button. + if (currentTime - lastTimeClosedFullscreenAd < 10000) return; + lastTimeClosedFullscreenAd = currentTime; + + LogHelper.printDebug(() -> "Closing fullscreen ad"); + + ReVancedUtils.runOnMainThreadDelayed(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK), 1000); + } } diff --git a/integrations/java/app/revanced/integrations/patches/components/ButtonsFilter.java b/integrations/java/app/revanced/integrations/patches/components/ButtonsFilter.java index 19cddf3f6..123c58036 100644 --- a/integrations/java/app/revanced/integrations/patches/components/ButtonsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/ButtonsFilter.java @@ -7,6 +7,7 @@ import androidx.annotation.RequiresApi; import app.revanced.integrations.settings.SettingsEnum; +@SuppressWarnings("unused") @RequiresApi(api = Build.VERSION_CODES.N) final class ButtonsFilter extends Filter { private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml"; @@ -20,14 +21,14 @@ final class ButtonsFilter extends Filter { null, VIDEO_ACTION_BAR_PATH ); - identifierFilterGroupList.addAll(actionBarGroup); + addIdentifierCallbacks(actionBarGroup); bufferFilterPathGroup = new StringFilterGroup( null, "|CellType|CollectionType|CellType|ContainerType|button.eml|" ); - pathFilterGroupList.addAll( + addPathCallbacks( new StringFilterGroup( SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON, "|segmented_like_dislike_button" @@ -48,33 +49,33 @@ final class ButtonsFilter extends Filter { ); bufferButtonsGroupList.addAll( - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_LIVE_CHAT_BUTTON, "yt_outline_message_bubble_overlap" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_REPORT_BUTTON, "yt_outline_flag" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_SHARE_BUTTON, "yt_outline_share" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_REMIX_BUTTON, "yt_outline_youtube_shorts_plus" ), // Check for clip button both here and using a path filter, // as there's a chance the path is a generic action button and won't contain 'clip_button' - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_CLIP_BUTTON, "yt_outline_scissors" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_SHOP_BUTTON, "yt_outline_bag" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_THANKS_BUTTON, "yt_outline_dollar_sign_heart" ) @@ -82,7 +83,7 @@ final class ButtonsFilter extends Filter { } private boolean isEveryFilterGroupEnabled() { - for (var group : pathFilterGroupList) + for (var group : pathCallbacks) if (!group.isEnabled()) return false; for (var group : bufferButtonsGroupList) @@ -93,7 +94,7 @@ final class ButtonsFilter extends Filter { @Override public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { // If the current matched group is the action bar group, // in case every filter group is enabled, hide the action bar. if (matchedGroup == actionBarGroup) { @@ -109,6 +110,6 @@ final class ButtonsFilter extends Filter { if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false; } - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } } diff --git a/integrations/java/app/revanced/integrations/patches/components/CommentsFilter.java b/integrations/java/app/revanced/integrations/patches/components/CommentsFilter.java index 089fb9480..6ae67f7c4 100644 --- a/integrations/java/app/revanced/integrations/patches/components/CommentsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/CommentsFilter.java @@ -2,6 +2,7 @@ package app.revanced.integrations.patches.components; import app.revanced.integrations.settings.SettingsEnum; +@SuppressWarnings("unused") final class CommentsFilter extends Filter { public CommentsFilter() { @@ -18,7 +19,7 @@ final class CommentsFilter extends Filter { "comments_entry_point_simplebox" ); - this.pathFilterGroupList.addAll( + addPathCallbacks( comments, previewComment ); diff --git a/integrations/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java b/integrations/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java index ccdd32104..e786144ad 100644 --- a/integrations/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java @@ -4,6 +4,7 @@ import androidx.annotation.Nullable; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.StringTrieSearch; +@SuppressWarnings("unused") final class DescriptionComponentsFilter extends Filter { private final StringTrieSearch exceptions = new StringTrieSearch(); @@ -48,7 +49,7 @@ final class DescriptionComponentsFilter extends Filter { "transcript_section" ); - pathFilterGroupList.addAll( + addPathCallbacks( chapterSection, infoCardsSection, gameSection, @@ -61,9 +62,9 @@ final class DescriptionComponentsFilter extends Filter { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (exceptions.matches(path)) return false; - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex); } } \ No newline at end of file diff --git a/integrations/java/app/revanced/integrations/patches/components/DummyFilter.java b/integrations/java/app/revanced/integrations/patches/components/DummyFilter.java deleted file mode 100644 index 3c36b51a1..000000000 --- a/integrations/java/app/revanced/integrations/patches/components/DummyFilter.java +++ /dev/null @@ -1,3 +0,0 @@ -package app.revanced.integrations.patches.components; - -final class DummyFilter extends Filter { } \ No newline at end of file diff --git a/integrations/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java b/integrations/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java index fc1f9a1e9..b1637b835 100644 --- a/integrations/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java +++ b/integrations/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java @@ -2,10 +2,11 @@ package app.revanced.integrations.patches.components; import app.revanced.integrations.settings.SettingsEnum; +@SuppressWarnings("unused") public final class HideInfoCardsFilterPatch extends Filter { public HideInfoCardsFilterPatch() { - identifierFilterGroupList.addAll( + addIdentifierCallbacks( new StringFilterGroup( SettingsEnum.HIDE_INFO_CARDS, "info_card_teaser_overlay.eml" diff --git a/integrations/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java b/integrations/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java index a66bb133c..afc9b8457 100644 --- a/integrations/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java @@ -1,24 +1,26 @@ package app.revanced.integrations.patches.components; - import android.os.Build; + import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; + import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.StringTrieSearch; +@SuppressWarnings("unused") @RequiresApi(api = Build.VERSION_CODES.N) public final class LayoutComponentsFilter extends Filter { private final StringTrieSearch exceptions = new StringTrieSearch(); private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch(); - private static final ByteArrayAsStringFilterGroup mixPlaylistsExceptions2 = new ByteArrayAsStringFilterGroup( + private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup( null, "cell_description_body" ); private final CustomFilterGroup custom; - private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup( + private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup( SettingsEnum.HIDE_MIX_PLAYLISTS, "&list=" ); @@ -26,6 +28,8 @@ public final class LayoutComponentsFilter extends Filter { private final StringFilterGroup inFeedSurvey; private final StringFilterGroup notifyMe; private final StringFilterGroup expandableMetadata; + private final ByteArrayFilterGroup searchResultRecommendations; + private final StringFilterGroup searchResultVideo; static { mixPlaylistsExceptions.addPatterns( @@ -39,11 +43,31 @@ public final class LayoutComponentsFilter extends Filter { exceptions.addPatterns( "home_video_with_context", "related_video_with_context", + "search_video_with_context", "comment_thread", // Whitelist comments "|comment.", // Whitelist comment replies "library_recent_shelf" ); + // Identifiers. + + final var graySeparator = new StringFilterGroup( + SettingsEnum.HIDE_GRAY_SEPARATOR, + "cell_divider" // layout residue (gray line above the buttoned ad), + ); + + final var chipsShelf = new StringFilterGroup( + SettingsEnum.HIDE_CHIPS_SHELF, + "chips_shelf" + ); + + addIdentifierCallbacks( + graySeparator, + chipsShelf + ); + + // Paths. + custom = new CustomFilterGroup( SettingsEnum.CUSTOM_FILTER, SettingsEnum.CUSTOM_FILTER_STRINGS @@ -64,7 +88,6 @@ public final class LayoutComponentsFilter extends Filter { "sponsorships_comments_upsell" ); - final var channelMemberShelf = new StringFilterGroup( SettingsEnum.HIDE_CHANNEL_MEMBER_SHELF, "member_recognition_shelf" @@ -107,6 +130,11 @@ public final class LayoutComponentsFilter extends Filter { "channel_guidelines_entry_banner" ); + final var emergencyBox = new StringFilterGroup( + SettingsEnum.HIDE_EMERGENCY_BOX, + "emergency_onebox" + ); + // The player audio track button does the exact same function as the audio track flyout menu option. // But if the copy url button is shown, these button clashes and the the audio button does not work. // Previously this was a setting to show/hide the player button. @@ -155,10 +183,6 @@ public final class LayoutComponentsFilter extends Filter { "image_shelf" ); - final var graySeparator = new StringFilterGroup( - SettingsEnum.HIDE_GRAY_SEPARATOR, - "cell_divider" // layout residue (gray line above the buttoned ad), - ); final var timedReactions = new StringFilterGroup( SettingsEnum.HIDE_TIMED_REACTIONS, @@ -181,11 +205,6 @@ public final class LayoutComponentsFilter extends Filter { "compact_sponsor_button" ); - final var chipsShelf = new StringFilterGroup( - SettingsEnum.HIDE_CHIPS_SHELF, - "chips_shelf" - ); - final var channelWatermark = new StringFilterGroup( SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK, "featured_channel_watermark_overlay" @@ -196,23 +215,36 @@ public final class LayoutComponentsFilter extends Filter { "mixed_content_shelf" ); - this.pathFilterGroupList.addAll( + searchResultVideo = new StringFilterGroup( + SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS, + "search_video_with_context.eml" + ); + + searchResultRecommendations = new ByteArrayFilterGroup( + SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS, + "endorsement_header_footer" + ); + + addPathCallbacks( + custom, + expandableMetadata, + inFeedSurvey, + notifyMe, channelBar, communityPosts, paidContent, + searchResultVideo, latestPosts, channelWatermark, communityGuidelines, quickActions, - expandableMetadata, relatedVideos, compactBanner, - inFeedSurvey, joinMembership, medicalPanel, - notifyMe, videoQualityMenuFooter, infoPanel, + emergencyBox, subscribersCommunityGuidelines, channelGuidelines, audioTrackButton, @@ -220,32 +252,31 @@ public final class LayoutComponentsFilter extends Filter { timedReactions, imageShelf, channelMemberShelf, - forYouShelf, - custom - ); - - this.identifierFilterGroupList.addAll( - graySeparator, - chipsShelf + forYouShelf ); } @Override public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + if (matchedGroup == searchResultVideo) { + if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) { + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); + } + } // The groups are excluded from the filter due to the exceptions list below. // Filter them separately here. - if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); if (matchedGroup != custom && exceptions.matches(path)) return false; // Exceptions are not filtered. // TODO: This also hides the feed Shorts shelf header - if (matchedGroup == searchResultShelfHeader && matchedIndex != 0) return false; + if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false; - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } /** diff --git a/integrations/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/integrations/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index 96b0976e5..26b31e64c 100644 --- a/integrations/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/integrations/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -195,6 +195,14 @@ class ByteArrayFilterGroup extends FilterGroup { super(setting, filters); } + /** + * Converts the Strings into byte arrays. Used to search for text in binary data. + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public ByteArrayFilterGroup(SettingsEnum setting, String... filters) { + super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new)); + } + private synchronized void buildFailurePatterns() { if (failurePatterns != null) return; // Thread race and another thread already initialized the search. LogHelper.printDebug(() -> "Building failure array for: " + this); @@ -211,12 +219,14 @@ class ByteArrayFilterGroup extends FilterGroup { int matchedLength = 0; int matchedIndex = -1; if (isEnabled()) { - if (failurePatterns == null) { + int[][] failures = failurePatterns; + if (failures == null) { buildFailurePatterns(); // Lazy load. + failures = failurePatterns; } for (int i = 0, length = filters.length; i < length; i++) { byte[] filter = filters[i]; - matchedIndex = indexOf(bytes, filter, failurePatterns[i]); + matchedIndex = indexOf(bytes, filter, failures[i]); if (matchedIndex >= 0) { matchedLength = filter.length; break; @@ -228,34 +238,16 @@ class ByteArrayFilterGroup extends FilterGroup { } -final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup { - - @RequiresApi(api = Build.VERSION_CODES.N) - public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) { - super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new)); - } -} - abstract class FilterGroupList> implements Iterable { private final List filterGroups = new ArrayList<>(); - /** - * Search graph. Created only if needed. - */ - private volatile TrieSearch search; + private final TrieSearch search = createSearchGraph(); @SafeVarargs protected final void addAll(final T... groups) { filterGroups.addAll(Arrays.asList(groups)); - search = null; // Rebuild, if already created. - } - protected final synchronized void buildSearch() { - // Since litho filtering is multi-threaded, this method can be concurrently called by multiple threads. - if (search != null) return; // Thread race and another thread already initialized the search. - LogHelper.printDebug(() -> "Creating prefix search tree for: " + this); - TrieSearch search = createSearchGraph(); - for (T group : filterGroups) { + for (T group : groups) { if (!group.includeInSearch()) { continue; } @@ -270,7 +262,6 @@ abstract class FilterGroupList> implements Iterable< }); } } - this.search = search; // Must set after it's completely initialized. } @NonNull @@ -293,9 +284,6 @@ abstract class FilterGroupList> implements Iterable< } protected FilterGroup.FilterGroupResult check(V stack) { - if (search == null) { - buildSearch(); // Lazy load. - } FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(); search.matches(stack, result); return result; @@ -322,42 +310,90 @@ final class ByteArrayFilterGroupList extends FilterGroupList identifierCallbacks = new ArrayList<>(); + /** + * Path callbacks. Do not add to this instance, + * and instead use {@link #addPathCallbacks(StringFilterGroup...)}. + */ + protected final List pathCallbacks = new ArrayList<>(); + + /** + * Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} + * if any of the groups are found. + */ + protected final void addIdentifierCallbacks(StringFilterGroup... groups) { + identifierCallbacks.addAll(Arrays.asList(groups)); + } + + /** + * Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} + * if any of the groups are found. + */ + protected final void addPathCallbacks(StringFilterGroup... groups) { + pathCallbacks.addAll(Arrays.asList(groups)); + } /** * Called after an enabled filter has been matched. - * Default implementation is to always filter the matched item. + * Default implementation is to always filter the matched component and log the action. * Subclasses can perform additional or different checks if needed. *

+ * If the content is to be filtered, subclasses should always + * call this method (and never return a plain 'true'). + * That way the logs will always show when a component was filtered and which filter hide it. + *

* Method is called off the main thread. * - * @param matchedList The list the group filter belongs to. * @param matchedGroup The actual filter that matched. - * @param matchedIndex Matched index of string/array. - * @return True if the litho item should be filtered out. + * @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. */ - @SuppressWarnings("rawtypes") boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (SettingsEnum.DEBUG.getBoolean()) { - if (matchedList == identifierFilterGroupList) { - LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier); + String filterSimpleName = getClass().getSimpleName(); + if (contentType == FilterContentType.IDENTIFIER) { + LogHelper.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier); } else { - LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path); + LogHelper.printDebug(() -> filterSimpleName + " Filtered path: " + path); } } return true; } } +/** + * Placeholder for actual filters. + */ +final class DummyFilter extends Filter { } + @RequiresApi(api = Build.VERSION_CODES.N) @SuppressWarnings("unused") public final class LithoFilterPatch { @@ -437,8 +473,10 @@ public final class LithoFilterPatch { static { for (Filter filter : filters) { - filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList); - filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList); + filterUsingCallbacks(identifierSearchTree, filter, + filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER); + filterUsingCallbacks(pathSearchTree, filter, + filter.pathCallbacks, Filter.FilterContentType.PATH); } LogHelper.printDebug(() -> "Using: " @@ -448,18 +486,19 @@ public final class LithoFilterPatch { + " (" + pathSearchTree.getEstimatedMemorySize() + " KB)"); } - private static void filterGroupLists(TrieSearch pathSearchTree, - Filter filter, FilterGroupList> list) { - for (FilterGroup group : list) { + private static void filterUsingCallbacks(StringTrieSearch pathSearchTree, + Filter filter, List groups, + Filter.FilterContentType type) { + for (StringFilterGroup group : groups) { if (!group.includeInSearch()) { continue; } - for (T pattern : group.filters) { + for (String pattern : group.filters) { pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> { if (!group.isEnabled()) return false; LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer, - list, group, matchedStartIndex); + group, type, matchedStartIndex); } ); } diff --git a/integrations/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java b/integrations/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java index 0f4b9bcfd..f609c43e2 100644 --- a/integrations/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java +++ b/integrations/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java @@ -12,7 +12,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter { public static volatile boolean isPlaybackSpeedMenuVisible; public PlaybackSpeedMenuFilterPatch() { - pathFilterGroupList.addAll(new StringFilterGroup( + addPathCallbacks(new StringFilterGroup( null, "playback_speed_sheet_content.eml-js" )); @@ -20,7 +20,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { isPlaybackSpeedMenuVisible = true; return false; diff --git a/integrations/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java b/integrations/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java index 4e700028b..4f994dd9f 100644 --- a/integrations/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -8,65 +8,64 @@ import androidx.annotation.RequiresApi; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; +@SuppressWarnings("unused") public class PlayerFlyoutMenuItemsFilter extends Filter { - // Search the buffer only if the flyout menu path is found. - // Handle the searching in this class instead of adding to the global filter group (which searches all the time) private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroup exception; @RequiresApi(api = Build.VERSION_CODES.N) public PlayerFlyoutMenuItemsFilter() { - exception = new ByteArrayAsStringFilterGroup( + exception = new ByteArrayFilterGroup( // Whitelist Quality menu item when "Hide Additional settings menu" is enabled SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU, "quality_sheet" ); // Using pathFilterGroupList due to new flyout panel(A/B) - pathFilterGroupList.addAll( + addPathCallbacks( new StringFilterGroup(null, "overflow_menu_item.eml|") ); flyoutFilterGroupList.addAll( - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_CAPTIONS_MENU, "closed_caption" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU, "yt_outline_gear" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_LOOP_VIDEO_MENU, "yt_outline_arrow_repeat_1_" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_AMBIENT_MODE_MENU, "yt_outline_screen_light" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_REPORT_MENU, "yt_outline_flag" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_HELP_MENU, "yt_outline_question_circle" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_MORE_INFO_MENU, "yt_outline_info_circle" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_SPEED_MENU, "yt_outline_play_arrow_half_circle" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_AUDIO_TRACK_MENU, "yt_outline_person_radar" ), - new ByteArrayAsStringFilterGroup( + new ByteArrayFilterGroup( SettingsEnum.HIDE_WATCH_IN_VR_MENU, "yt_outline_vr" ) @@ -75,15 +74,15 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { // Shorts also use this player flyout panel if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) return false; - // Only 1 group is added to the parent class, so the matched group must be the overflow menu. - if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) { + // Only 1 path callback was added, so the matched group must be the overflow menu. + if (contentIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) { // Super class handles logging. - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } return false; } diff --git a/integrations/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java b/integrations/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java index d6b7d151d..b0a4b4f97 100644 --- a/integrations/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java +++ b/integrations/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java @@ -71,21 +71,21 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter { private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList(); public ReturnYouTubeDislikeFilterPatch() { - pathFilterGroupList.addAll( + addPathCallbacks( new StringFilterGroup(SettingsEnum.RYD_SHORTS, "|shorts_dislike_button.eml|") ); // After the dislikes icon name is some binary data and then the video id for that specific short. videoIdFilterGroup.addAll( // Video was previously disliked before video was opened. - new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_on_shadowed"), + new ByteArrayFilterGroup(null, "ic_right_dislike_on_shadowed"), // Video was not already disliked. - new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_off_shadowed") + new ByteArrayFilterGroup(null, "ic_right_dislike_off_shadowed") ); } @Override public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray); if (result.isFiltered()) { String matchedVideoId = findVideoId(protobufBufferArray); @@ -112,7 +112,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter { } /** - * This could use {@link TrieSearch}, but since the video ids are constantly changing + * This could use {@link TrieSearch}, but since the patterns are constantly changing * the overhead of updating the Trie might negate the search performance gain. */ private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) { diff --git a/integrations/java/app/revanced/integrations/patches/components/ShortsFilter.java b/integrations/java/app/revanced/integrations/patches/components/ShortsFilter.java index 7ab5a5429..5e40f6e8e 100644 --- a/integrations/java/app/revanced/integrations/patches/components/ShortsFilter.java +++ b/integrations/java/app/revanced/integrations/patches/components/ShortsFilter.java @@ -10,7 +10,7 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import static app.revanced.integrations.utils.ReVancedUtils.hideViewBy1dpUnderCondition; import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition; -/** @noinspection unused*/ +@SuppressWarnings("unused") @RequiresApi(api = Build.VERSION_CODES.N) public final class ShortsFilter extends Filter { public static PivotBar pivotBar; // Set by patch. @@ -49,7 +49,7 @@ public final class ShortsFilter extends Filter { "suggested_action" ); - identifierFilterGroupList.addAll(shorts, shelfHeader, thanksButton); + addIdentifierCallbacks(shorts, shelfHeader, thanksButton); // Shorts player components. var joinButton = new StringFilterGroup( @@ -87,22 +87,22 @@ public final class ShortsFilter extends Filter { "ContainerType|shorts_video_action_button" ); - pathFilterGroupList.addAll( + addPathCallbacks( joinButton, subscribeButton, subscribeButtonPaused, channelBar, soundButton, infoPanel, videoActionButton ); - var shortsCommentButton = new ByteArrayAsStringFilterGroup( + var shortsCommentButton = new ByteArrayFilterGroup( SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON, "reel_comment_button" ); - var shortsShareButton = new ByteArrayAsStringFilterGroup( + var shortsShareButton = new ByteArrayFilterGroup( SettingsEnum.HIDE_SHORTS_SHARE_BUTTON, "reel_share_button" ); - var shortsRemixButton = new ByteArrayAsStringFilterGroup( + var shortsRemixButton = new ByteArrayFilterGroup( SettingsEnum.HIDE_SHORTS_REMIX_BUTTON, "reel_remix_button" ); @@ -112,19 +112,19 @@ public final class ShortsFilter extends Filter { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { - if (matchedList == pathFilterGroupList) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + if (contentType == FilterContentType.PATH) { // Always filter if matched. if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar || matchedGroup == subscribeButtonPaused - ) return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + ) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); // Video action buttons (comment, share, remix) have the same path. if (matchedGroup == videoActionButton) { if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered( - identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex + identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex ); return false; } @@ -133,18 +133,18 @@ public final class ShortsFilter extends Filter { // to avoid false positives. if (path.startsWith(REEL_CHANNEL_BAR_PATH)) if (matchedGroup == subscribeButton) return super.isFiltered( - identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex + identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex ); return false; } else if (matchedGroup == shelfHeader) { // Because the header is used in watch history and possibly other places, check for the index, // which is 0 when the shelf header is used for Shorts. - if (matchedIndex != 0) return false; + if (contentIndex != 0) return false; } // Super class handles logging. - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } public static void hideShortsShelf(final View shortsShelfView) { diff --git a/integrations/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java b/integrations/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java index d09f4ddca..2050cd3fa 100644 --- a/integrations/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java +++ b/integrations/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java @@ -9,11 +9,12 @@ import app.revanced.integrations.settings.SettingsEnum; * Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}. */ public final class VideoQualityMenuFilterPatch extends Filter { - // Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread. + // Must be volatile or synchronized, as litho filtering runs off main thread + // and this field is then access from the main thread. public static volatile boolean isVideoQualityMenuVisible; public VideoQualityMenuFilterPatch() { - pathFilterGroupList.addAll(new StringFilterGroup( + addPathCallbacks(new StringFilterGroup( SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU, "quick_quality_sheet_content.eml-js" )); @@ -21,7 +22,7 @@ public final class VideoQualityMenuFilterPatch extends Filter { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { isVideoQualityMenuVisible = true; return false; diff --git a/integrations/java/app/revanced/integrations/settings/SettingsEnum.java b/integrations/java/app/revanced/integrations/settings/SettingsEnum.java index 154c0c4bd..ef628fc62 100644 --- a/integrations/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/integrations/java/app/revanced/integrations/settings/SettingsEnum.java @@ -43,6 +43,7 @@ public enum SettingsEnum { "0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true), // Ads + HIDE_FULLSCREEN_ADS("revanced_hide_fullscreen_ads", BOOLEAN, TRUE), HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE), HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE), HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE), @@ -121,6 +122,7 @@ public enum SettingsEnum { HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE), HIDE_FOR_YOU_SHELF("revanced_hide_for_you_shelf", BOOLEAN, TRUE), HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE), + HIDE_SEARCH_RESULT_RECOMMENDATIONS("revanced_hide_search_result_recommendations", BOOLEAN, TRUE), PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true), PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE), SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"), @@ -207,6 +209,8 @@ public enum SettingsEnum { ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""), ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""), REMOVE_TRACKING_QUERY_PARAMETER("revanced_remove_tracking_query_parameter", BOOLEAN, TRUE), + REMOVE_VIEWER_DISCRETION_DIALOG("revanced_remove_viewer_discretion_dialog", BOOLEAN, FALSE, + "revanced_remove_viewer_discretion_dialog_user_dialog_message"), // Swipe controls SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE), @@ -597,8 +601,6 @@ public enum SettingsEnum { case SB_LAST_VIP_CHECK: case SB_HIDE_EXPORT_WARNING: case SB_SEEN_GUIDELINES: - case SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS: - case SB_LOCAL_TIME_SAVED_MILLISECONDS: return false; } return true; diff --git a/integrations/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java b/integrations/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java index e25ae7a5e..2257f82d1 100644 --- a/integrations/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java +++ b/integrations/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java @@ -26,8 +26,6 @@ import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.text.DecimalFormat; - import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.settings.SharedPrefCategory; import app.revanced.integrations.sponsorblock.SegmentPlaybackController; @@ -471,8 +469,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { } } - private static final DecimalFormat statsNumberOfSegmentsSkippedFormatter = new DecimalFormat("#,###,###"); - private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) { ReVancedUtils.verifyOnMainThread(); try { @@ -514,7 +510,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { // number of segment submissions (does not include ignored segments) Preference preference = new Preference(context); statsCategory.addPreference(preference); - String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount); + String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount); preference.setTitle(fromHtml(str("sb_stats_submissions", formatted))); if (stats.totalSegmentCountIncludingIgnored == 0) { preference.setSelectable(false); @@ -550,7 +546,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { stats_saved = str("sb_stats_saved_zero"); stats_saved_sum = str("sb_stats_saved_sum_zero"); } else { - stats_saved = str("sb_stats_saved", statsNumberOfSegmentsSkippedFormatter.format(stats.viewCount)); + stats_saved = str("sb_stats_saved", + SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount)); stats_saved_sum = str("sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved))); } preference.setTitle(fromHtml(stats_saved)); @@ -573,7 +570,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { statsCategory.addPreference(preference); Runnable updateStatsSelfSaved = () -> { - String formatted = statsNumberOfSegmentsSkippedFormatter.format(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt()); + String formatted = SponsorBlockUtils.getNumberOfSkipsString(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt()); preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted))); String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / 1000); preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved))); diff --git a/integrations/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java b/integrations/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java index ba2140096..c8edbdfca 100644 --- a/integrations/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java +++ b/integrations/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java @@ -510,15 +510,18 @@ public class SegmentPlaybackController { SponsorBlockViewController.hideSkipHighlightButton(); SponsorBlockViewController.hideSkipSegmentButton(); - // If trying to seek to end of the video, YouTube can seek just before of the actual end. - // (especially if the video does not end on a whole second boundary). - // This causes additional segment skip attempts, even though it cannot seek any closer to the desired time. - // Check for and ignore repeated skip attempts of the same segment over a small time period. final long now = System.currentTimeMillis(); - final long minimumMillisecondsBetweenSkippingSameSegment = 500; - if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) { - LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip); - return; + if (lastSegmentSkipped == segmentToSkip) { + // If trying to seek to end of the video, YouTube can seek just before of the actual end. + // (especially if the video does not end on a whole second boundary). + // This causes additional segment skip attempts, even though it cannot seek any closer to the desired time. + // Check for and ignore repeated skip attempts of the same segment over a small time period. + final long minTimeBetweenSkippingSameSegment = Math.max(500, + (long) (500 / VideoInformation.getPlaybackSpeed())); + if (now - lastSegmentSkippedTime < minTimeBetweenSkippingSameSegment) { + LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip); + return; + } } LogHelper.printDebug(() -> "Skipping segment: " + segmentToSkip); diff --git a/integrations/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java b/integrations/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java index 37020fce5..ef30eb247 100644 --- a/integrations/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java +++ b/integrations/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java @@ -12,6 +12,7 @@ import android.widget.EditText; import androidx.annotation.NonNull; import java.lang.ref.WeakReference; +import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Duration; @@ -39,6 +40,7 @@ public class SponsorBlockUtils { private static final SimpleDateFormat manualEditTimeFormatter = new SimpleDateFormat(MANUAL_EDIT_TIME_FORMAT); @SuppressLint("SimpleDateFormat") private static final SimpleDateFormat voteSegmentTimeFormatter = new SimpleDateFormat(); + private static final NumberFormat statsNumberFormatter = NumberFormat.getNumberInstance(); static { TimeZone utc = TimeZone.getTimeZone("UTC"); manualEditTimeFormatter.setTimeZone(utc); @@ -402,19 +404,27 @@ public class SponsorBlockUtils { } } + public static String getNumberOfSkipsString(int viewCount) { + return statsNumberFormatter.format(viewCount); + } + public static String getTimeSavedString(long totalSecondsSaved) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { Duration duration = Duration.ofSeconds(totalSecondsSaved); - final long hoursSaved = duration.toHours(); - final long minutesSaved = duration.toMinutes() % 60; - if (hoursSaved > 0) { - return str("sb_stats_saved_hour_format", hoursSaved, minutesSaved); + final long hours = duration.toHours(); + final long minutes = duration.toMinutes() % 60; + // Format all numbers so non-western numbers use a consistent appearance. + String minutesFormatted = statsNumberFormatter.format(minutes); + if (hours > 0) { + String hoursFormatted = statsNumberFormatter.format(hours); + return str("sb_stats_saved_hour_format", hoursFormatted, minutesFormatted); } - final long secondsSaved = duration.getSeconds() % 60; - if (minutesSaved > 0) { - return str("sb_stats_saved_minute_format", minutesSaved, secondsSaved); + final long seconds = duration.getSeconds() % 60; + String secondsFormatted = statsNumberFormatter.format(seconds); + if (minutes > 0) { + return str("sb_stats_saved_minute_format", minutesFormatted, secondsFormatted); } - return str("sb_stats_saved_second_format", secondsSaved); + return str("sb_stats_saved_second_format", secondsFormatted); } return "error"; // will never be reached. YouTube requires Android O or greater } diff --git a/integrations/java/app/revanced/tiktok/cleardisplay/RememberClearDisplayPatch.java b/integrations/java/app/revanced/tiktok/cleardisplay/RememberClearDisplayPatch.java new file mode 100644 index 000000000..b9171f3c9 --- /dev/null +++ b/integrations/java/app/revanced/tiktok/cleardisplay/RememberClearDisplayPatch.java @@ -0,0 +1,12 @@ +package app.revanced.tiktok.cleardisplay; + +import app.revanced.tiktok.settings.SettingsEnum; + +public class RememberClearDisplayPatch { + public static boolean getClearDisplayState() { + return SettingsEnum.CLEAR_DISPLAY.getBoolean(); + } + public static void rememberClearDisplayState(boolean newState) { + SettingsEnum.CLEAR_DISPLAY.saveValue(newState); + } +} diff --git a/integrations/java/app/revanced/tiktok/settings/SettingsEnum.java b/integrations/java/app/revanced/tiktok/settings/SettingsEnum.java index 21dd53e9a..4b6d93f24 100644 --- a/integrations/java/app/revanced/tiktok/settings/SettingsEnum.java +++ b/integrations/java/app/revanced/tiktok/settings/SettingsEnum.java @@ -25,6 +25,7 @@ public enum SettingsEnum { MIN_MAX_LIKES("min_max_likes", STRING, "0-" + Long.MAX_VALUE, true), DOWNLOAD_PATH("down_path", STRING, "DCIM/TikTok"), DOWNLOAD_WATERMARK("down_watermark", BOOLEAN, TRUE), + CLEAR_DISPLAY("clear_display", BOOLEAN, FALSE), SIM_SPOOF("simspoof", BOOLEAN, TRUE, true), SIM_SPOOF_ISO("simspoof_iso", STRING, "us"), SIMSPOOF_MCCMNC("simspoof_mccmnc", STRING, "310160"),