From d7fc06a647c539a593329dd6dc4638d09dbe9b2a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 21 Jul 2023 19:17:06 +0200 Subject: [PATCH 01/16] ci: clean after building --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cab88c30..c4fd0e14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: - name: Build with Gradle env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew build + run: ./gradlew build clean - name: Setup semantic-release run: npm install - name: Release From c7d2a9b3101439b241218c45d0c4b64012adab65 Mon Sep 17 00:00:00 2001 From: johnconner122 <107796137+johnconner122@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:47:56 +0500 Subject: [PATCH 02/16] feat(YouTube - Hide layout components): Hide `chips shelf` (#448) --- .../patches/components/LayoutComponentsFilter.java | 10 +++++++++- .../revanced/integrations/settings/SettingsEnum.java | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java index 71137084..f88b23e5 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java @@ -136,6 +136,11 @@ public final class LayoutComponentsFilter extends Filter { "cell_divider" // layout residue (gray line above the buttoned ad), ); + final var chipsShelf = new StringFilterGroup( + SettingsEnum.HIDE_CHIPS_SHELF, + "chips_shelf" + ); + this.pathFilterGroups.addAll( channelBar, communityPosts, @@ -158,7 +163,10 @@ public final class LayoutComponentsFilter extends Filter { channelMemberShelf ); - this.identifierFilterGroups.addAll(graySeparator); + this.identifierFilterGroups.addAll( + graySeparator, + chipsShelf + ); } @Override diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 0d632642..9171b411 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -105,6 +105,7 @@ public enum SettingsEnum { HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true), HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true), HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true), + HIDE_CHIPS_SHELF("revanced_hide_chips_shelf", BOOLEAN, TRUE), HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true), HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE), HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE), From 8ef81235d761a9761d6ef7d2dd44d00902c1b277 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 26 Jul 2023 13:51:32 +0000 Subject: [PATCH 03/16] chore(release): 0.115.0-dev.1 [skip ci] # [0.115.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.114.0...v0.115.0-dev.1) (2023-07-26) ### Features * **YouTube - Hide layout components:** Hide `chips shelf` ([#448](https://github.com/ReVanced/revanced-integrations/issues/448)) ([c7d2a9b](https://github.com/ReVanced/revanced-integrations/commit/c7d2a9b3101439b241218c45d0c4b64012adab65)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8bcb1f..36064c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.114.0...v0.115.0-dev.1) (2023-07-26) + + +### Features + +* **YouTube - Hide layout components:** Hide `chips shelf` ([#448](https://github.com/ReVanced/revanced-integrations/issues/448)) ([c7d2a9b](https://github.com/ReVanced/revanced-integrations/commit/c7d2a9b3101439b241218c45d0c4b64012adab65)) + # [0.114.0](https://github.com/ReVanced/revanced-integrations/compare/v0.113.0...v0.114.0) (2023-07-21) diff --git a/gradle.properties b/gradle.properties index dbb24c66..b3bfe16b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.114.0 +version = 0.115.0-dev.1 From ea7bc5737584ce967becd5b9e5d0d57f5d2fbc49 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Thu, 27 Jul 2023 01:24:13 +0400 Subject: [PATCH 04/16] fix(YouTube - SponsorBlock): Fix skip highlight button showing when set to 'show in seekbar' --- .../sponsorblock/SegmentPlaybackController.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java b/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java index be42a498..ba214009 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java @@ -50,7 +50,7 @@ public class SegmentPlaybackController { private static SponsorSegment[] segments; /** - * Highlight segment, if one exists. + * Highlight segment, if one exists and the skip behavior is not set to {@link CategoryBehaviour#SHOW_IN_SEEKBAR}. */ @Nullable private static SponsorSegment highlightSegment; @@ -112,10 +112,13 @@ public class SegmentPlaybackController { segments = videoSegments; calculateTimeWithoutSegments(); - for (SponsorSegment segment : videoSegments) { - if (segment.category == SegmentCategory.HIGHLIGHT) { - highlightSegment = segment; - return; + if (SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY + || SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) { + for (SponsorSegment segment : videoSegments) { + if (segment.category == SegmentCategory.HIGHLIGHT) { + highlightSegment = segment; + return; + } } } highlightSegment = null; From fdc56bf68e6edc55161ec380cc0509aaf02e6662 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 26 Jul 2023 21:29:00 +0000 Subject: [PATCH 05/16] chore(release): 0.115.0-dev.2 [skip ci] # [0.115.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.1...v0.115.0-dev.2) (2023-07-26) ### Bug Fixes * **YouTube - SponsorBlock:** Fix skip highlight button showing when set to 'show in seekbar' ([ea7bc57](https://github.com/ReVanced/revanced-integrations/commit/ea7bc5737584ce967becd5b9e5d0d57f5d2fbc49)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36064c12..bd6e888e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.1...v0.115.0-dev.2) (2023-07-26) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Fix skip highlight button showing when set to 'show in seekbar' ([ea7bc57](https://github.com/ReVanced/revanced-integrations/commit/ea7bc5737584ce967becd5b9e5d0d57f5d2fbc49)) + # [0.115.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.114.0...v0.115.0-dev.1) (2023-07-26) diff --git a/gradle.properties b/gradle.properties index b3bfe16b..1749d01f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.1 +version = 0.115.0-dev.2 From e906c6d3a2195c94c926f9903f1e4e66c3714e3e Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Thu, 27 Jul 2023 07:56:58 +0400 Subject: [PATCH 06/16] fix(YouTube - Spoof App Version): Remove 17.30.35 target --- .../java/app/revanced/integrations/settings/SettingsEnum.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 9171b411..6f4bdca0 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -128,7 +128,7 @@ public enum SettingsEnum { PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE), SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true), SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"), - SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.30.35", true, parents(SPOOF_APP_VERSION)), + SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.01.35", true, parents(SPOOF_APP_VERSION)), USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true), WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true), @Deprecated From d4bc6cff68325e759a21374b0d2a3b7645680e3d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 27 Jul 2023 04:01:17 +0000 Subject: [PATCH 07/16] chore(release): 0.115.0-dev.3 [skip ci] # [0.115.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.2...v0.115.0-dev.3) (2023-07-27) ### Bug Fixes * **YouTube - Spoof App Version:** Remove 17.30.35 target ([e906c6d](https://github.com/ReVanced/revanced-integrations/commit/e906c6d3a2195c94c926f9903f1e4e66c3714e3e)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6e888e..cdbd36a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.2...v0.115.0-dev.3) (2023-07-27) + + +### Bug Fixes + +* **YouTube - Spoof App Version:** Remove 17.30.35 target ([e906c6d](https://github.com/ReVanced/revanced-integrations/commit/e906c6d3a2195c94c926f9903f1e4e66c3714e3e)) + # [0.115.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.1...v0.115.0-dev.2) (2023-07-26) diff --git a/gradle.properties b/gradle.properties index 1749d01f..42d59eed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.2 +version = 0.115.0-dev.3 From ac8de0a3b703851be9c8c3ac6f8889716f164c05 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Thu, 27 Jul 2023 09:18:35 +0400 Subject: [PATCH 08/16] chore(YouTube - Spoof App Version): Adjust target version --- .../java/app/revanced/integrations/settings/SettingsEnum.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 6f4bdca0..6cd43e0f 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -128,7 +128,7 @@ public enum SettingsEnum { PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE), SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true), SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"), - SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.01.35", true, parents(SPOOF_APP_VERSION)), + SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)), USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true), WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true), @Deprecated From b2085aeebf4cfc21d69c0abfe545b382e2c82abe Mon Sep 17 00:00:00 2001 From: johnconner122 <107796137+johnconner122@users.noreply.github.com> Date: Sun, 30 Jul 2023 14:51:16 +0500 Subject: [PATCH 09/16] feat(YouTube): add `Player Flyout Menu` patch (#416) Co-authored-by: oSumAtrIX Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- .../patches/HideWatchInVRPatch.java | 9 --- .../PlayerFlyoutMenuItemsFilter.java | 63 +++++++++++++++++++ .../integrations/settings/SettingsEnum.java | 13 +++- 3 files changed, 75 insertions(+), 10 deletions(-) delete mode 100644 app/src/main/java/app/revanced/integrations/patches/HideWatchInVRPatch.java create mode 100644 app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java diff --git a/app/src/main/java/app/revanced/integrations/patches/HideWatchInVRPatch.java b/app/src/main/java/app/revanced/integrations/patches/HideWatchInVRPatch.java deleted file mode 100644 index 538f8ea6..00000000 --- a/app/src/main/java/app/revanced/integrations/patches/HideWatchInVRPatch.java +++ /dev/null @@ -1,9 +0,0 @@ -package app.revanced.integrations.patches; - -import app.revanced.integrations.settings.SettingsEnum; - -public class HideWatchInVRPatch { - public static boolean hideWatchInVR() { - return SettingsEnum.HIDE_WATCH_IN_VR.getBoolean(); - } -} diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java new file mode 100644 index 00000000..3d33420c --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -0,0 +1,63 @@ +package app.revanced.integrations.patches.components; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import app.revanced.integrations.settings.SettingsEnum; + +public class PlayerFlyoutMenuItemsFilter extends Filter { + @RequiresApi(api = Build.VERSION_CODES.N) + public PlayerFlyoutMenuItemsFilter() { + protobufBufferFilterGroups.addAll( + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_QUALITY_MENU, + "yt_outline_gear" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_CAPTIONS_MENU, + "closed_caption" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_LOOP_VIDEO_MENU, + "yt_outline_arrow_repeat_1_" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_AMBIENT_MODE_MENU, + "yt_outline_screen_light" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_REPORT_MENU, + "yt_outline_flag" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_HELP_MENU, + "yt_outline_question_circle" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_MORE_INFO_MENU, + "yt_outline_info_circle" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_SPEED_MENU, + "yt_outline_play_arrow_half_circle" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_AUDIO_TRACK_MENU, + "yt_outline_person_radar" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_WATCH_IN_VR_MENU, + "yt_outline_vr" + ) + ); + } + + @Override + boolean isFiltered(String path, String identifier, byte[] _protobufBufferArray) { + if (identifier != null && identifier.startsWith("overflow_menu_item.eml|")) + return super.isFiltered(path, identifier, _protobufBufferArray); + + return false; + } +} diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 6cd43e0f..ef07eff4 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -124,7 +124,6 @@ public enum SettingsEnum { HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true), HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE), HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE), - HIDE_WATCH_IN_VR("revanced_hide_watch_in_vr", BOOLEAN, FALSE, true), PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE), SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true), SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"), @@ -150,6 +149,18 @@ public enum SettingsEnum { HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true), HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true), + //Player flyout menu items + HIDE_QUALITY_MENU("revanced_hide_player_flyout_quality", BOOLEAN, FALSE), + HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE), + HIDE_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", BOOLEAN, FALSE), + HIDE_AMBIENT_MODE_MENU("revanced_hide_player_flyout_ambient_mode", BOOLEAN, FALSE), + HIDE_REPORT_MENU("revanced_hide_player_flyout_report", BOOLEAN, TRUE), + HIDE_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE), + HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE), + HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE), + HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, TRUE), + HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE), + // Misc AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE), DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE), From 0207496926b2a7de5f59d3c870d160b12d1f58ea Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 30 Jul 2023 09:55:03 +0000 Subject: [PATCH 10/16] chore(release): 0.115.0-dev.4 [skip ci] # [0.115.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.3...v0.115.0-dev.4) (2023-07-30) ### Features * **YouTube:** add `Player Flyout Menu` patch ([#416](https://github.com/ReVanced/revanced-integrations/issues/416)) ([b2085ae](https://github.com/ReVanced/revanced-integrations/commit/b2085aeebf4cfc21d69c0abfe545b382e2c82abe)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbd36a9..b9eb1cc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.3...v0.115.0-dev.4) (2023-07-30) + + +### Features + +* **YouTube:** add `Player Flyout Menu` patch ([#416](https://github.com/ReVanced/revanced-integrations/issues/416)) ([b2085ae](https://github.com/ReVanced/revanced-integrations/commit/b2085aeebf4cfc21d69c0abfe545b382e2c82abe)) + # [0.115.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.2...v0.115.0-dev.3) (2023-07-27) diff --git a/gradle.properties b/gradle.properties index 42d59eed..61402bd3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.3 +version = 0.115.0-dev.4 From 18f29004b8d570915a0228e292f1256785ac2cab Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 1 Aug 2023 21:05:31 +0400 Subject: [PATCH 11/16] perf(YouTube): Filter litho components using prefix tree (#447) Co-authored-by: oSumAtrIX --- .../patches/components/AdsFilter.java | 19 +- .../patches/components/ButtonsFilter.java | 15 +- .../components/LayoutComponentsFilter.java | 38 +- .../patches/components/LithoFilterPatch.java | 400 ++++++++++++------ .../PlaybackSpeedMenuFilterPatch.java | 7 +- .../PlayerFlyoutMenuItemsFilter.java | 21 +- .../patches/components/ShortsFilter.java | 72 ++-- .../VideoQualityMenuFilterPatch.java | 7 +- .../quality/OldVideoQualityMenuPatch.java | 3 +- .../speed/CustomPlaybackSpeedPatch.java | 36 +- .../integrations/utils/ByteTrieSearch.java | 38 ++ .../integrations/utils/StringTrieSearch.java | 40 ++ .../integrations/utils/TrieSearch.java | 305 +++++++++++++ 13 files changed, 790 insertions(+), 211 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java create mode 100644 app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java create mode 100644 app/src/main/java/app/revanced/integrations/utils/TrieSearch.java diff --git a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java index 4e1bdfd8..3797f53a 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java @@ -2,21 +2,25 @@ package app.revanced.integrations.patches.components; import android.view.View; + +import androidx.annotation.Nullable; + import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.ReVancedUtils; +import app.revanced.integrations.utils.StringTrieSearch; public final class AdsFilter extends Filter { - private final String[] exceptions; + private final StringTrieSearch exceptions = new StringTrieSearch(); public AdsFilter() { - exceptions = new String[]{ + exceptions.addPatterns( "home_video_with_context", // Don't filter anything in the home page video component. "related_video_with_context", // Don't filter anything in the related video component. "comment_thread", // Don't filter anything in the comments. "|comment.", // Don't filter anything in the comments replies. - "library_recent_shelf", - }; + "library_recent_shelf" + ); final var buttonedAd = new StringFilterGroup( SettingsEnum.HIDE_BUTTONED_ADS, @@ -95,11 +99,12 @@ public final class AdsFilter extends Filter { } @Override - public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) { - if (ReVancedUtils.containsAny(path, exceptions)) + public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + if (exceptions.matches(path)) return false; - return super.isFiltered(path, identifier, _protobufBufferArray); + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } /** diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java index 42a4bdf2..5722e640 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java @@ -1,5 +1,7 @@ package app.revanced.integrations.patches.components; +import androidx.annotation.Nullable; + import app.revanced.integrations.settings.SettingsEnum; final class ButtonsFilter extends Filter { @@ -33,7 +35,8 @@ final class ButtonsFilter extends Filter { SettingsEnum.HIDE_ACTION_BUTTONS, "ContainerType|video_action_button", "|CellType|CollectionType|CellType|ContainerType|button.eml|" - ) + ), + actionBarRule ); } @@ -45,10 +48,12 @@ final class ButtonsFilter extends Filter { } @Override - public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) { - if (isEveryFilterGroupEnabled()) - if (actionBarRule.check(identifier).isFiltered()) return true; + public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + if (matchedGroup == actionBarRule) { + return isEveryFilterGroupEnabled(); + } - return super.isFiltered(path, identifier, _protobufBufferArray); + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java index f88b23e5..53860b9e 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java @@ -2,15 +2,16 @@ package app.revanced.integrations.patches.components; import android.os.Build; -import androidx.annotation.RequiresApi; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.utils.ReVancedUtils; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.StringTrieSearch; @RequiresApi(api = Build.VERSION_CODES.N) public final class LayoutComponentsFilter extends Filter { - private final String[] exceptions; - + private final StringTrieSearch exceptions = new StringTrieSearch(); private final CustomFilterGroup custom; private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup( @@ -20,13 +21,13 @@ public final class LayoutComponentsFilter extends Filter { @RequiresApi(api = Build.VERSION_CODES.N) public LayoutComponentsFilter() { - exceptions = new String[]{ + exceptions.addPatterns( "home_video_with_context", "related_video_with_context", "comment_thread", // skip filtering anything in the comments "|comment.", // skip filtering anything in the comments replies - "library_recent_shelf", - }; + "library_recent_shelf" + ); custom = new CustomFilterGroup( SettingsEnum.CUSTOM_FILTER, @@ -160,7 +161,8 @@ public final class LayoutComponentsFilter extends Filter { artistCard, imageShelf, subscribersCommunityGuidelines, - channelMemberShelf + channelMemberShelf, + custom ); this.identifierFilterGroups.addAll( @@ -170,19 +172,21 @@ public final class LayoutComponentsFilter extends Filter { } @Override - public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) { - if (custom.isEnabled() && custom.check(path).isFiltered()) - return true; - - if (ReVancedUtils.containsAny(path, exceptions)) + public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + if (matchedGroup != custom && exceptions.matches(path)) return false; // Exceptions are not filtered. - return super.isFiltered(path, identifier, _protobufBufferArray); + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } - // Called from a different place then the other filters. + /** + * Injection point. + * + * Called from a different place then the other filters. + */ public static boolean filterMixPlaylists(final byte[] bytes) { - return mixPlaylists.isEnabled() && mixPlaylists.check(bytes).isFiltered(); + return mixPlaylists.check(bytes).isFiltered(); } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index 8e10eea9..d475bd21 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -1,31 +1,30 @@ package app.revanced.integrations.patches.components; import android.os.Build; - import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.*; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Spliterator; +import java.util.*; import java.util.function.Consumer; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; - abstract class FilterGroup { final static class FilterGroupResult { - private final boolean filtered; - private final SettingsEnum setting; + SettingsEnum setting; + boolean filtered; - public FilterGroupResult(final SettingsEnum setting, final boolean filtered) { + FilterGroupResult(SettingsEnum setting, boolean filtered) { this.setting = setting; this.filtered = filtered; } + /** + * A null value if the group has no setting, + * or if no match is returned from {@link FilterGroupList#check(Object)}. + */ public SettingsEnum getSetting() { return setting; } @@ -48,51 +47,87 @@ abstract class FilterGroup { public FilterGroup(final SettingsEnum setting, final T... filters) { this.setting = setting; this.filters = filters; + if (filters.length == 0) { + throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)"); + } } public boolean isEnabled() { return setting == null || setting.getBoolean(); } + /** + * @return If {@link FilterGroupList} should include this group when searching. + * By default, all filters are included except non enabled settings that require reboot. + */ + public boolean includeInSearch() { + return isEnabled() || !setting.rebootApp; + } + + @NonNull + @Override + public String toString() { + return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting); + } + public abstract FilterGroupResult check(final T stack); } class StringFilterGroup extends FilterGroup { - /** - * {@link FilterGroup#FilterGroup(SettingsEnum, Object[])} - */ public StringFilterGroup(final SettingsEnum setting, final String... filters) { super(setting, filters); } @Override public FilterGroupResult check(final String string) { - return new FilterGroupResult(setting, string != null && ReVancedUtils.containsAny(string, filters)); + return new FilterGroupResult(setting, + (setting == null || setting.getBoolean()) && ReVancedUtils.containsAny(string, filters)); } } final class CustomFilterGroup extends StringFilterGroup { - /** - * {@link FilterGroup#FilterGroup(SettingsEnum, Object[])} - */ public CustomFilterGroup(final SettingsEnum setting, final SettingsEnum filter) { super(setting, filter.getString().split(",")); } } +/** + * If you have more than 1 filter patterns, then all instances of + * this class should filtered using {@link ByteArrayFilterGroupList#check(byte[])}, + * which uses a prefix tree to give better performance. + */ class ByteArrayFilterGroup extends FilterGroup { + + private int[][] failurePatterns; + // Modified implementation from https://stackoverflow.com/a/1507813 - private int indexOf(final byte[] data, final byte[] pattern) { - if (data.length == 0) - return -1; + private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) { + // Finds the first occurrence of the pattern in the byte array using + // KMP matching algorithm. + int patternLength = pattern.length; + for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) { + while (j > 0 && pattern[j] != data[i]) { + j = failure[j - 1]; + } + if (pattern[j] == data[i]) { + j++; + } + if (j == patternLength) { + return i - patternLength + 1; + } + } + return -1; + } + + private static int[] createFailurePattern(byte[] pattern) { // Computes the failure function using a boot-strapping process, // where the pattern is matched against itself. - final int[] failure = new int[pattern.length]; + final int patternLength = pattern.length; + final int[] failure = new int[patternLength]; - int j = 0; - for (int i = 1; i < pattern.length; i++) { + for (int i = 1, j = 0; i < patternLength; i++) { while (j > 0 && pattern[j] != pattern[i]) { j = failure[j - 1]; } @@ -101,54 +136,43 @@ class ByteArrayFilterGroup extends FilterGroup { } failure[i] = j; } - - // Finds the first occurrence of the pattern in the byte array using - // KMP matching algorithm. - - j = 0; - - for (int i = 0; i < data.length; i++) { - while (j > 0 && pattern[j] != data[i]) { - j = failure[j - 1]; - } - if (pattern[j] == data[i]) { - j++; - } - if (j == pattern.length) { - return i - pattern.length + 1; - } - } - return -1; + return failure; } - /** - * {@link FilterGroup#FilterGroup(SettingsEnum, Object[])} - */ - public ByteArrayFilterGroup(final SettingsEnum setting, final byte[]... filters) { + public ByteArrayFilterGroup(SettingsEnum setting, byte[]... filters) { super(setting, filters); } + private void buildFailurePatterns() { + LogHelper.printDebug(() -> "Building failure array for: " + this); + failurePatterns = new int[filters.length][]; + int i = 0; + for (byte[] pattern : filters) { + failurePatterns[i++] = createFailurePattern(pattern); + } + } + @Override public FilterGroupResult check(final byte[] bytes) { var matched = false; - for (byte[] filter : filters) { - if (indexOf(bytes, filter) == -1) - continue; - - matched = true; - break; + if (isEnabled()) { + if (failurePatterns == null) { + buildFailurePatterns(); // Lazy load. + } + for (int i = 0, length = filters.length; i < length; i++) { + if (indexOf(bytes, filters[i], failurePatterns[i]) >= 0) { + matched = true; + break; + } + } } - - final var filtered = matched; - return new FilterGroupResult(setting, filtered); + return new FilterGroupResult(setting, matched); } } + final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup { - /** - * {@link ByteArrayFilterGroup#ByteArrayFilterGroup(SettingsEnum, byte[]...)} - */ @RequiresApi(api = Build.VERSION_CODES.N) public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) { super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new)); @@ -156,11 +180,38 @@ final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup { } abstract class FilterGroupList> implements Iterable { - private final ArrayList filterGroups = new ArrayList<>(); + + private final List filterGroups = new ArrayList<>(); + /** + * Search graph. Created only if needed. + */ + private TrieSearch search; @SafeVarargs - protected final void addAll(final T... filterGroups) { - this.filterGroups.addAll(Arrays.asList(filterGroups)); + protected final void addAll(final T... groups) { + filterGroups.addAll(Arrays.asList(groups)); + search = null; // Rebuild, if already created. + } + + protected final void buildSearch() { + LogHelper.printDebug(() -> "Creating prefix search tree for: " + this); + search = createSearchGraph(); + for (T group : filterGroups) { + if (!group.includeInSearch()) { + continue; + } + for (V pattern : group.filters) { + search.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> { + if (group.isEnabled()) { + FilterGroup.FilterGroupResult result = (FilterGroup.FilterGroupResult) callbackParameter; + result.setting = group.setting; + result.filtered = true; + return true; + } + return false; + }); + } + } } @NonNull @@ -182,94 +233,207 @@ abstract class FilterGroupList> implements Iterable< return filterGroups.spliterator(); } - protected boolean contains(final V stack) { - for (T filterGroup : this) { - if (!filterGroup.isEnabled()) - continue; - - var result = filterGroup.check(stack); - if (result.isFiltered()) { - return true; - } + protected FilterGroup.FilterGroupResult check(V stack) { + if (search == null) { + buildSearch(); // Lazy load. } - - return false; + FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(null, false); + search.matches(stack, result); + return result; } + + protected abstract TrieSearch createSearchGraph(); } final class StringFilterGroupList extends FilterGroupList { + protected StringTrieSearch createSearchGraph() { + return new StringTrieSearch(); + } } +/** + * If searching for a single byte pattern, then it is slightly better to use + * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster + * than a prefix tree to search for only 1 pattern. + */ final class ByteArrayFilterGroupList extends FilterGroupList { + protected ByteTrieSearch createSearchGraph() { + return new ByteTrieSearch(); + } } abstract class Filter { - final protected StringFilterGroupList pathFilterGroups = new StringFilterGroupList(); - final protected StringFilterGroupList identifierFilterGroups = new StringFilterGroupList(); - final protected ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList(); + /** + * All group filters must be set before the constructor call completes. + * Otherwise {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)} + * will never be called for any matches. + */ + + protected final StringFilterGroupList pathFilterGroups = new StringFilterGroupList(); + protected final StringFilterGroupList identifierFilterGroups = new StringFilterGroupList(); + /** + * A collection of {@link ByteArrayFilterGroup} that are always searched for (no matter what). + * + * If possible, avoid adding values to this list and instead use a path or identifier filter + * for the item you are looking for. Then inside + * {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}, + * the buffer can then be searched using using a different + * {@link ByteArrayFilterGroupList} or a {@link ByteArrayFilterGroup}. + * This way, the expensive buffer searching only occurs if the cheap and fast path/identifier is already found. + */ + protected final ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList(); /** - * Check if the given path, identifier or protobuf buffer is filtered by any - * {@link FilterGroup}. Method is called off the main thread. + * Called after an enabled filter has been matched. + * Default implementation is to always filter the matched item. + * Subclasses can perform additional or different checks if needed. * - * @return True if filtered, false otherwise. + * 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. */ - boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) { - if (pathFilterGroups.contains(path)) { - LogHelper.printDebug(() -> String.format("Filtered path: %s", path)); - return true; + @SuppressWarnings("rawtypes") + boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + if (SettingsEnum.DEBUG.getBoolean()) { + if (pathFilterGroups == matchedList) { + LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path); + } else if (identifierFilterGroups == matchedList) { + LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier); + } else if (protobufBufferFilterGroups == matchedList) { + LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered from protobuf-buffer"); + } } - - if (identifierFilterGroups.contains(identifier)) { - LogHelper.printDebug(() -> String.format("Filtered identifier: %s", identifier)); - return true; - } - - if (protobufBufferFilterGroups.contains(protobufBufferArray)) { - LogHelper.printDebug(() -> "Filtered from protobuf-buffer"); - return true; - } - - return false; + return true; } } @RequiresApi(api = Build.VERSION_CODES.N) @SuppressWarnings("unused") public final class LithoFilterPatch { + /** + * Simple wrapper to pass the litho parameters through the prefix search. + */ + private static final class LithoFilterParameters { + final String path; + final String identifier; + final byte[] protoBuffer; + + LithoFilterParameters(StringBuilder lithoPath, String lithoIdentifier, ByteBuffer protoBuffer) { + this.path = lithoPath.toString(); + this.identifier = lithoIdentifier; + this.protoBuffer = protoBuffer.array(); + } + + @NonNull + @Override + public String toString() { + // Estimate the percentage of the buffer that are Strings. + StringBuilder builder = new StringBuilder(protoBuffer.length / 2); + builder.append( "ID: "); + builder.append(identifier); + builder.append(" Path: "); + builder.append(path); + // TODO: allow turning on/off buffer logging with a debug setting? + builder.append(" BufferStrings: "); + findAsciiStrings(builder, protoBuffer); + + return builder.toString(); + } + + /** + * Search through a byte array for all ASCII strings. + */ + private static void findAsciiStrings(StringBuilder builder, byte[] buffer) { + // Valid ASCII values (ignore control characters). + final int minimumAscii = 32; // 32 = space character + final int maximumAscii = 126; // 127 = delete character + final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include. + String delimitingCharacter = "❙"; // Non ascii character, to allow easier log filtering. + + final int length = buffer.length; + int start = 0; + int end = 0; + while (end < length) { + int value = buffer[end]; + if (value < minimumAscii || value > maximumAscii || end == length - 1) { + if (end - start >= minimumAsciiStringLength) { + builder.append(new String(buffer, start, end - start)); + builder.append(delimitingCharacter); + } + start = end + 1; + } + end++; + } + } + } + private static final Filter[] filters = new Filter[] { new DummyFilter() // Replaced by patch. }; + private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); + private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); + private static final ByteTrieSearch protoSearchTree = new ByteTrieSearch(); + + static { + for (Filter filter : filters) { + filterGroupLists(pathSearchTree, filter, filter.pathFilterGroups); + filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroups); + filterGroupLists(protoSearchTree, filter, filter.protobufBufferFilterGroups); + } + + LogHelper.printDebug(() -> "Using: " + + pathSearchTree.numberOfPatterns() + " path filters" + + " (" + pathSearchTree.getEstimatedMemorySize() + " KB), " + + identifierSearchTree.numberOfPatterns() + " identifier filters" + + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), " + + protoSearchTree.numberOfPatterns() + " buffer filters" + + " (" + protoSearchTree.getEstimatedMemorySize() + " KB)"); + } + + private static void filterGroupLists(TrieSearch pathSearchTree, + Filter filter, FilterGroupList> list) { + for (FilterGroup group : list) { + if (!group.includeInSearch()) { + continue; + } + for (T pattern : group.filters) { + pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> { + if (!group.isEnabled()) return false; + LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; + return filter.isFiltered(parameters.path, parameters.identifier, parameters.protoBuffer, + list, group, matchedStartIndex); + } + ); + } + } + } + /** * Injection point. Called off the main thread. */ @SuppressWarnings("unused") - public static boolean filter(final StringBuilder pathBuilder, final String identifier, - final ByteBuffer protobufBuffer) { - // TODO: Maybe this can be moved to the Filter class, to prevent unnecessary - // string creation - // because some filters might not need the path. - var path = pathBuilder.toString(); + public static boolean filter(@NonNull StringBuilder pathBuilder, @Nullable String lithoIdentifier, + @NonNull ByteBuffer protobufBuffer) { + try { + // It is assumed that protobufBuffer is empty as well in this case. + if (pathBuilder.length() == 0) + return false; - // It is assumed that protobufBuffer is empty as well in this case. - if (path.isEmpty()) - return false; + LithoFilterParameters parameter = new LithoFilterParameters(pathBuilder, lithoIdentifier, protobufBuffer); + LogHelper.printDebug(() -> "Searching " + parameter); - LogHelper.printDebug(() -> String.format( - "Searching (ID: %s, Buffer-size: %s): %s", - identifier, protobufBuffer.remaining(), path)); - - var protobufBufferArray = protobufBuffer.array(); - - for (var filter : filters) { - var filtered = filter.isFiltered(path, identifier, protobufBufferArray); - - LogHelper.printDebug( - () -> String.format("%s (ID: %s): %s", filtered ? "Filtered" : "Unfiltered", identifier, path)); - - if (filtered) - return true; + if (pathSearchTree.matches(parameter.path, parameter)) return true; + if (parameter.identifier != null) { + if (identifierSearchTree.matches(parameter.identifier, parameter)) return true; + } + if (protoSearchTree.matches(parameter.protoBuffer, parameter)) return true; + } catch (Exception ex) { + LogHelper.printException(() -> "Litho filter failure", ex); } return false; diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java index 66b229f4..ab2e63b4 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java @@ -1,5 +1,7 @@ package app.revanced.integrations.patches.components; +import androidx.annotation.Nullable; + // Abuse LithoFilter for CustomPlaybackSpeedPatch. public final class PlaybackSpeedMenuFilterPatch extends Filter { // Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread. @@ -13,8 +15,9 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter { } @Override - boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) { - isPlaybackSpeedMenuVisible = super.isFiltered(path, identifier, protobufBufferArray); + boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + isPlaybackSpeedMenuVisible = true; return false; } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java index 3d33420c..3f435e08 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -2,14 +2,22 @@ package app.revanced.integrations.patches.components; import android.os.Build; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import app.revanced.integrations.settings.SettingsEnum; public class PlayerFlyoutMenuItemsFilter extends Filter { + + // Search the buffer only if the flyout menu identifier 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(); + @RequiresApi(api = Build.VERSION_CODES.N) public PlayerFlyoutMenuItemsFilter() { - protobufBufferFilterGroups.addAll( + identifierFilterGroups.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|")); + + flyoutFilterGroupList.addAll( new ByteArrayAsStringFilterGroup( SettingsEnum.HIDE_QUALITY_MENU, "yt_outline_gear" @@ -54,10 +62,13 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { } @Override - boolean isFiltered(String path, String identifier, byte[] _protobufBufferArray) { - if (identifier != null && identifier.startsWith("overflow_menu_item.eml|")) - return super.isFiltered(path, identifier, _protobufBufferArray); - + boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + // 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()) { + // Super class handles logging. + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + } return false; } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java index ec1ffe46..c63a7614 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java @@ -1,22 +1,40 @@ package app.revanced.integrations.patches.components; -import android.view.View; -import app.revanced.integrations.settings.SettingsEnum; -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; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; + +import app.revanced.integrations.settings.SettingsEnum; + public final class ShortsFilter extends Filter { - // Set by patch. - public static PivotBar pivotBar; - final StringFilterGroupList shortsFilterGroup = new StringFilterGroupList(); - private final StringFilterGroup reelChannelBar = new StringFilterGroup( - null, - "reel_channel_bar" - ); + private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar"; + public static PivotBar pivotBar; // Set by patch. + + private final StringFilterGroup channelBar; + private final StringFilterGroup soundButton; + private final StringFilterGroup infoPanel; public ShortsFilter() { + channelBar = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_CHANNEL_BAR, + REEL_CHANNEL_BAR_PATH + ); + + soundButton = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_SOUND_BUTTON, + "reel_pivot_button" + ); + + infoPanel = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_INFO_PANEL, + "shorts_info_panel_overview" + ); + final var thanksButton = new StringFilterGroup( SettingsEnum.HIDE_SHORTS_THANKS_BUTTON, "suggested_action" @@ -32,21 +50,6 @@ public final class ShortsFilter extends Filter { "sponsor_button" ); - final var soundButton = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_SOUND_BUTTON, - "reel_pivot_button" - ); - - final var infoPanel = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_INFO_PANEL, - "shorts_info_panel_overview" - ); - - final var channelBar = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_CHANNEL_BAR, - "reel_channel_bar" - ); - final var shorts = new StringFilterGroup( SettingsEnum.HIDE_SHORTS, "shorts_shelf", @@ -55,22 +58,21 @@ public final class ShortsFilter extends Filter { "shorts_video_cell" ); - shortsFilterGroup.addAll(soundButton, infoPanel); - pathFilterGroups.addAll(joinButton, subscribeButton, channelBar); + pathFilterGroups.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel); identifierFilterGroups.addAll(shorts, thanksButton); } @Override - boolean isFiltered(final String path, final String identifier, - final byte[] protobufBufferArray) { + boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) return true; // Filter the path only when reelChannelBar is visible. - if (reelChannelBar.check(path).isFiltered()) - if (this.pathFilterGroups.contains(path)) return true; + if (pathFilterGroups == matchedList) { + return path.contains(REEL_CHANNEL_BAR_PATH); + } - if (shortsFilterGroup.contains(path)) return true; - - return this.identifierFilterGroups.contains(identifier); + return identifierFilterGroups == matchedList; } public static void hideShortsShelf(final View shortsShelfView) { diff --git a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java index a500c7ba..fb94de05 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java @@ -1,5 +1,7 @@ package app.revanced.integrations.patches.components; +import androidx.annotation.Nullable; + import app.revanced.integrations.settings.SettingsEnum; // Abuse LithoFilter for OldVideoQualityMenuPatch. @@ -15,8 +17,9 @@ public final class VideoQualityMenuFilterPatch extends Filter { } @Override - boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) { - isVideoQualityMenuVisible = super.isFiltered(path, identifier, protobufBufferArray); + boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + isVideoQualityMenuVisible = true; return false; } diff --git a/app/src/main/java/app/revanced/integrations/patches/playback/quality/OldVideoQualityMenuPatch.java b/app/src/main/java/app/revanced/integrations/patches/playback/quality/OldVideoQualityMenuPatch.java index 97582040..70defb4c 100644 --- a/app/src/main/java/app/revanced/integrations/patches/playback/quality/OldVideoQualityMenuPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/playback/quality/OldVideoQualityMenuPatch.java @@ -26,7 +26,8 @@ public final class OldVideoQualityMenuPatch { // The quality menu is a RecyclerView with 3 children. The third child is the "Advanced" quality menu. addRecyclerListener(linearLayout, 3, 2, recyclerView -> { // Check if the current view is the quality menu. - if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) {// Hide the video quality menu. + if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) { + VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false; linearLayout.setVisibility(View.GONE); // Click the "Advanced" quality menu to show the "old" quality menu. diff --git a/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java b/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java index 631ad943..a65a2530 100644 --- a/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -1,22 +1,19 @@ package app.revanced.integrations.patches.playback.speed; -import static app.revanced.integrations.patches.playback.quality.OldVideoQualityMenuPatch.addRecyclerListener; - import android.preference.ListPreference; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; - import androidx.annotation.NonNull; - -import com.facebook.litho.ComponentHost; - -import java.util.Arrays; - import app.revanced.integrations.patches.components.PlaybackSpeedMenuFilterPatch; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; +import com.facebook.litho.ComponentHost; + +import java.util.Arrays; + +import static app.revanced.integrations.patches.playback.quality.OldVideoQualityMenuPatch.addRecyclerListener; public class CustomPlaybackSpeedPatch { /** @@ -110,23 +107,24 @@ public class CustomPlaybackSpeedPatch { } /* - * To reduce copy paste between two similar code paths. + * To reduce copy and paste between two similar code paths. */ public static void onFlyoutMenuCreate(final LinearLayout linearLayout) { // The playback rate menu is a RecyclerView with 2 children. The third child is the "Advanced" quality menu. addRecyclerListener(linearLayout, 2, 1, recyclerView -> { - if (PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible && - recyclerView.getChildCount() == 1 && - recyclerView.getChildAt(0) instanceof ComponentHost - ) { - linearLayout.setVisibility(View.GONE); + if (PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible) { + PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible = false; - // Close the new Playback speed menu and instead show the old one. - showOldPlaybackSpeedMenu(); + if (recyclerView.getChildCount() == 1 && recyclerView.getChildAt(0) instanceof ComponentHost) { + linearLayout.setVisibility(View.GONE); - // DismissView [R.id.touch_outside] is the 1st ChildView of the 3rd ParentView. - ((ViewGroup) linearLayout.getParent().getParent().getParent()) - .getChildAt(0).performClick(); + // Close the new Playback speed menu and instead show the old one. + showOldPlaybackSpeedMenu(); + + // DismissView [R.id.touch_outside] is the 1st ChildView of the 3rd ParentView. + ((ViewGroup) linearLayout.getParent().getParent().getParent()) + .getChildAt(0).performClick(); + } } }); } diff --git a/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java new file mode 100644 index 00000000..e83a921c --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java @@ -0,0 +1,38 @@ +package app.revanced.integrations.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Objects; + +public final class ByteTrieSearch extends TrieSearch { + + private static final class ByteTrieNode extends TrieNode { + TrieNode createNode() { + return new ByteTrieNode(); + } + char getCharValue(byte[] text, int index) { + return (char) text[index]; + } + } + + public ByteTrieSearch() { + super(new ByteTrieNode()); + } + + @Override + public void addPattern(@NonNull byte[] pattern) { + super.addPattern(pattern, pattern.length, null); + } + + @Override + public void addPattern(@NonNull byte[] pattern, @NonNull TriePatternMatchedCallback callback) { + super.addPattern(pattern, pattern.length, Objects.requireNonNull(callback)); + } + + @Override + public boolean matches(@NonNull byte[] textToSearch, @Nullable Object callbackParameter) { + return super.matches(textToSearch, textToSearch.length, callbackParameter); + } + +} diff --git a/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java new file mode 100644 index 00000000..7afafc60 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java @@ -0,0 +1,40 @@ +package app.revanced.integrations.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Objects; + +/** + * Text pattern searching using a prefix tree (trie). + */ +public final class StringTrieSearch extends TrieSearch { + + private static final class StringTrieNode extends TrieNode { + TrieNode createNode() { + return new StringTrieNode(); + } + char getCharValue(String text, int index) { + return text.charAt(index); + } + } + + public StringTrieSearch() { + super(new StringTrieNode()); + } + + @Override + public void addPattern(@NonNull String pattern) { + super.addPattern(pattern, pattern.length(), null); + } + + @Override + public void addPattern(@NonNull String pattern, @NonNull TriePatternMatchedCallback callback) { + super.addPattern(pattern, pattern.length(), Objects.requireNonNull(callback)); + } + + @Override + public boolean matches(@NonNull String textToSearch, @Nullable Object callbackParameter) { + return super.matches(textToSearch, textToSearch.length(), callbackParameter); + } +} diff --git a/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java new file mode 100644 index 00000000..9d2db167 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java @@ -0,0 +1,305 @@ +package app.revanced.integrations.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Searches for a group of different patterns using a trie (prefix tree). + * Can significantly speed up searching for multiple patterns. + * + * Currently only supports ASCII non-control characters (letters/numbers/symbols). + * But could be modified to also support UTF-8 unicode. + */ +public abstract class TrieSearch { + + public interface TriePatternMatchedCallback { + /** + * Called when a pattern is matched. + * + * @param textSearched Text that was searched. + * @param matchedStartIndex Start index of the search text, where the pattern was matched. + * @param callbackParameter Optional parameter passed into {@link TrieSearch#matches(Object, Object)}. + * @return True, if the search should stop here. + * If false, searching will continue to look for other matches. + */ + boolean patternMatched(T textSearched, int matchedStartIndex, Object callbackParameter); + } + + /** + * Represents a compressed tree path for a single pattern that shares no sibling nodes. + * + * For example, if a tree contains the patterns: "foobar", "football", "feet", + * it would contain 3 compressed paths of: "bar", "tball", "eet". + * + * And the tree would contain children arrays only for the first level containing 'f', + * the second level containing 'o', + * and the third level containing 'o'. + * + * This is done to reduce memory usage, which can be substantial if many long patterns are used. + */ + private static final class TrieCompressedPath { + final T pattern; + final int patternLength; + final int patternStartIndex; + final TriePatternMatchedCallback callback; + + TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback callback) { + this.pattern = pattern; + this.patternLength = patternLength; + this.patternStartIndex = patternStartIndex; + this.callback = callback; + } + boolean matches(TrieNode enclosingNode, // Used only for the get character method. + T searchText, int searchTextLength, int searchTextIndex, Object callbackParameter) { + if (searchTextLength - searchTextIndex < patternLength - patternStartIndex) { + return false; // Remaining search text is shorter than the remaining leaf pattern and they cannot match. + } + for (int i = searchTextIndex, j = patternStartIndex; j < patternLength; i++, j++) { + if (enclosingNode.getCharValue(searchText, i) != enclosingNode.getCharValue(pattern, j)) { + return false; + } + } + return callback == null + || callback.patternMatched(searchText, searchTextIndex - patternStartIndex, callbackParameter); + } + } + + static abstract class TrieNode { + // Support only ASCII letters/numbers/symbols and filter out all control characters. + private static final char MIN_VALID_CHAR = 32; // Space character. + private static final char MAX_VALID_CHAR = 126; // 127 = delete character. + private static final int NUMBER_OF_CHILDREN = MAX_VALID_CHAR - MIN_VALID_CHAR + 1; + + private static boolean isInvalidRange(char character) { + return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR; + } + + /** + * A compressed graph path that represents the remaining pattern characters of a single child node. + * + * If present then child array is always null, although callbacks for other + * end of patterns can also exist on this same node. + */ + @Nullable + private TrieCompressedPath leaf; + + /** + * All child nodes. Only present if no compressed leaf exist. + */ + @Nullable + private TrieNode[] children; + + /** + * Callbacks for all patterns that end at this node. + */ + @Nullable + private List> endOfPatternCallback; + + /** + * @param pattern Pattern to add. + * @param patternLength Length of the pattern. + * @param patternIndex Current recursive index of the pattern. + * @param callback Callback, where a value of NULL indicates to always accept a pattern match. + */ + private void addPattern(@NonNull T pattern, int patternLength, int patternIndex, + @Nullable TriePatternMatchedCallback callback) { + if (patternIndex == patternLength) { // Reached the end of the pattern. + if (endOfPatternCallback == null) { + endOfPatternCallback = new ArrayList<>(1); + } + endOfPatternCallback.add(callback); + return; + } + if (leaf != null) { + // Reached end of the graph and a leaf exist. + // Recursively call back into this method and push the existing leaf down 1 level. + if (children != null) throw new IllegalStateException(); + //noinspection unchecked + children = new TrieNode[NUMBER_OF_CHILDREN]; + TrieCompressedPath temp = leaf; + leaf = null; + addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback); + // Continue onward and add the parameter pattern. + } else if (children == null) { + leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback); + return; + } + char character = getCharValue(pattern, patternIndex); + if (isInvalidRange(character)) { + throw new IllegalArgumentException("invalid character at index " + patternIndex + ": " + pattern); + } + character -= MIN_VALID_CHAR; // Adjust to the array range. + TrieNode child = children[character]; + if (child == null) { + child = createNode(); + children[character] = child; + } + child.addPattern(pattern, patternLength, patternIndex + 1, callback); + } + + /** + * @param searchText Text to search for patterns in. + * @param searchTextLength Length of the search text. + * @param searchTextIndex Current recursive search text index. Also, the end index of the current pattern match. + * @param currentMatchLength current search depth, and also the length of the current pattern match. + * @return If any pattern matches, and it's associated callback halted the search. + */ + private boolean matches(T searchText, int searchTextLength, int searchTextIndex, int currentMatchLength, + Object callbackParameter) { + if (leaf != null && leaf.matches(this, + searchText, searchTextLength, searchTextIndex, callbackParameter)) { + return true; // Leaf exists and it matched the search text. + } + if (endOfPatternCallback != null) { + final int matchStartIndex = searchTextIndex - currentMatchLength; + for (@Nullable TriePatternMatchedCallback callback : endOfPatternCallback) { + if (callback == null) { + return true; // No callback and all matches are valid. + } + if (callback.patternMatched(searchText, matchStartIndex, callbackParameter)) { + return true; // Callback confirmed the match. + } + } + } + if (children == null) { + return false; // Reached a graph end point and there's no further patterns to search. + } + + if (searchTextIndex == searchTextLength) { + return false; // Reached end of the search text and found no matches. + } + + char character = getCharValue(searchText, searchTextIndex); + if (isInvalidRange(character)) { + return false; // Not an ASCII letter/number/symbol. + } + character -= MIN_VALID_CHAR; // Adjust to the array range. + TrieNode child = children[character]; + if (child == null) { + return false; + } + return child.matches(searchText, searchTextLength, searchTextIndex + 1, + currentMatchLength + 1, callbackParameter); + } + + /** + * Gives an approximate memory usage. + * + * @return Estimated number of memory pointers used, starting from this node and including all children. + */ + private int estimatedNumberOfPointersUsed() { + int numberOfPointers = 3; // Number of fields in this class. + if (leaf != null) { + numberOfPointers += 4; // Number of fields in leaf node. + } + if (endOfPatternCallback != null) { + numberOfPointers += endOfPatternCallback.size(); + } + if (children != null) { + numberOfPointers += NUMBER_OF_CHILDREN; + for (TrieNode child : children) { + if (child != null) { + numberOfPointers += child.estimatedNumberOfPointersUsed(); + } + } + } + return numberOfPointers; + } + + abstract TrieNode createNode(); + abstract char getCharValue(T text, int index); + } + + /** + * Root node, and it's children represent the first pattern characters. + */ + private final TrieNode root; + + /** + * Patterns to match. + */ + private final List patterns = new ArrayList<>(); + + TrieSearch(@NonNull TrieNode root) { + this.root = Objects.requireNonNull(root); + } + + @SafeVarargs + public final void addPatterns(@NonNull T... patterns) { + for (T pattern : patterns) { + addPattern(pattern); + } + } + + void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback callback) { + if (patternLength == 0) return; // Nothing to match + + patterns.add(pattern); + root.addPattern(pattern, patternLength, 0, callback); + } + + boolean matches(@NonNull T textToSearch, int textToSearchLength, @Nullable Object callbackParameter) { + if (patterns.size() == 0) { + return false; // No patterns were added. + } + for (int i = 0; i < textToSearchLength; i++) { + if (root.matches(textToSearch, textToSearchLength, i, 0, callbackParameter)) return true; + } + return false; + } + + /** + * @return Estimated memory size (in kilobytes) of this instance. + */ + public int getEstimatedMemorySize() { + if (patterns.size() == 0) { + return 0; + } + // Assume the device has less than 32GB of ram (and can use pointer compression), + // or the device is 32-bit. + final int numberOfBytesPerPointer = 4; + return (int) Math.ceil((numberOfBytesPerPointer * root.estimatedNumberOfPointersUsed()) / 1024.0); + } + + public int numberOfPatterns() { + return patterns.size(); + } + + public List getPatterns() { + return Collections.unmodifiableList(patterns); + } + + /** + * Adds a pattern that will always return a positive match if found. + * + * @param pattern Pattern to add. Calling this with a zero length pattern does nothing. + */ + public abstract void addPattern(@NonNull T pattern); + + /** + * @param pattern Pattern to add. Calling this with a zero length pattern does nothing. + * @param callback Callback to determine if searching should halt when a match is found. + */ + public abstract void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback callback); + + /** + * Searches through text, looking for any substring that matches any pattern in this tree. + * + * @param textToSearch Text to search through. + * @param callbackParameter Optional parameter passed to the callbacks. + * @return If any pattern matched, and it's callback halted searching. + */ + public abstract boolean matches(@NonNull T textToSearch, @Nullable Object callbackParameter); + + /** + * Identical to {@link #matches(Object, Object)} but with a null callback parameter. + */ + public final boolean matches(@NonNull T textToSearch) { + return matches(textToSearch, null); + } +} From 1c2976526e27a84e57acd0916ce1173264bb9a0c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 1 Aug 2023 17:09:27 +0000 Subject: [PATCH 12/16] chore(release): 0.115.0-dev.5 [skip ci] # [0.115.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.4...v0.115.0-dev.5) (2023-08-01) ### Performance Improvements * **YouTube:** Filter litho components using prefix tree ([#447](https://github.com/ReVanced/revanced-integrations/issues/447)) ([18f2900](https://github.com/ReVanced/revanced-integrations/commit/18f29004b8d570915a0228e292f1256785ac2cab)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9eb1cc1..9c83b0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.4...v0.115.0-dev.5) (2023-08-01) + + +### Performance Improvements + +* **YouTube:** Filter litho components using prefix tree ([#447](https://github.com/ReVanced/revanced-integrations/issues/447)) ([18f2900](https://github.com/ReVanced/revanced-integrations/commit/18f29004b8d570915a0228e292f1256785ac2cab)) + # [0.115.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.3...v0.115.0-dev.4) (2023-07-30) diff --git a/gradle.properties b/gradle.properties index 61402bd3..88690447 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.4 +version = 0.115.0-dev.5 From d3f523f9dd5c3638cf9e2ba849b91e446daa99fa Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:04:29 +0400 Subject: [PATCH 13/16] fix(YouTube): fix potential litho filter thread race --- .../patches/components/LithoFilterPatch.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index d475bd21..b61c9df6 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -100,7 +100,7 @@ final class CustomFilterGroup extends StringFilterGroup { */ class ByteArrayFilterGroup extends FilterGroup { - private int[][] failurePatterns; + private volatile int[][] failurePatterns; // Modified implementation from https://stackoverflow.com/a/1507813 private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) { @@ -143,13 +143,15 @@ class ByteArrayFilterGroup extends FilterGroup { super(setting, filters); } - private void buildFailurePatterns() { + private synchronized void buildFailurePatterns() { + if (failurePatterns != null) return; // Thread race and another thread already initialized the search. LogHelper.printDebug(() -> "Building failure array for: " + this); - failurePatterns = new int[filters.length][]; + int[][] failurePatterns = new int[filters.length][]; int i = 0; for (byte[] pattern : filters) { failurePatterns[i++] = createFailurePattern(pattern); } + this.failurePatterns = failurePatterns; // Must set after initialization finishes. } @Override @@ -185,7 +187,7 @@ abstract class FilterGroupList> implements Iterable< /** * Search graph. Created only if needed. */ - private TrieSearch search; + private volatile TrieSearch search; @SafeVarargs protected final void addAll(final T... groups) { @@ -193,9 +195,11 @@ abstract class FilterGroupList> implements Iterable< search = null; // Rebuild, if already created. } - protected final void buildSearch() { + 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); - search = createSearchGraph(); + TrieSearch search = createSearchGraph(); for (T group : filterGroups) { if (!group.includeInSearch()) { continue; @@ -212,6 +216,7 @@ abstract class FilterGroupList> implements Iterable< }); } } + this.search = search; // Must set after it's completely initialized. } @NonNull From d2a8c33109ae69d34275e1d214e5c5e0f7ec6091 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 2 Aug 2023 11:09:39 +0000 Subject: [PATCH 14/16] chore(release): 0.115.0-dev.6 [skip ci] # [0.115.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.5...v0.115.0-dev.6) (2023-08-02) ### Bug Fixes * **YouTube:** fix potential litho filter thread race ([d3f523f](https://github.com/ReVanced/revanced-integrations/commit/d3f523f9dd5c3638cf9e2ba849b91e446daa99fa)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c83b0dd..3fdd1b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.5...v0.115.0-dev.6) (2023-08-02) + + +### Bug Fixes + +* **YouTube:** fix potential litho filter thread race ([d3f523f](https://github.com/ReVanced/revanced-integrations/commit/d3f523f9dd5c3638cf9e2ba849b91e446daa99fa)) + # [0.115.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.4...v0.115.0-dev.5) (2023-08-01) diff --git a/gradle.properties b/gradle.properties index 88690447..cf2c465d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.5 +version = 0.115.0-dev.6 From c16c038396bde365ce1eec7b385eeb1f485efe99 Mon Sep 17 00:00:00 2001 From: Vu Hoan Huy <43563783+d4rkk3y@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:09:07 +0700 Subject: [PATCH 15/16] feat(Tiktok - Feed filter): Add more filters (#445) Co-authored-by: oSumAtrIX --- .../tiktok/download/DownloadsPatch.java | 4 +- .../revanced/tiktok/feedfilter/AdsFilter.java | 16 ++ .../tiktok/feedfilter/FeedItemsFilter.java | 43 ++-- .../revanced/tiktok/feedfilter/IFilter.java | 9 + .../tiktok/feedfilter/ImageVideoFilter.java | 16 ++ .../tiktok/feedfilter/LikeCountFilter.java | 32 +++ .../tiktok/feedfilter/LiveFilter.java | 16 ++ .../tiktok/feedfilter/StoryFilter.java | 16 ++ .../tiktok/feedfilter/ViewCountFilter.java | 32 +++ .../tiktok/settings/SettingsEnum.java | 68 +++-- .../ReVancedPreferenceFragment.java | 119 +++++++++ .../ReVancedSettingsFragment.java | 242 ------------------ .../tiktok/settingsmenu/SettingsMenu.java | 2 +- .../tiktok/settingsmenu/SettingsStatus.java | 12 +- .../preference/DownloadPathPreference.java | 46 ++-- .../preference/InputTextPreference.java | 17 ++ .../preference/RangeValuePreference.java | 130 ++++++++++ .../preference/TogglePreference.java | 17 ++ .../ConditionalPreferenceCategory.java | 22 ++ .../DownloadsPreferenceCategory.java | 35 +++ .../FeedFilterPreferenceCategory.java | 55 ++++ .../IntegrationsPreferenceCategory.java | 28 ++ .../SimSpoofPreferenceCategory.java | 47 ++++ .../tiktok/spoof/sim/SpoofSimPatch.java | 8 +- .../app/revanced/tiktok/utils/LogHelper.java | 2 +- .../revanced/tiktok/utils/ReVancedUtils.java | 24 +- .../android/ugc/aweme/feed/model/Aweme.java | 38 ++- .../ugc/aweme/feed/model/AwemeStatistics.java | 10 + 28 files changed, 775 insertions(+), 331 deletions(-) create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/AdsFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/IFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/ImageVideoFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/LikeCountFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/LiveFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/StoryFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/feedfilter/ViewCountFilter.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedPreferenceFragment.java delete mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/InputTextPreference.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/RangeValuePreference.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/TogglePreference.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/ConditionalPreferenceCategory.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/DownloadsPreferenceCategory.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/FeedFilterPreferenceCategory.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/IntegrationsPreferenceCategory.java create mode 100644 app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/SimSpoofPreferenceCategory.java create mode 100644 dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/AwemeStatistics.java diff --git a/app/src/main/java/app/revanced/tiktok/download/DownloadsPatch.java b/app/src/main/java/app/revanced/tiktok/download/DownloadsPatch.java index e078ab66..fcbd4ffc 100644 --- a/app/src/main/java/app/revanced/tiktok/download/DownloadsPatch.java +++ b/app/src/main/java/app/revanced/tiktok/download/DownloadsPatch.java @@ -4,10 +4,10 @@ import app.revanced.tiktok.settings.SettingsEnum; public class DownloadsPatch { public static String getDownloadPath() { - return SettingsEnum.TIK_DOWN_PATH.getString(); + return SettingsEnum.DOWNLOAD_PATH.getString(); } public static boolean shouldRemoveWatermark() { - return SettingsEnum.TIK_DOWN_WATERMARK.getBoolean(); + return SettingsEnum.DOWNLOAD_WATERMARK.getBoolean(); } } diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/AdsFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/AdsFilter.java new file mode 100644 index 00000000..8270cff3 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/AdsFilter.java @@ -0,0 +1,16 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; + +public class AdsFilter implements IFilter { + @Override + public boolean getEnabled() { + return SettingsEnum.REMOVE_ADS.getBoolean(); + } + + @Override + public boolean getFiltered(Aweme item) { + return item.isAd() || item.isWithPromotionalMusic(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java index d04d3d94..e8ba8432 100644 --- a/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/FeedItemsFilter.java @@ -6,33 +6,28 @@ import com.ss.android.ugc.aweme.feed.model.FeedItemList; import java.util.Iterator; import java.util.List; -import app.revanced.tiktok.settings.SettingsEnum; - -public class FeedItemsFilter { +public final class FeedItemsFilter { + private static final List FILTERS = List.of( + new AdsFilter(), + new LiveFilter(), + new StoryFilter(), + new ImageVideoFilter(), + new ViewCountFilter(), + new LikeCountFilter() + ); public static void filter(FeedItemList feedItemList) { - if (SettingsEnum.TIK_REMOVE_ADS.getBoolean()) removeAds(feedItemList); - if (SettingsEnum.TIK_HIDE_LIVE.getBoolean()) removeLive(feedItemList); - } + Iterator feedItemListIterator = feedItemList.items.iterator(); + while (feedItemListIterator.hasNext()) { + Aweme item = feedItemListIterator.next(); + if (item == null) continue; - private static void removeAds(FeedItemList feedItemList) { - List items = feedItemList.items; - Iterator it = items.iterator(); - while (it.hasNext()) { - Aweme item = it.next(); - if (item != null && (item.isAd() || item.isWithPromotionalMusic())) { - it.remove(); - } - } - } - - private static void removeLive(FeedItemList feedItemList) { - List items = feedItemList.items; - Iterator it = items.iterator(); - while (it.hasNext()) { - Aweme item = it.next(); - if (item != null && (item.isLive() || item.isLiveReplay())) { - it.remove(); + for (IFilter filter : FILTERS) { + boolean enabled = filter.getEnabled(); + if (enabled && filter.getFiltered(item)) { + feedItemListIterator.remove(); + break; + } } } } diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/IFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/IFilter.java new file mode 100644 index 00000000..5ddcddf4 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/IFilter.java @@ -0,0 +1,9 @@ +package app.revanced.tiktok.feedfilter; + +import com.ss.android.ugc.aweme.feed.model.Aweme; + +public interface IFilter { + boolean getEnabled(); + + boolean getFiltered(Aweme item); +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/ImageVideoFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/ImageVideoFilter.java new file mode 100644 index 00000000..b1d23c69 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/ImageVideoFilter.java @@ -0,0 +1,16 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; + +public class ImageVideoFilter implements IFilter { + @Override + public boolean getEnabled() { + return SettingsEnum.HIDE_IMAGE.getBoolean(); + } + + @Override + public boolean getFiltered(Aweme item) { + return item.isImage() || item.isPhotoMode(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/LikeCountFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/LikeCountFilter.java new file mode 100644 index 00000000..80657e38 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/LikeCountFilter.java @@ -0,0 +1,32 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; +import com.ss.android.ugc.aweme.feed.model.AwemeStatistics; + +import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax; + +public final class LikeCountFilter implements IFilter { + final long minLike; + final long maxLike; + + LikeCountFilter() { + long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_LIKES); + minLike = minMax[0]; + maxLike = minMax[1]; + } + + @Override + public boolean getEnabled() { + return true; + } + + @Override + public boolean getFiltered(Aweme item) { + AwemeStatistics statistics = item.getStatistics(); + if (statistics == null) return false; + + long likeCount = statistics.getDiggCount(); + return likeCount < minLike || likeCount > maxLike; + } +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/LiveFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/LiveFilter.java new file mode 100644 index 00000000..4a97c2d1 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/LiveFilter.java @@ -0,0 +1,16 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; + +public class LiveFilter implements IFilter { + @Override + public boolean getEnabled() { + return SettingsEnum.HIDE_LIVE.getBoolean(); + } + + @Override + public boolean getFiltered(Aweme item) { + return item.isLive() || item.isLiveReplay(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/StoryFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/StoryFilter.java new file mode 100644 index 00000000..0260fff0 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/StoryFilter.java @@ -0,0 +1,16 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; + +public class StoryFilter implements IFilter { + @Override + public boolean getEnabled() { + return SettingsEnum.HIDE_STORY.getBoolean(); + } + + @Override + public boolean getFiltered(Aweme item) { + return item.getIsTikTokStory(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/feedfilter/ViewCountFilter.java b/app/src/main/java/app/revanced/tiktok/feedfilter/ViewCountFilter.java new file mode 100644 index 00000000..ef869958 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/feedfilter/ViewCountFilter.java @@ -0,0 +1,32 @@ +package app.revanced.tiktok.feedfilter; + +import app.revanced.tiktok.settings.SettingsEnum; +import com.ss.android.ugc.aweme.feed.model.Aweme; +import com.ss.android.ugc.aweme.feed.model.AwemeStatistics; + +import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax; + +public class ViewCountFilter implements IFilter { + final long minView; + final long maxView; + + ViewCountFilter() { + long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_VIEWS); + minView = minMax[0]; + maxView = minMax[1]; + } + + @Override + public boolean getEnabled() { + return true; + } + + @Override + public boolean getFiltered(Aweme item) { + AwemeStatistics statistics = item.getStatistics(); + if (statistics == null) return false; + + long playCount = statistics.getPlayCount(); + return playCount < minView || playCount > maxView; + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java b/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java index 2318c869..21dd53e9 100644 --- a/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/tiktok/settings/SettingsEnum.java @@ -1,32 +1,42 @@ package app.revanced.tiktok.settings; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.BOOLEAN; -import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.STRING; - import android.content.Context; import android.util.Log; - import androidx.annotation.NonNull; - +import androidx.annotation.Nullable; import app.revanced.tiktok.utils.LogHelper; import app.revanced.tiktok.utils.ReVancedUtils; +import java.util.HashMap; +import java.util.Map; + +import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.BOOLEAN; +import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.STRING; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + public enum SettingsEnum { - //TikTok Settings - TIK_DEBUG("tik_debug", BOOLEAN, FALSE), // must be first value, otherwise logging during loading will not work - TIK_REMOVE_ADS("tik_remove_ads", BOOLEAN, TRUE, true), - TIK_HIDE_LIVE("tik_hide_live", BOOLEAN, FALSE, true), - TIK_DOWN_PATH("tik_down_path", STRING, "DCIM/TikTok"), - TIK_DOWN_WATERMARK("tik_down_watermark", BOOLEAN, TRUE), - TIK_SIMSPOOF("tik_simspoof", BOOLEAN, TRUE, true), - TIK_SIMSPOOF_ISO("tik_simspoof_iso", STRING, "us"), - TIK_SIMSPOOF_MCCMNC("tik_simspoof_mccmnc", STRING, "310160"), - TIK_SIMSPOOF_OP_NAME("tik_simspoof_op_name", STRING, "T-Mobile"); + DEBUG("debug", BOOLEAN, FALSE), // Must be first value, otherwise logging during loading will not work. + REMOVE_ADS("remove_ads", BOOLEAN, TRUE, true), + HIDE_LIVE("hide_live", BOOLEAN, FALSE, true), + HIDE_STORY("hide_story", BOOLEAN, FALSE, true), + HIDE_IMAGE("hide_image", BOOLEAN, FALSE, true), + MIN_MAX_VIEWS("min_max_views", STRING, "0-" + Long.MAX_VALUE, true), + 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), + SIM_SPOOF("simspoof", BOOLEAN, TRUE, true), + SIM_SPOOF_ISO("simspoof_iso", STRING, "us"), + SIMSPOOF_MCCMNC("simspoof_mccmnc", STRING, "310160"), + SIMSPOOF_OP_NAME("simspoof_op_name", STRING, "T-Mobile"); + + private static final Map pathToSetting = new HashMap<>(2 * values().length); static { loadAllSettings(); + for (SettingsEnum setting : values()) { + pathToSetting.put(setting.path, setting); + } } @NonNull @@ -47,11 +57,12 @@ public enum SettingsEnum { SettingsEnum(String path, ReturnType returnType, Object defaultValue) { this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, false); } + SettingsEnum(String path, ReturnType returnType, Object defaultValue, boolean rebootApp) { this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, rebootApp); } - SettingsEnum(@NonNull String path, @NonNull ReturnType returnType, @NonNull Object defaultValue, - @NonNull SharedPrefCategory prefName, boolean rebootApp) { + + SettingsEnum(@NonNull String path, @NonNull ReturnType returnType, @NonNull Object defaultValue, @NonNull SharedPrefCategory prefName, boolean rebootApp) { this.path = path; this.returnType = returnType; this.defaultValue = defaultValue; @@ -59,6 +70,11 @@ public enum SettingsEnum { this.rebootApp = rebootApp; } + @Nullable + public static SettingsEnum getSettingsFromPath(@NonNull String str) { + return pathToSetting.get(str); + } + private static void loadAllSettings() { try { Context context = ReVancedUtils.getAppContext(); @@ -144,11 +160,15 @@ public enum SettingsEnum { return (String) value; } + /** + * @return the value of this setting as as generic object type. + */ + @NonNull + public Object getObjectValue() { + return value; + } + public enum ReturnType { - BOOLEAN, - INTEGER, - LONG, - FLOAT, - STRING, + BOOLEAN, INTEGER, LONG, FLOAT, STRING, } } diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedPreferenceFragment.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedPreferenceFragment.java new file mode 100644 index 00000000..0fa735b3 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedPreferenceFragment.java @@ -0,0 +1,119 @@ +package app.revanced.tiktok.settingsmenu; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Process; +import android.preference.*; +import androidx.annotation.Nullable; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.settings.SharedPrefCategory; +import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference; +import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference; +import app.revanced.tiktok.settingsmenu.preference.categories.DownloadsPreferenceCategory; +import app.revanced.tiktok.settingsmenu.preference.categories.FeedFilterPreferenceCategory; +import app.revanced.tiktok.settingsmenu.preference.categories.IntegrationsPreferenceCategory; +import app.revanced.tiktok.settingsmenu.preference.categories.SimSpoofPreferenceCategory; +import app.revanced.tiktok.utils.ReVancedUtils; +import com.ss.android.ugc.aweme.splash.SplashActivity; + +@SuppressWarnings("deprecation") +public class ReVancedPreferenceFragment extends PreferenceFragment { + private boolean registered = false; + private boolean settingsInitialized = false; + + SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + try { + SettingsEnum setting = SettingsEnum.getSettingsFromPath(str); + if (setting == null) { + return; + } + Preference pref = findPreference(str); + if (pref == null) { + return; + } + if (pref instanceof SwitchPreference) { + SwitchPreference switchPref = (SwitchPreference) pref; + SettingsEnum.setValue(setting, switchPref.isChecked()); + } else if (pref instanceof EditTextPreference) { + EditTextPreference editPreference = (EditTextPreference) pref; + SettingsEnum.setValue(setting, editPreference.getText()); + } else if (pref instanceof ListPreference) { + ListPreference listPref = (ListPreference) pref; + SettingsEnum.setValue(setting, listPref.getValue()); + updateListPreferenceSummary((ListPreference) pref, setting); + } else if (pref instanceof RangeValuePreference) { + RangeValuePreference rangeValuePref = (RangeValuePreference) pref; + SettingsEnum.setValue(setting, rangeValuePref.getValue()); + } else if (pref instanceof DownloadPathPreference) { + DownloadPathPreference downloadPathPref = (DownloadPathPreference) pref; + SettingsEnum.setValue(setting, downloadPathPref.getValue()); + } else { + LogHelper.printException(() -> "Setting cannot be handled: " + pref.getClass() + " " + pref); + return; + } + if (ReVancedUtils.getAppContext() != null && this.settingsInitialized && setting.rebootApp) { + rebootDialog(getActivity()); + } + } catch (Exception ex) { + LogHelper.printException(() -> "OnSharedPreferenceChangeListener failure", ex); + } + }; + + private void updateListPreferenceSummary(ListPreference listPreference, SettingsEnum setting) { + String objectStringValue = setting.getObjectValue().toString(); + final int entryIndex = listPreference.findIndexOfValue(objectStringValue); + if (entryIndex >= 0) { + listPreference.setSummary(listPreference.getEntries()[entryIndex]); + listPreference.setValue(objectStringValue); + } else { + listPreference.setSummary(objectStringValue); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.registered = true; + + getPreferenceManager().setSharedPreferencesName(SharedPrefCategory.TIKTOK_PREFS.prefName); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener); + + final Activity context = this.getActivity(); + PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context); + setPreferenceScreen(preferenceScreen); + + new FeedFilterPreferenceCategory(context, preferenceScreen); + new DownloadsPreferenceCategory(context, preferenceScreen); + new SimSpoofPreferenceCategory(context, preferenceScreen); + new IntegrationsPreferenceCategory(context, preferenceScreen); + + this.settingsInitialized = true; + } + + @Override + public void onDestroy() { + if (this.registered) { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener); + this.registered = false; + } + + super.onDestroy(); + } + + private void reboot(Activity activity) { + int intent = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; + ((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, SplashActivity.class), intent)); + Process.killProcess(Process.myPid()); + } + + private void rebootDialog(final Activity activity) { + new AlertDialog.Builder(activity).setMessage("Refresh and restart").setPositiveButton("Restart", (dialog, i) -> reboot(activity)).setNegativeButton("Cancel", null).show(); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java deleted file mode 100644 index 0febb987..00000000 --- a/app/src/main/java/app/revanced/tiktok/settingsmenu/ReVancedSettingsFragment.java +++ /dev/null @@ -1,242 +0,0 @@ -package app.revanced.tiktok.settingsmenu; - -import android.app.Activity; -import android.app.AlarmManager; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Environment; -import android.os.Process; -import android.preference.EditTextPreference; -import android.preference.PreferenceCategory; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.preference.SwitchPreference; - -import androidx.annotation.Nullable; - -import com.ss.android.ugc.aweme.splash.SplashActivity; - -import app.revanced.tiktok.settings.SettingsEnum; -import app.revanced.tiktok.settings.SharedPrefCategory; -import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference; -import app.revanced.tiktok.utils.ReVancedUtils; - -public class ReVancedSettingsFragment extends PreferenceFragment { - - private boolean Registered = false; - private boolean settingsInitialized = false; - - SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { - for (SettingsEnum setting : SettingsEnum.values()) { - if (!setting.path.equals(str)) continue; - - if (ReVancedUtils.getAppContext() != null && this.settingsInitialized && setting.rebootApp) { - rebootDialog(getActivity()); - } - } - }; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getPreferenceManager().setSharedPreferencesName(SharedPrefCategory.TIKTOK_PREFS.prefName); - getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener); - this.Registered = true; - - final Activity context = this.getActivity(); - PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context); - setPreferenceScreen(preferenceScreen); - - //Feed filter - if (SettingsStatus.feedFilter) { - PreferenceCategory feedFilter = new PreferenceCategory(context); - feedFilter.setTitle("Feed filter"); - preferenceScreen.addPreference(feedFilter); - - //Remove ads toggle - { - SwitchPreference preference = new SwitchPreference(context); - feedFilter.addPreference(preference); - preference.setKey(SettingsEnum.TIK_REMOVE_ADS.path); - preference.setDefaultValue(SettingsEnum.TIK_REMOVE_ADS.defaultValue); - preference.setChecked(SettingsEnum.TIK_REMOVE_ADS.getBoolean()); - preference.setTitle("Remove feed ads"); - preference.setSummary("Remove ads from feed."); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - // FIXME: the value is already saved in the preferences. - // instead of saving again, simple call SettingsEnum#setValue() - final boolean value = (Boolean) newValue; - SettingsEnum.TIK_REMOVE_ADS.saveValue(value); - return true; - }); - } - //Hide LiveStreams toggle - { - SwitchPreference preference = new SwitchPreference(context); - feedFilter.addPreference(preference); - preference.setKey(SettingsEnum.TIK_HIDE_LIVE.path); - preference.setDefaultValue(SettingsEnum.TIK_HIDE_LIVE.defaultValue); - preference.setChecked(SettingsEnum.TIK_HIDE_LIVE.getBoolean()); - preference.setTitle("Hide livestreams"); - preference.setSummary("Hide livestreams from feed."); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final boolean value = (Boolean) newValue; - SettingsEnum.TIK_HIDE_LIVE.saveValue(value); - return true; - }); - } - } - - //Download - if (SettingsStatus.download) { - PreferenceCategory download = new PreferenceCategory(context); - download.setTitle("Download"); - preferenceScreen.addPreference(download); - //Download path - { - DownloadPathPreference preference = new DownloadPathPreference(context); - download.addPreference(preference); - preference.setKey(SettingsEnum.TIK_DOWN_PATH.path); - preference.setDefaultValue(SettingsEnum.TIK_DOWN_PATH.defaultValue); - preference.setValue(SettingsEnum.TIK_DOWN_PATH.getString()); - preference.setTitle("Download path"); - preference.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + preference.getValue()); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final String value = (String) newValue; - SettingsEnum.TIK_DOWN_PATH.saveValue(value); - return true; - }); - } - //Download watermark - { - SwitchPreference preference = new SwitchPreference(context); - download.addPreference(preference); - preference.setKey(SettingsEnum.TIK_DOWN_WATERMARK.path); - preference.setDefaultValue(SettingsEnum.TIK_DOWN_WATERMARK.defaultValue); - preference.setChecked(SettingsEnum.TIK_DOWN_WATERMARK.getBoolean()); - preference.setTitle("Remove watermark"); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final boolean value = (Boolean) newValue; - SettingsEnum.TIK_DOWN_WATERMARK.saveValue(value); - return true; - }); - } - } - - // SpoofSimPatch - if(SettingsStatus.simSpoof) { - PreferenceCategory simSpoof = new PreferenceCategory(context); - simSpoof.setTitle("Bypass regional restriction"); - preferenceScreen.addPreference(simSpoof); - //Global Switch - { - SwitchPreference preference = new SwitchPreference(context); - simSpoof.addPreference(preference); - preference.setKey(SettingsEnum.TIK_SIMSPOOF.path); - preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF.defaultValue); - preference.setChecked(SettingsEnum.TIK_SIMSPOOF.getBoolean()); - preference.setTitle("Fake sim card info"); - preference.setSummary("Bypass regional restriction by fake sim card information."); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final boolean value = (Boolean) newValue; - SettingsEnum.TIK_SIMSPOOF.saveValue(value); - return true; - }); - } - //Country ISO - { - EditTextPreference preference = new EditTextPreference(context); - simSpoof.addPreference(preference); - preference.setKey(SettingsEnum.TIK_SIMSPOOF_ISO.path); - preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_ISO.defaultValue); - preference.setText(SettingsEnum.TIK_SIMSPOOF_ISO.getString()); - preference.setTitle("Country ISO"); - preference.setSummary("us, uk, jp, ..."); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final String value = (String) newValue; - SettingsEnum.TIK_SIMSPOOF_ISO.saveValue(value); - return true; - }); - } - //Operator mcc+mnc - { - EditTextPreference preference = new EditTextPreference(context); - simSpoof.addPreference(preference); - preference.setKey(SettingsEnum.TIK_SIMSPOOF_MCCMNC.path); - preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_MCCMNC.defaultValue); - preference.setText(SettingsEnum.TIK_SIMSPOOF_MCCMNC.getString()); - preference.setTitle("Operator mcc+mnc"); - preference.setSummary("mcc+mnc"); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final String value = (String) newValue; - SettingsEnum.TIK_SIMSPOOF_MCCMNC.saveValue(value); - return true; - }); - } - //Operator name - { - EditTextPreference preference = new EditTextPreference(context); - simSpoof.addPreference(preference); - preference.setKey(SettingsEnum.TIK_SIMSPOOF_OP_NAME.path); - preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_OP_NAME.defaultValue); - preference.setText(SettingsEnum.TIK_SIMSPOOF_OP_NAME.getString()); - preference.setTitle("Operator name"); - preference.setSummary("Name of the operator"); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final String value = (String) newValue; - SettingsEnum.TIK_SIMSPOOF_OP_NAME.saveValue(value); - return true; - }); - } - } - - //Integration - PreferenceCategory integration = new PreferenceCategory(context); - integration.setTitle("Integration"); - preferenceScreen.addPreference(integration); - //Enable DebugLog toggle - { - SwitchPreference preference = new SwitchPreference(context); - integration.addPreference(preference); - preference.setKey(SettingsEnum.TIK_DEBUG.path); - preference.setDefaultValue(SettingsEnum.TIK_DEBUG.defaultValue); - preference.setChecked(SettingsEnum.TIK_DEBUG.getBoolean()); - preference.setTitle("Enable debug log"); - preference.setSummary("Show integration debug log."); - preference.setOnPreferenceChangeListener((pref, newValue) -> { - final boolean value = (Boolean) newValue; - SettingsEnum.TIK_DEBUG.saveValue(value); - return true; - }); - } - this.settingsInitialized = true; - } - - @Override // android.preference.PreferenceFragment, android.app.Fragment - public void onDestroy() { - if (this.Registered) { - getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener); - this.Registered = false; - } - super.onDestroy(); - } - - private void reboot(Activity activity) { - int intent; - intent = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; - ((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, SplashActivity.class), intent)); - Process.killProcess(Process.myPid()); - } - - private void rebootDialog(final Activity activity) { - new AlertDialog.Builder(activity). - setMessage("Refresh and restart"). - setPositiveButton("RESTART", (dialog, i) -> reboot(activity)) - .setNegativeButton("CANCEL", null) - .show(); - } -} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java index fc43153b..fc9bcfab 100644 --- a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java @@ -55,7 +55,7 @@ public class SettingsMenu { linearLayout.addView(fragment); base.setContentView(linearLayout); - PreferenceFragment preferenceFragment = new ReVancedSettingsFragment(); + PreferenceFragment preferenceFragment = new ReVancedPreferenceFragment(); base.getFragmentManager().beginTransaction().replace(fragmentId, preferenceFragment).commit(); return true; diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java index e770ad45..2b8eb12c 100644 --- a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsStatus.java @@ -1,20 +1,20 @@ package app.revanced.tiktok.settingsmenu; public class SettingsStatus { - public static boolean feedFilter = false; - public static boolean download = false; - public static boolean simSpoof = false; + public static boolean feedFilterEnabled = false; + public static boolean downloadEnabled = false; + public static boolean simSpoofEnabled = false; public static void enableFeedFilter() { - feedFilter = true; + feedFilterEnabled = true; } public static void enableDownload() { - download = true; + downloadEnabled = true; } public static void enableSimSpoof() { - simSpoof = true; + simSpoofEnabled = true; } public static void load() { diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/DownloadPathPreference.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/DownloadPathPreference.java index acf6e08b..734a2b59 100644 --- a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/DownloadPathPreference.java +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/DownloadPathPreference.java @@ -7,6 +7,7 @@ import android.os.Environment; import android.preference.DialogPreference; import android.text.Editable; import android.text.InputType; +import android.text.TextUtils; import android.text.TextWatcher; import android.view.View; import android.widget.EditText; @@ -14,23 +15,48 @@ import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; +import app.revanced.tiktok.settings.SettingsEnum; + +@SuppressWarnings("deprecation") public class DownloadPathPreference extends DialogPreference { private final Context context; private final String[] entryValues = {"DCIM", "Movies", "Pictures"}; - private String value; + private String mValue; + + private boolean mValueSet; private int mediaPathIndex; private String childDownloadPath; - public DownloadPathPreference(Context context) { + public DownloadPathPreference(Context context, String title, SettingsEnum setting) { super(context); this.context = context; + this.setTitle(title); + this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.getString()); + this.setKey(setting.path); + this.setValue(setting.getString()); + } + + public String getValue() { + return this.mValue; + } + + public void setValue(String value) { + final boolean changed = !TextUtils.equals(mValue, value); + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistString(value); + if (changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } } @Override protected View onCreateDialogView() { String currentMedia = getValue().split("/")[0]; childDownloadPath = getValue().substring(getValue().indexOf("/") + 1); - mediaPathIndex = findIndexOf(currentMedia); LinearLayout dialogView = new LinearLayout(context); @@ -84,10 +110,8 @@ public class DownloadPathPreference extends DialogPreference { protected void onDialogClosed(boolean positiveResult) { if (positiveResult && mediaPathIndex >= 0) { String newValue = entryValues[mediaPathIndex] + "/" + childDownloadPath; - if (callChangeListener(newValue)) { - setValue(newValue); - setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + newValue); - } + setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + newValue); + setValue(newValue); } } @@ -97,12 +121,4 @@ public class DownloadPathPreference extends DialogPreference { } return -1; } - - public String getValue() { - return this.value; - } - - public void setValue(String value) { - this.value = value; - } } diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/InputTextPreference.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/InputTextPreference.java new file mode 100644 index 00000000..0ee0e773 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/InputTextPreference.java @@ -0,0 +1,17 @@ +package app.revanced.tiktok.settingsmenu.preference; + +import android.content.Context; +import android.preference.EditTextPreference; + +import app.revanced.tiktok.settings.SettingsEnum; + +public class InputTextPreference extends EditTextPreference { + + public InputTextPreference(Context context, String title, String summary, SettingsEnum setting) { + super(context); + this.setTitle(title); + this.setSummary(summary); + this.setKey(setting.path); + this.setText(setting.getString()); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/RangeValuePreference.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/RangeValuePreference.java new file mode 100644 index 00000000..79344339 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/RangeValuePreference.java @@ -0,0 +1,130 @@ +package app.revanced.tiktok.settingsmenu.preference; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.DialogPreference; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import app.revanced.tiktok.settings.SettingsEnum; + +@SuppressWarnings("deprecation") +public class RangeValuePreference extends DialogPreference { + private final Context context; + + private String minValue; + + private String maxValue; + + private String mValue; + + private boolean mValueSet; + + public RangeValuePreference(Context context, String title, String summary, SettingsEnum setting) { + super(context); + this.context = context; + setTitle(title); + setSummary(summary); + setKey(setting.path); + setValue(setting.getString()); + } + + public void setValue(String value) { + final boolean changed = !TextUtils.equals(mValue, value); + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistString(value); + if (changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } + } + + public String getValue() { + return mValue; + } + + @Override + protected View onCreateDialogView() { + minValue = getValue().split("-")[0]; + maxValue = getValue().split("-")[1]; + LinearLayout dialogView = new LinearLayout(context); + dialogView.setOrientation(LinearLayout.VERTICAL); + LinearLayout minView = new LinearLayout(context); + minView.setOrientation(LinearLayout.HORIZONTAL); + TextView min = new TextView(context); + min.setText("Min: "); + minView.addView(min); + EditText minEditText = new EditText(context); + minEditText.setInputType(InputType.TYPE_CLASS_NUMBER); + minEditText.setText(minValue); + minView.addView(minEditText); + dialogView.addView(minView); + LinearLayout maxView = new LinearLayout(context); + maxView.setOrientation(LinearLayout.HORIZONTAL); + TextView max = new TextView(context); + max.setText("Max: "); + maxView.addView(max); + EditText maxEditText = new EditText(context); + maxEditText.setInputType(InputType.TYPE_CLASS_NUMBER); + maxEditText.setText(maxValue); + maxView.addView(maxEditText); + dialogView.addView(maxView); + minEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + minValue = editable.toString(); + } + }); + maxEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + maxValue = editable.toString(); + } + }); + return dialogView; + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> this.onClick(dialog, DialogInterface.BUTTON_POSITIVE)); + builder.setNegativeButton(android.R.string.cancel, null); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + String newValue = minValue + "-" + maxValue; + setValue(newValue); + } + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/TogglePreference.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/TogglePreference.java new file mode 100644 index 00000000..f729920e --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/TogglePreference.java @@ -0,0 +1,17 @@ +package app.revanced.tiktok.settingsmenu.preference; + +import android.content.Context; +import android.preference.SwitchPreference; + +import app.revanced.tiktok.settings.SettingsEnum; + +@SuppressWarnings("deprecation") +public class TogglePreference extends SwitchPreference { + public TogglePreference(Context context, String title, String summary, SettingsEnum setting) { + super(context); + this.setTitle(title); + this.setSummary(summary); + this.setKey(setting.path); + this.setChecked(setting.getBoolean()); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/ConditionalPreferenceCategory.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/ConditionalPreferenceCategory.java new file mode 100644 index 00000000..5d3bed46 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/ConditionalPreferenceCategory.java @@ -0,0 +1,22 @@ +package app.revanced.tiktok.settingsmenu.preference.categories; + +import android.content.Context; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; + +@SuppressWarnings("deprecation") +public abstract class ConditionalPreferenceCategory extends PreferenceCategory { + public ConditionalPreferenceCategory(Context context, PreferenceScreen screen) { + super(context); + + if (getSettingsStatus()) { + screen.addPreference(this); + addPreferences(context); + } + } + + public abstract boolean getSettingsStatus(); + + public abstract void addPreferences(Context context); +} + diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/DownloadsPreferenceCategory.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/DownloadsPreferenceCategory.java new file mode 100644 index 00000000..40f73d64 --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/DownloadsPreferenceCategory.java @@ -0,0 +1,35 @@ +package app.revanced.tiktok.settingsmenu.preference.categories; + +import android.content.Context; +import android.preference.PreferenceScreen; +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.settingsmenu.SettingsStatus; +import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference; +import app.revanced.tiktok.settingsmenu.preference.TogglePreference; + +@SuppressWarnings("deprecation") +public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory { + public DownloadsPreferenceCategory(Context context, PreferenceScreen screen) { + super(context, screen); + setTitle("Downloads"); + } + + @Override + public boolean getSettingsStatus() { + return SettingsStatus.downloadEnabled; + } + + @Override + public void addPreferences(Context context) { + addPreference(new DownloadPathPreference( + context, + "Download path", + SettingsEnum.DOWNLOAD_PATH + )); + addPreference(new TogglePreference( + context, + "Remove watermark", "", + SettingsEnum.DOWNLOAD_WATERMARK + )); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/FeedFilterPreferenceCategory.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/FeedFilterPreferenceCategory.java new file mode 100644 index 00000000..b5fbbdeb --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/FeedFilterPreferenceCategory.java @@ -0,0 +1,55 @@ +package app.revanced.tiktok.settingsmenu.preference.categories; + +import android.content.Context; +import android.preference.PreferenceScreen; +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.settingsmenu.SettingsStatus; +import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference; +import app.revanced.tiktok.settingsmenu.preference.TogglePreference; + +@SuppressWarnings("deprecation") +public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory { + public FeedFilterPreferenceCategory(Context context, PreferenceScreen screen) { + super(context, screen); + setTitle("Feed filter"); + } + + @Override + public boolean getSettingsStatus() { + return SettingsStatus.feedFilterEnabled; + } + + @Override + public void addPreferences(Context context) { + addPreference(new TogglePreference( + context, + "Remove feed ads", "Remove ads from feed.", + SettingsEnum.REMOVE_ADS + )); + addPreference(new TogglePreference( + context, + "Hide livestreams", "Hide livestreams from feed.", + SettingsEnum.HIDE_LIVE + )); + addPreference(new TogglePreference( + context, + "Hide story", "Hide story from feed.", + SettingsEnum.HIDE_STORY + )); + addPreference(new TogglePreference( + context, + "Hide image video", "Hide image video from feed.", + SettingsEnum.HIDE_IMAGE + )); + addPreference(new RangeValuePreference( + context, + "Min/Max views", "The minimum or maximum views of a video to show.", + SettingsEnum.MIN_MAX_VIEWS + )); + addPreference(new RangeValuePreference( + context, + "Min/Max likes", "The minimum or maximum likes of a video to show.", + SettingsEnum.MIN_MAX_LIKES + )); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/IntegrationsPreferenceCategory.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/IntegrationsPreferenceCategory.java new file mode 100644 index 00000000..9fcb450f --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/IntegrationsPreferenceCategory.java @@ -0,0 +1,28 @@ +package app.revanced.tiktok.settingsmenu.preference.categories; + +import android.content.Context; +import android.preference.PreferenceScreen; +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.settingsmenu.preference.TogglePreference; + +@SuppressWarnings("deprecation") +public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory { + public IntegrationsPreferenceCategory(Context context, PreferenceScreen screen) { + super(context, screen); + setTitle("Integrations"); + } + + @Override + public boolean getSettingsStatus() { + return true; + } + + @Override + public void addPreferences(Context context) { + addPreference(new TogglePreference(context, + "Enable debug log", + "Show integration debug log.", + SettingsEnum.DEBUG + )); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/SimSpoofPreferenceCategory.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/SimSpoofPreferenceCategory.java new file mode 100644 index 00000000..86f82a1d --- /dev/null +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/preference/categories/SimSpoofPreferenceCategory.java @@ -0,0 +1,47 @@ +package app.revanced.tiktok.settingsmenu.preference.categories; + +import android.content.Context; +import android.preference.PreferenceScreen; +import app.revanced.tiktok.settings.SettingsEnum; +import app.revanced.tiktok.settingsmenu.SettingsStatus; +import app.revanced.tiktok.settingsmenu.preference.InputTextPreference; +import app.revanced.tiktok.settingsmenu.preference.TogglePreference; + +@SuppressWarnings("deprecation") +public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory { + public SimSpoofPreferenceCategory(Context context, PreferenceScreen screen) { + super(context, screen); + setTitle("Bypass regional restriction"); + } + + + @Override + public boolean getSettingsStatus() { + return SettingsStatus.simSpoofEnabled; + } + + @Override + public void addPreferences(Context context) { + addPreference(new TogglePreference( + context, + "Fake sim card info", + "Bypass regional restriction by fake sim card information.", + SettingsEnum.SIM_SPOOF + )); + addPreference(new InputTextPreference( + context, + "Country ISO", "us, uk, jp, ...", + SettingsEnum.SIM_SPOOF_ISO + )); + addPreference(new InputTextPreference( + context, + "Operator mcc+mnc", "mcc+mnc", + SettingsEnum.SIMSPOOF_MCCMNC + )); + addPreference(new InputTextPreference( + context, + "Operator name", "Name of the operator.", + SettingsEnum.SIMSPOOF_OP_NAME + )); + } +} diff --git a/app/src/main/java/app/revanced/tiktok/spoof/sim/SpoofSimPatch.java b/app/src/main/java/app/revanced/tiktok/spoof/sim/SpoofSimPatch.java index 361ac428..799ec95c 100644 --- a/app/src/main/java/app/revanced/tiktok/spoof/sim/SpoofSimPatch.java +++ b/app/src/main/java/app/revanced/tiktok/spoof/sim/SpoofSimPatch.java @@ -4,11 +4,11 @@ import app.revanced.tiktok.settings.SettingsEnum; public class SpoofSimPatch { public static boolean isEnable() { - return SettingsEnum.TIK_SIMSPOOF.getBoolean(); + return SettingsEnum.SIM_SPOOF.getBoolean(); } public static String getCountryIso(String value) { if (isEnable()) { - return SettingsEnum.TIK_SIMSPOOF_ISO.getString(); + return SettingsEnum.SIM_SPOOF_ISO.getString(); } else { return value; } @@ -16,14 +16,14 @@ public class SpoofSimPatch { } public static String getOperator(String value) { if (isEnable()) { - return SettingsEnum.TIK_SIMSPOOF_MCCMNC.getString(); + return SettingsEnum.SIMSPOOF_MCCMNC.getString(); } else { return value; } } public static String getOperatorName(String value) { if (isEnable()) { - return SettingsEnum.TIK_SIMSPOOF_OP_NAME.getString(); + return SettingsEnum.SIMSPOOF_OP_NAME.getString(); } else { return value; } diff --git a/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java b/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java index c0dab428..d8d9a580 100644 --- a/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java +++ b/app/src/main/java/app/revanced/tiktok/utils/LogHelper.java @@ -10,7 +10,7 @@ import app.revanced.tiktok.settings.SettingsEnum; public class LogHelper { public static void debug(Class clazz, String message) { - if (SettingsEnum.TIK_DEBUG.getBoolean()) { + if (SettingsEnum.DEBUG.getBoolean()) { Log.d("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message); } } diff --git a/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java b/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java index 7ac089ff..1577fd61 100644 --- a/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java +++ b/app/src/main/java/app/revanced/tiktok/utils/ReVancedUtils.java @@ -1,13 +1,14 @@ package app.revanced.tiktok.utils; +import android.annotation.SuppressLint; import android.content.Context; +import app.revanced.tiktok.settings.SettingsEnum; public class ReVancedUtils { - //Used by TiktokIntegrations patch + @SuppressLint("StaticFieldLeak") public static Context context; - //Used by TiktokIntegrations patch public static Context getAppContext() { if (context != null) { return context; @@ -15,4 +16,23 @@ public class ReVancedUtils { LogHelper.printException(ReVancedUtils.class, "Context is null!"); return null; } + + public static long[] parseMinMax(SettingsEnum setting) { + if (setting.returnType == SettingsEnum.ReturnType.STRING) { + final String[] minMax = setting.getString().split("-"); + + if (minMax.length == 2) + try { + final long min = Long.parseLong(minMax[0]); + final long max = Long.parseLong(minMax[1]); + + if (min <= max && min >= 0) return new long[]{min, max}; + + } catch (NumberFormatException ignored) { + } + } + + setting.saveValue("0-" + Long.MAX_VALUE); + return new long[]{0L, Long.MAX_VALUE}; + } } \ No newline at end of file diff --git a/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java b/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java index 82f0fc5d..e1ea9af6 100644 --- a/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java +++ b/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java @@ -3,16 +3,34 @@ package com.ss.android.ugc.aweme.feed.model; //Dummy class public class Aweme { public boolean isAd() { - return true; - } - public boolean isLive() { - return true; - } - public boolean isLiveReplay() { - return true; - } - public boolean isWithPromotionalMusic() { - return true; + throw new UnsupportedOperationException("Stub"); } + public boolean isLive() { + throw new UnsupportedOperationException("Stub"); + } + + public boolean isLiveReplay() { + throw new UnsupportedOperationException("Stub"); + } + + public boolean isWithPromotionalMusic() { + throw new UnsupportedOperationException("Stub"); + } + + public boolean getIsTikTokStory() { + throw new UnsupportedOperationException("Stub"); + } + + public boolean isImage() { + throw new UnsupportedOperationException("Stub"); + } + + public boolean isPhotoMode() { + throw new UnsupportedOperationException("Stub"); + } + + public AwemeStatistics getStatistics() { + throw new UnsupportedOperationException("Stub"); + } } diff --git a/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/AwemeStatistics.java b/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/AwemeStatistics.java new file mode 100644 index 00000000..a9123e67 --- /dev/null +++ b/dummy/src/main/java/com/ss/android/ugc/aweme/feed/model/AwemeStatistics.java @@ -0,0 +1,10 @@ +package com.ss.android.ugc.aweme.feed.model; + +public class AwemeStatistics { + public long getPlayCount() { + throw new UnsupportedOperationException("Stub"); + } + public long getDiggCount() { + throw new UnsupportedOperationException("Stub"); + } +} From cf76cbe72f4649f45a542d0e3b586685b1dc97d4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 2 Aug 2023 12:12:32 +0000 Subject: [PATCH 16/16] chore(release): 0.115.0-dev.7 [skip ci] # [0.115.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.6...v0.115.0-dev.7) (2023-08-02) ### Features * **Tiktok - Feed filter:** Add more filters ([#445](https://github.com/ReVanced/revanced-integrations/issues/445)) ([c16c038](https://github.com/ReVanced/revanced-integrations/commit/c16c038396bde365ce1eec7b385eeb1f485efe99)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdd1b7e..9c6e09c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.115.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.6...v0.115.0-dev.7) (2023-08-02) + + +### Features + +* **Tiktok - Feed filter:** Add more filters ([#445](https://github.com/ReVanced/revanced-integrations/issues/445)) ([c16c038](https://github.com/ReVanced/revanced-integrations/commit/c16c038396bde365ce1eec7b385eeb1f485efe99)) + # [0.115.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.5...v0.115.0-dev.6) (2023-08-02) diff --git a/gradle.properties b/gradle.properties index cf2c465d..59c186c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.115.0-dev.6 +version = 0.115.0-dev.7