mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-12-03 17:22:54 +01:00
chore: Merge branch dev
to main
(#499)
This commit is contained in:
commit
bb9b35554f
56
CHANGELOG.md
56
CHANGELOG.md
@ -1,3 +1,59 @@
|
||||
# [0.120.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0-dev.5...v0.120.0-dev.6) (2023-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add `Announcements` patch ([#503](https://github.com/ReVanced/revanced-integrations/issues/503)) ([59687f1](https://github.com/ReVanced/revanced-integrations/commit/59687f1a39768ab71b2680a5c49df5aaae0d3b4c))
|
||||
|
||||
# [0.120.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0-dev.4...v0.120.0-dev.5) (2023-10-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add `Spoof device dimensions` patch ([16f1163](https://github.com/ReVanced/revanced-integrations/commit/16f1163a346fef0a87ca9384c9bf6aea977dc8fb))
|
||||
|
||||
# [0.120.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0-dev.3...v0.120.0-dev.4) (2023-10-17)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **YouTube:** Reduce memory requirement for prefix tree searching ([#501](https://github.com/ReVanced/revanced-integrations/issues/501)) ([f5add51](https://github.com/ReVanced/revanced-integrations/commit/f5add51fa7eb620a6edd1b27f02d38618f144480))
|
||||
|
||||
# [0.120.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0-dev.2...v0.120.0-dev.3) (2023-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Old video quality menu:** Fix toast error on tablet devices ([#500](https://github.com/ReVanced/revanced-integrations/issues/500)) ([d3eba27](https://github.com/ReVanced/revanced-integrations/commit/d3eba27c909e519eaeda072c1f995a2284007749))
|
||||
|
||||
# [0.120.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.120.0-dev.1...v0.120.0-dev.2) (2023-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Minimized playback:** Fix pip incorrectly showing if app is minimized immediately after opening a Short ([7d02774](https://github.com/ReVanced/revanced-integrations/commit/7d02774ea192510e692e90ae55a86e25ee321926))
|
||||
|
||||
# [0.120.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.119.3-dev.2...v0.120.0-dev.1) (2023-10-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Theme:** Disable gradient loading screen ([fd09e46](https://github.com/ReVanced/revanced-integrations/commit/fd09e46d01c820632cfe440dac34f5cd957e793d))
|
||||
|
||||
## [0.119.3-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.119.3-dev.1...v0.119.3-dev.2) (2023-10-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Layout components:** Exempt expandable chips from exceptions ([#498](https://github.com/ReVanced/revanced-integrations/issues/498)) ([6f79746](https://github.com/ReVanced/revanced-integrations/commit/6f79746d788f196f3aa63b8e7c24b7f15ecd3f50))
|
||||
|
||||
## [0.119.3-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.119.2...v0.119.3-dev.1) (2023-10-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Hide new channel watermark component ([9670bd3](https://github.com/ReVanced/revanced-integrations/commit/9670bd305b3b9bbbc900af3b64152aaac125ec14))
|
||||
|
||||
## [0.119.2](https://github.com/ReVanced/revanced-integrations/compare/v0.119.1...v0.119.2) (2023-10-12)
|
||||
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
package app.revanced.integrations.patches;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class BrandingWaterMarkPatch {
|
||||
|
||||
// Used by: app.revanced.patches.youtube.layout.watermark.patch.HideWatermarkPatch
|
||||
public static boolean isBrandingWatermarkShown() {
|
||||
return SettingsEnum.HIDE_VIDEO_WATERMARK.getBoolean() == false;
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import app.revanced.integrations.shared.PlayerType;
|
||||
public class MinimizedPlaybackPatch {
|
||||
|
||||
public static boolean isPlaybackNotShort() {
|
||||
return !PlayerType.getCurrent().isNoneOrHidden();
|
||||
return !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
|
||||
}
|
||||
|
||||
public static boolean overrideMinimizedPlaybackAvailable() {
|
||||
|
@ -0,0 +1,151 @@
|
||||
package app.revanced.integrations.patches.announcements;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import app.revanced.integrations.patches.announcements.requests.AnnouncementsRoutes;
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static app.revanced.integrations.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT;
|
||||
|
||||
public final class AnnouncementsPatch {
|
||||
private final static String CONSUMER = getOrSetConsumer();
|
||||
|
||||
private AnnouncementsPatch() {
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void showAnnouncement(final Activity context) {
|
||||
if (!SettingsEnum.ANNOUNCEMENTS.getBoolean()) return;
|
||||
|
||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT, CONSUMER);
|
||||
|
||||
LogHelper.printDebug(() -> "Get latest announcement route connection url: " + connection.getURL().toString());
|
||||
|
||||
try {
|
||||
// Do not show the announcement if the request failed.
|
||||
if (connection.getResponseCode() != 200) {
|
||||
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return;
|
||||
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
|
||||
ReVancedUtils.showToastLong("Failed to get announcement");
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
final var message = "Failed connecting to announcements provider";
|
||||
|
||||
LogHelper.printException(() -> message, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
||||
|
||||
// Do not show the announcement if it is older or the same as the last one.
|
||||
final byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes);
|
||||
if (hash.equals(SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString())) return;
|
||||
|
||||
// Parse the announcement. Fall-back to raw string if it fails.
|
||||
String title;
|
||||
String message;
|
||||
Level level = Level.INFO;
|
||||
try {
|
||||
final var announcement = new JSONObject(jsonString);
|
||||
|
||||
title = announcement.getString("title");
|
||||
message = announcement.getJSONObject("content").getString("message");
|
||||
|
||||
if (!announcement.isNull("level")) level = Level.fromInt(announcement.getInt("level"));
|
||||
} catch (Throwable ex) {
|
||||
LogHelper.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
|
||||
|
||||
title = "Announcement";
|
||||
message = jsonString;
|
||||
}
|
||||
|
||||
final var finalTitle = title;
|
||||
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
||||
final Level finalLevel = level;
|
||||
|
||||
ReVancedUtils.runOnMainThread(() -> {
|
||||
// Show the announcement.
|
||||
var alertDialog = new android.app.AlertDialog.Builder(context)
|
||||
.setTitle(finalTitle)
|
||||
.setMessage(finalMessage)
|
||||
.setIcon(finalLevel.icon)
|
||||
.setPositiveButton("Ok", (dialog, which) -> {
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue(hash);
|
||||
dialog.dismiss();
|
||||
}).setNegativeButton("Dismiss", (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
|
||||
// Make links clickable.
|
||||
((TextView)alertDialog.findViewById(android.R.id.message))
|
||||
.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
final var message = "Failed to get announcement";
|
||||
|
||||
LogHelper.printException(() -> message, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the last announcement hash if it is not empty.
|
||||
*
|
||||
* @return true if the last announcement hash was empty.
|
||||
*/
|
||||
private static boolean emptyLastAnnouncementHash() {
|
||||
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return true;
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getOrSetConsumer() {
|
||||
final var consumer = SettingsEnum.ANNOUNCEMENT_CONSUMER.getString();
|
||||
if (!consumer.isEmpty()) return consumer;
|
||||
|
||||
final var uuid = UUID.randomUUID().toString();
|
||||
SettingsEnum.ANNOUNCEMENT_CONSUMER.saveValue(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// TODO: Use better icons.
|
||||
private enum Level {
|
||||
INFO(android.R.drawable.ic_dialog_info),
|
||||
WARNING(android.R.drawable.ic_dialog_alert),
|
||||
SEVERE(android.R.drawable.ic_dialog_alert);
|
||||
|
||||
public final int icon;
|
||||
|
||||
Level(int icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public static Level fromInt(int value) {
|
||||
return values()[Math.min(value, values().length - 1)];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.integrations.patches.announcements.requests;
|
||||
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import static app.revanced.integrations.requests.Route.Method.GET;
|
||||
|
||||
public class AnnouncementsRoutes {
|
||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v2";
|
||||
|
||||
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?consumer={consumer}");
|
||||
|
||||
private AnnouncementsRoutes() {
|
||||
}
|
||||
|
||||
public static HttpURLConnection getAnnouncementsConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(ANNOUNCEMENTS_PROVIDER, route, params);
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
private final StringFilterGroup searchResultShelfHeader;
|
||||
private final StringFilterGroup inFeedSurvey;
|
||||
private final StringFilterGroup notifyMe;
|
||||
private final StringFilterGroup expandableMetadata;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public LayoutComponentsFilter() {
|
||||
@ -114,7 +115,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"official_card"
|
||||
);
|
||||
|
||||
final var expandableMetadata = new StringFilterGroup(
|
||||
expandableMetadata = new StringFilterGroup(
|
||||
SettingsEnum.HIDE_EXPANDABLE_CHIP,
|
||||
"inline_expander"
|
||||
);
|
||||
@ -175,11 +176,17 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"chips_shelf"
|
||||
);
|
||||
|
||||
final var channelWatermark = new StringFilterGroup(
|
||||
SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK,
|
||||
"featured_channel_watermark_overlay"
|
||||
);
|
||||
|
||||
this.pathFilterGroupList.addAll(
|
||||
channelBar,
|
||||
communityPosts,
|
||||
paidContent,
|
||||
latestPosts,
|
||||
channelWatermark,
|
||||
communityGuidelines,
|
||||
quickActions,
|
||||
expandableMetadata,
|
||||
@ -211,7 +218,10 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||
|
||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey) return true;
|
||||
// The groups are excluded from the filter due to the exceptions list below.
|
||||
// Filter them separately here.
|
||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||
|
||||
if (matchedGroup != custom && exceptions.matches(path))
|
||||
return false; // Exceptions are not filtered.
|
||||
@ -225,7 +235,6 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Called from a different place then the other filters.
|
||||
*/
|
||||
public static boolean filterMixPlaylists(final byte[] bytes) {
|
||||
@ -236,4 +245,8 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
return isMixPlaylistFiltered;
|
||||
}
|
||||
|
||||
public static boolean showWatermark() {
|
||||
return !SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK.getBoolean();
|
||||
}
|
||||
}
|
||||
|
@ -425,15 +425,15 @@ public final class LithoFilterPatch {
|
||||
|
||||
static {
|
||||
for (Filter filter : filters) {
|
||||
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
|
||||
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList);
|
||||
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
|
||||
}
|
||||
|
||||
LogHelper.printDebug(() -> "Using: "
|
||||
+ pathSearchTree.numberOfPatterns() + " path filters"
|
||||
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB), "
|
||||
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
|
||||
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB)");
|
||||
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), "
|
||||
+ pathSearchTree.numberOfPatterns() + " path filters"
|
||||
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
|
||||
}
|
||||
|
||||
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
|
||||
|
@ -27,10 +27,13 @@ public final class OldVideoQualityMenuPatch {
|
||||
// Check if the current view is the quality menu.
|
||||
if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) {
|
||||
VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false;
|
||||
((ViewGroup) recyclerView.getParent().getParent().getParent()).setVisibility(View.GONE);
|
||||
|
||||
// Click the "Advanced" quality menu to show the "old" quality menu.
|
||||
((ViewGroup) recyclerView.getChildAt(0)).getChildAt(3).performClick();
|
||||
((ViewGroup) recyclerView.getParent().getParent().getParent()).setVisibility(View.GONE);
|
||||
View advancedQualityView = ((ViewGroup) recyclerView.getChildAt(0)).getChildAt(3);
|
||||
if (advancedQualityView != null) {
|
||||
// Click the "Advanced" quality menu to show the "old" quality menu.
|
||||
advancedQualityView.performClick();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "onFlyoutMenuCreate failure", ex);
|
||||
|
@ -0,0 +1,14 @@
|
||||
package app.revanced.integrations.patches.spoof;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class SpoofDeviceDimensionsPatch {
|
||||
private static final boolean SPOOF = SettingsEnum.SPOOF_DEVICE_DIMENSIONS.getBoolean();
|
||||
public static int getMinHeightOrWidth(int minHeightOrWidth) {
|
||||
return SPOOF ? 64 : minHeightOrWidth;
|
||||
}
|
||||
|
||||
public static int getMaxHeightOrWidth(int maxHeightOrWidth) {
|
||||
return SPOOF ? 4096 : maxHeightOrWidth;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package app.revanced.integrations.patches.spoof.requests;
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -75,7 +76,12 @@ final class PlayerRoutes {
|
||||
/** @noinspection SameParameterValue*/
|
||||
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route) throws IOException {
|
||||
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);
|
||||
connection.setRequestProperty("User-Agent", "com.google.android.youtube/18.37.36 (Linux; U; Android 12; GB) gzip");
|
||||
|
||||
connection.setRequestProperty(
|
||||
"User-Agent", "com.google.android.youtube/" +
|
||||
ReVancedUtils.getVersionName() +
|
||||
" (Linux; U; Android 12; GB) gzip"
|
||||
);
|
||||
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
package app.revanced.integrations.patches.theme;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.utils.ThemeHelper;
|
||||
|
||||
public class ThemeLithoComponentsPatch {
|
||||
public class ThemePatch {
|
||||
// color constants used in relation with litho components
|
||||
private static final int[] WHITE_VALUES = {
|
||||
-1, // comments chip background
|
||||
@ -40,6 +41,10 @@ public class ThemeLithoComponentsPatch {
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
public static boolean gradientLoadingScreenEnabled() {
|
||||
return SettingsEnum.GRADIENT_LOADING_SCREEN.getBoolean();
|
||||
}
|
||||
|
||||
private static int getBlackColor() {
|
||||
if (blackColor == 0) blackColor = ReVancedUtils.getResourceColor("yt_black1");
|
||||
return blackColor;
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.integrations.requests;
|
||||
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -23,7 +24,7 @@ public class Requester {
|
||||
String url = apiUrl + route.getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";revanced");
|
||||
connection.setRequestProperty("User-Agent", System.getProperty("http.agent") + "; ReVanced/" + ReVancedUtils.getVersionName());
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ import app.revanced.integrations.utils.StringRef;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.*;
|
||||
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
||||
@ -123,6 +120,7 @@ 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_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, 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"),
|
||||
@ -130,6 +128,7 @@ public enum SettingsEnum {
|
||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
||||
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
|
||||
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
||||
GRADIENT_LOADING_SCREEN("revanced_gradient_loading_screen", BOOLEAN, FALSE),
|
||||
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
|
||||
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
|
||||
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
|
||||
@ -175,7 +174,11 @@ public enum SettingsEnum {
|
||||
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
||||
SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false,
|
||||
parents(SPOOF_SIGNATURE)),
|
||||
SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true),
|
||||
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
|
||||
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
|
||||
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
|
||||
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
|
||||
|
||||
// Swipe controls
|
||||
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
||||
@ -374,6 +377,8 @@ public enum SettingsEnum {
|
||||
|
||||
// region Migration
|
||||
|
||||
migrateOldSettingToNew(HIDE_VIDEO_WATERMARK, HIDE_VIDEO_CHANNEL_WATERMARK);
|
||||
|
||||
// Do _not_ delete this SB private user id migration property until sometime in 2024.
|
||||
// This is the only setting that cannot be reconfigured if lost,
|
||||
// and more time should be given for users who rarely upgrade.
|
||||
@ -550,6 +555,7 @@ public enum SettingsEnum {
|
||||
private boolean includeWithImportExport() {
|
||||
switch (this) {
|
||||
case RYD_USER_ID: // Not useful to export, no reason to include it.
|
||||
case ANNOUNCEMENT_CONSUMER: // Not useful to export, no reason to include it.
|
||||
case SB_LAST_VIP_CHECK:
|
||||
case SB_HIDE_EXPORT_WARNING:
|
||||
case SB_SEEN_GUIDELINES:
|
||||
|
@ -8,9 +8,17 @@ import java.util.Objects;
|
||||
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||
|
||||
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||
TrieNode<byte[]> createNode() {
|
||||
return new ByteTrieNode();
|
||||
ByteTrieNode() {
|
||||
super();
|
||||
}
|
||||
ByteTrieNode(char nodeCharacterValue) {
|
||||
super(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
TrieNode<byte[]> createNode(char nodeCharacterValue) {
|
||||
return new ByteTrieNode(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
char getCharValue(byte[] text, int index) {
|
||||
return (char) text[index];
|
||||
}
|
||||
|
@ -2,8 +2,11 @@ package app.revanced.integrations.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
@ -25,9 +28,36 @@ public class ReVancedUtils {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static Context context;
|
||||
|
||||
private static String versionName;
|
||||
|
||||
private ReVancedUtils() {
|
||||
} // utility class
|
||||
|
||||
public static String getVersionName() {
|
||||
if (versionName != null) return versionName;
|
||||
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
packageInfo = context.getPackageManager().getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(0)
|
||||
);
|
||||
else
|
||||
packageInfo = context.getPackageManager().getPackageInfo(
|
||||
packageName,
|
||||
0
|
||||
);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogHelper.printException(() -> "Failed to get package info", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return versionName = packageInfo.versionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its layout height and width to 1dp.
|
||||
*
|
||||
|
@ -11,9 +11,17 @@ import java.util.Objects;
|
||||
public final class StringTrieSearch extends TrieSearch<String> {
|
||||
|
||||
private static final class StringTrieNode extends TrieNode<String> {
|
||||
TrieNode<String> createNode() {
|
||||
return new StringTrieNode();
|
||||
StringTrieNode() {
|
||||
super();
|
||||
}
|
||||
StringTrieNode(char nodeCharacterValue) {
|
||||
super(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
TrieNode<String> createNode(char nodeValue) {
|
||||
return new StringTrieNode(nodeValue);
|
||||
}
|
||||
@Override
|
||||
char getCharValue(String text, int index) {
|
||||
return text.charAt(index);
|
||||
}
|
||||
|
@ -71,15 +71,31 @@ public abstract class TrieSearch<T> {
|
||||
}
|
||||
|
||||
static abstract class TrieNode<T> {
|
||||
/**
|
||||
* Dummy value used for root node. Value can be anything as it's never referenced.
|
||||
*/
|
||||
private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character.
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* How much to expand the children array when resizing.
|
||||
*/
|
||||
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
||||
private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
||||
|
||||
private static boolean isInvalidRange(char character) {
|
||||
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character this node represents.
|
||||
* This field is ignored for the root node (which does not represent any character).
|
||||
*/
|
||||
private final char nodeValue;
|
||||
|
||||
/**
|
||||
* A compressed graph path that represents the remaining pattern characters of a single child node.
|
||||
*
|
||||
@ -91,6 +107,24 @@ public abstract class TrieSearch<T> {
|
||||
|
||||
/**
|
||||
* All child nodes. Only present if no compressed leaf exist.
|
||||
*
|
||||
* Array is dynamically increased in size as needed,
|
||||
* and uses perfect hashing for the elements it contains.
|
||||
*
|
||||
* So if the array contains a given character,
|
||||
* the character will always map to the node with index: (character % arraySize).
|
||||
*
|
||||
* Elements not contained can collide with elements the array does contain,
|
||||
* so must compare the nodes character value.
|
||||
*
|
||||
* Alternatively this array could be a sorted and densely packed array,
|
||||
* and lookup is done using binary search.
|
||||
* That would save a small amount of memory because there's no null children entries,
|
||||
* but would give a worst case search of O(nlog(m)) where n is the number of
|
||||
* characters in the searched text and m is the maximum size of the sorted character arrays.
|
||||
* Using a hash table array always gives O(n) search time.
|
||||
* The memory usage here is very small (all Litho filters use ~10KB of memory),
|
||||
* so the more performant hash implementation is chosen.
|
||||
*/
|
||||
@Nullable
|
||||
private TrieNode<T>[] children;
|
||||
@ -101,6 +135,13 @@ public abstract class TrieSearch<T> {
|
||||
@Nullable
|
||||
private List<TriePatternMatchedCallback<T>> endOfPatternCallback;
|
||||
|
||||
TrieNode() {
|
||||
this.nodeValue = ROOT_NODE_CHARACTER_VALUE;
|
||||
}
|
||||
TrieNode(char nodeCharacterValue) {
|
||||
this.nodeValue = nodeCharacterValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern Pattern to add.
|
||||
* @param patternLength Length of the pattern.
|
||||
@ -121,7 +162,7 @@ public abstract class TrieSearch<T> {
|
||||
// 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];
|
||||
children = new TrieNode[1];
|
||||
TrieCompressedPath<T> temp = leaf;
|
||||
leaf = null;
|
||||
addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback);
|
||||
@ -130,19 +171,65 @@ public abstract class TrieSearch<T> {
|
||||
leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback);
|
||||
return;
|
||||
}
|
||||
char character = getCharValue(pattern, patternIndex);
|
||||
final 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<T> child = children[character];
|
||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||
TrieNode<T> child = children[arrayIndex];
|
||||
if (child == null) {
|
||||
child = createNode();
|
||||
children[character] = child;
|
||||
child = createNode(character);
|
||||
children[arrayIndex] = child;
|
||||
} else if (child.nodeValue != character) {
|
||||
// Hash collision. Resize the table until perfect hashing is found.
|
||||
child = createNode(character);
|
||||
expandChildArray(child);
|
||||
}
|
||||
child.addPattern(pattern, patternLength, patternIndex + 1, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the children table until all nodes hash to exactly one array index.
|
||||
* Worse case, this will resize the array to {@link #CHILDREN_ARRAY_MAX_SIZE} elements.
|
||||
*/
|
||||
private void expandChildArray(TrieNode<T> child) {
|
||||
int replacementArraySize = Objects.requireNonNull(children).length;
|
||||
while (true) {
|
||||
replacementArraySize += CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT;
|
||||
//noinspection unchecked
|
||||
TrieNode<T>[] replacement = new TrieNode[replacementArraySize];
|
||||
addNodeToArray(replacement, child);
|
||||
boolean collision = false;
|
||||
for (TrieNode<T> existingChild : children) {
|
||||
if (existingChild != null) {
|
||||
if (!addNodeToArray(replacement, existingChild)) {
|
||||
collision = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collision) {
|
||||
if (replacementArraySize > CHILDREN_ARRAY_MAX_SIZE) throw new IllegalStateException();
|
||||
continue;
|
||||
}
|
||||
children = replacement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean addNodeToArray(TrieNode<T>[] array, TrieNode<T> childToAdd) {
|
||||
final int insertIndex = hashIndexForTableSize(array.length, childToAdd.nodeValue);
|
||||
if (array[insertIndex] != null ) {
|
||||
return false; // Collision.
|
||||
}
|
||||
array[insertIndex] = childToAdd;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int hashIndexForTableSize(int arraySize, char nodeValue) {
|
||||
return (nodeValue - MIN_VALID_CHAR) % arraySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchText Text to search for patterns in.
|
||||
* @param searchTextLength Length of the search text.
|
||||
@ -170,18 +257,17 @@ public abstract class TrieSearch<T> {
|
||||
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);
|
||||
final 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<T> child = children[character];
|
||||
if (child == null) {
|
||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||
TrieNode<T> child = children[arrayIndex];
|
||||
if (child == null || child.nodeValue != character) {
|
||||
return false;
|
||||
}
|
||||
return child.matches(searchText, searchTextLength, searchTextIndex + 1,
|
||||
@ -194,7 +280,7 @@ public abstract class TrieSearch<T> {
|
||||
* @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.
|
||||
int numberOfPointers = 4; // Number of fields in this class.
|
||||
if (leaf != null) {
|
||||
numberOfPointers += 4; // Number of fields in leaf node.
|
||||
}
|
||||
@ -202,7 +288,7 @@ public abstract class TrieSearch<T> {
|
||||
numberOfPointers += endOfPatternCallback.size();
|
||||
}
|
||||
if (children != null) {
|
||||
numberOfPointers += NUMBER_OF_CHILDREN;
|
||||
numberOfPointers += children.length;
|
||||
for (TrieNode<T> child : children) {
|
||||
if (child != null) {
|
||||
numberOfPointers += child.estimatedNumberOfPointersUsed();
|
||||
@ -212,7 +298,7 @@ public abstract class TrieSearch<T> {
|
||||
return numberOfPointers;
|
||||
}
|
||||
|
||||
abstract TrieNode<T> createNode();
|
||||
abstract TrieNode<T> createNode(char nodeValue);
|
||||
abstract char getCharValue(T text, int index);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
android.useAndroidX = true
|
||||
version = 0.119.2
|
||||
version = 0.120.0-dev.6
|
||||
|
Loading…
Reference in New Issue
Block a user