From 53f594a8329824549ffff4f6965c1e43ebecbfc8 Mon Sep 17 00:00:00 2001 From: swakwork Date: Mon, 15 Sep 2025 21:15:17 +0530 Subject: [PATCH 01/61] Migration v21: initial --- .gitignore | 6 +- build.gradle.kts | 119 +- extensions/proguard-rules.pro | 10 + extensions/shared/build.gradle.kts | 4 + extensions/shared/library/build.gradle.kts | 22 + .../extension/shared/GmsCoreSupport.java | 256 + .../app/revanced/extension/shared/Logger.java | 214 + .../revanced/extension/shared/StringRef.java | 122 + .../app/revanced/extension/shared/Utils.java | 1540 +++++ .../extension/shared/checks/Check.java | 212 + .../shared/checks/CheckEnvironmentPatch.java | 341 + .../extension/shared/checks/PatchInfo.java | 32 + .../fixes/redgifs/BaseFixRedgifsApiPatch.java | 71 + .../fixes/redgifs/RedgifsTokenManager.java | 94 + .../fixes/slink/BaseFixSLinksPatch.java | 208 + .../shared/fixes/slink/ResolveResult.java | 10 + .../extension/shared/requests/Requester.java | 145 + .../extension/shared/requests/Route.java | 66 + .../shared/settings/AppLanguage.java | 119 + .../shared/settings/BaseSettings.java | 39 + .../shared/settings/BooleanSetting.java | 81 + .../shared/settings/EnumSetting.java | 122 + .../shared/settings/FloatSetting.java | 67 + .../shared/settings/IntegerSetting.java | 67 + .../shared/settings/LongSetting.java | 67 + .../extension/shared/settings/Setting.java | 486 ++ .../shared/settings/StringSetting.java | 67 + .../AbstractPreferenceFragment.java | 346 + .../preference/ColorPickerPreference.java | 449 ++ .../settings/preference/ColorPickerView.java | 500 ++ .../CustomDialogListPreference.java | 171 + .../preference/ImportExportPreference.java | 125 + .../settings/preference/LogBufferManager.java | 113 + .../preference/NoTitlePreferenceCategory.java | 58 + .../preference/ReVancedAboutPreference.java | 363 + .../ResettableEditTextPreference.java | 105 + .../preference/SharedPrefCategory.java | 223 + .../preference/SortedListPreference.java | 124 + .../extension/shared/spoof/ClientType.java | 259 + .../shared/spoof/SpoofVideoStreamsPatch.java | 267 + .../shared/spoof/requests/PlayerRoutes.java | 91 + .../spoof/requests/StreamingDataRequest.java | 259 + .../shared/src/main/AndroidManifest.xml | 1 + extensions/twitter/build.gradle.kts | 20 + extensions/twitter/library/build.gradle.kts | 24 + .../twitter/src/main/AndroidManifest.xml | 1 + .../app/revanced/extension/twitter/Pref.java | 392 ++ .../app/revanced/extension/twitter/Utils.java | 418 ++ .../extension/twitter/model/Debug.java | 125 + .../twitter/model/ExtMediaEntities.java | 66 + .../extension/twitter/model/Media.java | 28 + .../extension/twitter/model/Tweet.java | 128 + .../extension/twitter/model/TweetInfo.java | 36 + .../extension/twitter/model/Video.java | 67 + .../twitter/patches/DatabasePatch.java | 141 + .../twitter/patches/DownloadPatch.java | 130 + .../twitter/patches/FeatureSwitchPatch.java | 80 + .../twitter/patches/TimelineEntry.java | 119 + .../extension/twitter/patches/TweetInfo.java | 68 + .../twitter/patches/customise/Customise.java | 198 + .../links/HandleCustomDeepLinksPatch.java | 37 + .../patches/links/UnshortenUrlsPatch.java | 28 + .../patches/loggers/ResponseLogger.java | 48 + .../nativeFeatures/NativeDownloader.java | 121 + .../emoji/CachedEmojiLoader.java | 52 + .../readerMode/ReaderModeFragment.java | 132 + .../readerMode/ReaderModeTemplate.java | 497 ++ .../readerMode/ReaderModeUtils.java | 233 + .../nativeFeatures/translator/Constants.java | 37 + .../nativeFeatures/translator/DialogBox.java | 109 + .../translator/NativeTranslator.java | 82 + .../translator/providers/GTranslate.java | 106 + .../translator/providers/GTranslateV2.java | 101 + .../translator/providers/Translate.java | 23 + .../twitter/patches/tweet/TweetInfoAPI.java | 104 + .../twitter/settings/ActivityHook.java | 141 + .../extension/twitter/settings/DeepLink.java | 88 + .../twitter/settings/ScreenBuilder.java | 1065 +++ .../extension/twitter/settings/Settings.java | 119 + .../twitter/settings/SettingsStatus.java | 377 ++ .../settings/featureflags/FeatureFlag.java | 31 + .../featureflags/FeatureFlagAdapter.java | 109 + .../FeatureFlagSearchAdapter.java | 79 + .../featureflags/FeatureFlagsFragment.java | 210 + .../fragments/BackupPrefFragment.java | 81 + .../settings/fragments/PageFragment.java | 67 + .../fragments/RestorePrefFragment.java | 99 + .../fragments/SettingsAboutFragment.java | 200 + .../settings/fragments/SettingsFragment.java | 57 + .../twitter/settings/widgets/ButtonPref.java | 128 + .../settings/widgets/EditTextPref.java | 50 + .../twitter/settings/widgets/Helper.java | 116 + .../twitter/settings/widgets/ListPref.java | 81 + .../settings/widgets/MultiSelectListPref.java | 69 + .../settings/widgets/PikoSettingsButton.java | 47 + .../twitter/settings/widgets/SwitchPref.java | 41 + extensions/twitter/stub/build.gradle.kts | 17 + .../twitter/stub/src/main/AndroidManifest.xml | 2 + .../support/constraint/ConstraintLayout.java | 20 + .../support/v7/widget/RecyclerView.java | 19 + .../java/androidx/emoji/text/EmojiCompat.java | 41 + .../androidx/emoji/text/MetadataRepo.java | 21 + .../emoji/text/MetadataRepoLoader.java | 10 + .../revanced/integrations/BuildConfig.java | 9 + .../AdPersonalizationActivity.java | 6 + ...tadataScrollableButtonContainerLayout.java | 25 + .../rendering/ui/pivotbar/PivotBar.java | 10 + .../FloatingActionButton.java | 10 + .../android/material/tabs/TabLayout$g.java | 5 + .../java/com/reddit/domain/model/ILink.java | 7 + .../android/ugc/aweme/feed/model/Aweme.java | 36 + .../ugc/aweme/feed/model/AwemeStatistics.java | 10 + .../ugc/aweme/feed/model/FeedItemList.java | 8 + .../tumblr/rumblr/model/TimelineObject.java | 8 + .../rumblr/model/TimelineObjectType.java | 4 + .../com/tumblr/rumblr/model/Timelineable.java | 5 + .../api/model/json/core/JsonApiTweet.java | 7 + .../model/json/card/JsonBindingValue.java | 7 + .../model/json/card/JsonCardInstanceData.java | 10 + .../json/core/JsonSensitiveMediaWarning.java | 7 + .../JsonTweetQuickPromoteEligibility.java | 4 + .../model/json/core/JsonUrlEntity.java | 8 + .../json/search/JsonTypeaheadResponse.java | 10 + .../json/timeline/urt/JsonTimelineEntry.java | 7 + .../timeline/urt/JsonTimelineModuleItem.java | 5 + .../LegacyTwitterPreferenceCategory.java | 11 + .../com/twitter/util/user/UserIdentifier.java | 12 + .../java/org/chromium/net/UrlRequest.java | 4 + .../org/chromium/net/UrlResponseInfo.java | 12 + .../chromium/net/impl/CronetUrlRequest.java | 11 + .../settings/menu/SettingsMenuGroup.java | 14 + .../android/settings/SettingsActivity.java | 5 + .../chat/util/ClickableUsernameSpan.java | 9 + gradle.properties | 3 +- gradle/libs.versions.toml | 38 +- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 6 +- gradlew | 17 +- gradlew.bat | 20 +- package-lock.json | 5915 +++++++++-------- package.json | 5 +- patches/api/patches.api | 707 ++ patches/build.gradle.kts | 62 + .../twitter/ads/timelineEntryHook/HideAds.kt | 18 + .../twitter/ads/timelineEntryHook/HideCTJ.kt | 18 + .../twitter/ads/timelineEntryHook/HideCTS.kt | 18 + .../timelineEntryHook/HideDetailedPosts.kt | 19 + .../ads/timelineEntryHook/HideGoogleAds.kt | 22 + .../ads/timelineEntryHook/HideMainEvent.kt | 19 + .../HidePinnedByFollowers.kt | 19 + .../timelineEntryHook/HidePremiumPrompt.kt | 19 + .../ads/timelineEntryHook/HideRevistBmk.kt | 19 + .../timelineEntryHook/HideSuperHeroEvent.kt | 20 + .../ads/timelineEntryHook/HideTodaysNews.kt | 18 + .../timelineEntryHook/HideTopPeopleSearch.kt | 19 + .../ads/timelineEntryHook/HideVideosForYou.kt | 19 + .../twitter/ads/timelineEntryHook/HideWTF.kt | 18 + .../TimelineEntryHookPatch.kt | 39 + .../TimelineModuleItemHookPatch.kt | 39 + .../ads/trends/HidePromotedTrendPatch.kt | 61 + .../featureFlag/DisableChirpFontPatch.kt | 22 + .../twitter/featureFlag/FeatureFlagPatch.kt | 45 + .../featureFlag/FeatureFlagResourcePatch.kt | 22 + .../HideBookmarkInTimelinePatch.kt | 22 + .../featureFlag/HideFABMenuButtonsPatch.kt | 22 + .../featureFlag/HideImmersivePlayer.kt | 24 + .../featureFlag/RemoveViewCountPatch.kt | 24 + .../fingerprints/CustomAdapterFingerprint.kt | 11 + .../fingerprints/FeatureFlagFingerprint.kt | 8 + .../FeatureFlagLoadFingerprint.kt | 11 + .../changedirectory/ChangeDownloadDirPatch.kt | 59 + .../downloads/copyMediaLink/CopyMediaLink.kt | 53 + .../ClearTrackingParamsPatch.kt | 36 + .../customDeeplinks/CustomDeepLinksPatch.kt | 149 + .../HandleCustomDeepLinksPatch.kt | 26 + ...UrlInterpreterActivityCreateFingerprint.kt | 13 + .../CustomSharingDomainPatch.kt | 41 + .../link/unshorten/NoShortenedUrlPatch.kt | 51 + .../responseLogging/ResponseLogging.kt | 43 + .../exploretabs/CustomiseExploreTabsPatch.kt | 47 + .../inlinebar/CustomiseInlineBarPatch.kt | 46 + .../customize/navbar/CustomiseNavBarPatch.kt | 73 + .../postFontSize/CustomizePostFontSize.kt | 41 + .../profiletabs/CustomiseProfileTabsPatch.kt | 93 + .../replySorting/DefaultReplySortingPatch.kt | 81 + .../searchtabs/CustomiseSearchTabsPatch.kt | 50 + .../sidebar/CustomiseSideBarPatch.kt | 54 + .../CustomiseTimelineTabsPatch.kt | 71 + .../CustomiseTypeAheadResponsePatch.kt | 45 + .../twitter/misc/extension/ExtensionPatch.kt | 5 + .../patches/twitter/misc/extension/Hooks.kt | 11 + .../patches/twitter/misc/fab/HideFABPatch.kt | 45 + .../HideRecommendedUsersPatch.kt | 57 + .../roundOffNumbers/RoundOffNumbersPatch.kt | 66 + .../selectabletext/SelectableTextPatch.kt | 34 + .../selectabletext/SelectableTextResource.kt | 21 + .../twitter/misc/settings/Fingerprints.kt | 34 + .../twitter/misc/settings/SettingsPatch.kt | 140 + .../misc/settings/SettingsResourcePatch.kt | 177 + .../twitter/models/ExtMediaEntityPatch.kt | 47 + .../patches/twitter/models/Fingerprints.kt | 80 + .../twitter/models/TweetEntityPatch.kt | 89 + .../twitter/models/TweetInfoEntityPatch.kt | 36 + .../timeline/banner/HideBannerPatch.kt | 54 + .../DeleteFromDatabasePatch.kt | 21 + .../DisableAutoScrollPatch.kt | 46 + .../EnableVidAutoAdvancePatch.kt | 57 + .../twitter/timeline/forceHD/ForceHDPatch.kt | 62 + .../hideCommunityBadge/HideCommunityBadge.kt | 55 + .../HideHiddenRepliesPatch.kt | 52 + .../hideNudgeButtons/HideNudgeButtonPatch.kt | 66 + .../hideSocialProof/HideSoialProofPatch.kt | 50 + .../timeline/live/HideLiveThreadsPatch.kt | 54 + .../RemovePremiumUpsellPatch.kt | 40 + .../ShowSensitiveMediaPatch.kt | 51 + .../showSourceLabel/ShowSourceLabel.kt | 71 + .../showpollresults/ShowPollResultsPatch.kt | 55 + .../timeline/tweetinfo/ForceTranslate.kt | 21 + .../timeline/tweetinfo/HideCommunityNotes.kt | 20 + .../timeline/tweetinfo/HidePromoteButton.kt | 21 + .../timeline/tweetinfo/TweetInfoHook.kt | 44 + .../main/kotlin/app/crimera/utils/Utils.kt | 54 + .../all/misc/resources/AddResourcesPatch.kt | 396 ++ .../all/misc/transformation/MethodCall.kt | 95 + .../TransformInstructionsPatch.kt | 49 + .../revanced/patches/shared/Fingerprints.kt | 11 + .../shared/misc/extension/Fingerprints.kt | 22 + .../misc/extension/SharedExtensionPatch.kt | 122 + .../shared/misc/hex/HexPatchBuilder.kt | 154 + .../misc/mapping/ResourceMappingPatch.kt | 41 +- .../shared/misc/settings/Fingerprints.kt | 23 + .../shared/misc/settings/SettingsPatch.kt | 154 + .../settings/preference/BasePreference.kt | 58 + .../preference/BasePreferenceScreen.kt | 100 + .../misc/settings/preference/InputType.kt | 9 + .../settings/preference/IntentPreference.kt | 57 + .../settings/preference/ListPreference.kt | 80 + .../preference/NonInteractivePreference.kt | 32 + .../settings/preference/PreferenceCategory.kt | 34 + .../preference/PreferenceScreenPreference.kt | 72 + .../misc/settings/preference/SummaryType.kt | 5 + .../settings/preference/SwitchPreference.kt | 32 + .../settings/preference/TextPreference.kt | 33 + .../shared/misc/string/ReplaceStringPatch.kt | 39 + .../kotlin/app/revanced/util/BytecodeUtils.kt | 1238 ++++ .../app/revanced/util/CrowdinPreprocessor.kt | 40 + .../kotlin/app/revanced/util/ResourceUtils.kt | 229 + .../main/kotlin/app/revanced/util/Utils.kt | 10 + .../revanced}/util/resource/ArrayResource.kt | 0 .../revanced}/util/resource/BaseResource.kt | 0 .../revanced/util/resource/StringResource.kt | 64 + .../drawable-hdpi/ic_stat_twitter.webp | Bin .../drawable-mdpi/ic_stat_twitter.webp | Bin .../drawable-xhdpi/ic_stat_twitter.webp | Bin .../drawable-xxhdpi/ic_stat_twitter.webp | Bin .../drawable-xxxhdpi/ic_stat_twitter.webp | Bin .../drawable/ic_vector_home.xml | 8 +- .../drawable/ic_vector_home_stroke.xml | 8 +- .../drawable/ic_vector_twitter.xml | 0 .../drawable/ic_vector_twitter_white.xml | 8 +- .../drawable/splash_screen_icon.xml | 0 .../mipmap-hdpi/ic_launcher_twitter.webp | Bin .../ic_launcher_twitter_foreground.webp | Bin .../ic_launcher_twitter_round.webp | Bin .../mipmap-mdpi/ic_launcher_twitter.webp | Bin .../ic_launcher_twitter_foreground.webp | Bin .../ic_launcher_twitter_round.webp | Bin .../mipmap-xhdpi/ic_launcher_twitter.webp | Bin .../ic_launcher_twitter_foreground.webp | Bin .../ic_launcher_twitter_round.webp | Bin .../mipmap-xxhdpi/fg_launcher_twitter.webp | Bin .../mipmap-xxhdpi/ic_launcher_twitter.webp | Bin .../ic_launcher_twitter_foreground.webp | Bin .../ic_launcher_twitter_round.webp | Bin .../mipmap-xxxhdpi/ic_launcher_twitter.webp | Bin .../ic_launcher_twitter_foreground.webp | Bin .../ic_launcher_twitter_round.webp | Bin .../settings/layout/feature_flags_view.xml | 0 .../twitter/settings/layout/item_row.xml | 0 .../twitter/settings/layout/search_dialog.xml | 0 .../settings/layout/search_item_row.xml | 0 .../twitter/settings/values-ar/strings.xml | 0 .../twitter/settings/values-es/strings.xml | 0 .../twitter/settings/values-fr/strings.xml | 340 +- .../twitter/settings/values-hi/strings.xml | 0 .../twitter/settings/values-in/strings.xml | 0 .../twitter/settings/values-it/strings.xml | 0 .../twitter/settings/values-ja/strings.xml | 0 .../twitter/settings/values-ko/strings.xml | 0 .../twitter/settings/values-pl/strings.xml | 0 .../settings/values-pt-rBR/strings.xml | 0 .../twitter/settings/values-ru/strings.xml | 0 .../twitter/settings/values-tr/strings.xml | 0 .../twitter/settings/values-uk/strings.xml | 0 .../twitter/settings/values-v21/arrays.xml | 0 .../twitter/settings/values-v21/strings.xml | 0 .../twitter/settings/values-vi/strings.xml | 0 .../settings/values-zh-rCN/strings.xml | 0 .../settings/values-zh-rTW/strings.xml | 0 .../twitter/settings/values/arrays.xml | 0 .../twitter/settings/values/strings.xml | 0 patches/stub/build.gradle.kts | 10 + .../stub/src/main/java/android/os/Build.java | 22 + settings.gradle.kts | 34 +- .../generator/JsonPatchesFileGenerator.kt | 49 - .../kotlin/app/revanced/generator/Main.kt | 12 - .../generator/PatchesFileGenerator.kt | 7 - .../integrations/BaseIntegrationsPatch.kt | 124 - .../IntegrationsUtilsFingerprint.kt | 10 - .../misc/dynamiccolor/DynamicColorPatch.kt | 160 - src/main/kotlin/app/util/PikoUtils.kt | 8 - src/main/kotlin/app/util/ResourceUtils.kt | 210 - .../app/util/resource/StringResource.kt | 41 - .../exportall/ExportAllActivitiesPatch.kt | 43 - .../all/appDowngrading/AppDowngradingPatch.kt | 21 - .../twitter/ads/timelineEntryHook/HideAds.kt | 22 - .../twitter/ads/timelineEntryHook/HideCTJ.kt | 22 - .../twitter/ads/timelineEntryHook/HideCTS.kt | 23 - .../timelineEntryHook/HideDetailedPosts.kt | 23 - .../ads/timelineEntryHook/HideGoogleAds.kt | 24 - .../ads/timelineEntryHook/HideMainEvent.kt | 23 - .../HidePinnedByFollowers.kt | 22 - .../timelineEntryHook/HidePremiumPrompt.kt | 22 - .../ads/timelineEntryHook/HideRevistBmk.kt | 22 - .../timelineEntryHook/HideSuperHeroEvent.kt | 23 - .../ads/timelineEntryHook/HideTodaysNews.kt | 22 - .../timelineEntryHook/HideTopPeopleSearch.kt | 23 - .../ads/timelineEntryHook/HideVideosForYou.kt | 23 - .../twitter/ads/timelineEntryHook/HideWTF.kt | 22 - .../TimelineEntryHookPatch.kt | 45 - .../TimelineModuleItemHookPatch.kt | 45 - .../ads/trends/HidePromotedTrendPatch.kt | 60 - .../HidePromotedTrendFingerprint.kt | 10 - .../featureFlag/DisableChirpFontPatch.kt | 28 - .../twitter/featureFlag/FeatureFlagPatch.kt | 52 - .../featureFlag/FeatureFlagResourcePatch.kt | 21 - .../HideBookmarkInTimelinePatch.kt | 29 - .../featureFlag/HideFABMenuButtonsPatch.kt | 28 - .../featureFlag/HideImmersivePlayer.kt | 28 - .../featureFlag/RemoveViewCountPatch.kt | 28 - .../fingerprints/CustomAdapterFingerprint.kt | 10 - .../fingerprints/FeatureFlagFingerprint.kt | 7 - .../FeatureFlagLoadFingerprint.kt | 20 - .../changedirectory/ChangeDownloadDirPatch.kt | 55 - .../downloads/copyMediaLink/CopyMediaLink.kt | 58 - .../ClearTrackingParamsPatch.kt | 30 - .../AddSessionTokenFingerprint.kt | 12 - .../customDeeplinks/CustomDeepLinksPatch.kt | 104 - .../HandleCustomDeepLinksPatch.kt | 24 - ...UrlInterpreterActivityCreateFingerprint.kt | 13 - .../CustomSharingDomainPatch.kt | 40 - .../CustomSharingDomainFingerprint.kt | 12 - .../link/unshorten/NoShortenedUrlPatch.kt | 47 - .../JsonObjectMapperFingerprint.kt | 10 - .../responseLogging/ResponseLogging.kt | 46 - .../BringBackTwitterResourcePatch.kt | 145 - .../custromstringsupdater/ja.kt | 20 - .../custromstringsupdater/pt_rBR.kt | 105 - .../bringbacktwitter/strings/StringsMap.kt | 13 - .../misc/bringbacktwitter/strings/en_rGB.kt | 67 - .../misc/bringbacktwitter/strings/hi.kt | 66 - .../misc/bringbacktwitter/strings/ru.kt | 67 - .../misc/bringbacktwitter/strings/tr.kt | 66 - .../misc/bringbacktwitter/strings/zh_rCN.kt | 66 - .../misc/bringbacktwitter/strings/zh_rTW.kt | 66 - .../exploretabs/CustomiseExploreTabsPatch.kt | 53 - .../inlinebar/CustomiseInlineBarPatch.kt | 55 - .../customize/navbar/CustomiseNavBarPatch.kt | 79 - .../postFontSize/CustomizePostFontSize.kt | 50 - .../profiletabs/CustomiseProfileTabsPatch.kt | 95 - .../replySorting/DefaultReplySortingPatch.kt | 82 - .../searchtabs/CustomiseSearchTabsPatch.kt | 59 - .../sidebar/CustomiseSideBarPatch.kt | 61 - .../CustomiseTimelineTabsPatch.kt | 77 - .../CustomiseTypeAheadResponsePatch.kt | 53 - .../patches/twitter/misc/fab/HideFABPatch.kt | 45 - .../fab/fingerprints/HideFABFingerprint.kt | 9 - .../misc/integrations/IntegrationsPatch.kt | 12 - .../fingerprints/InitFingerprint.kt | 10 - .../ReVancedUtilsPatchesVersionFingerprint.kt | 16 - .../HideRecommendedUsersPatch.kt | 48 - .../HideRecommendedUsersFingerprint.kt | 14 - .../roundOffNumbers/RoundOffNumbersPatch.kt | 59 - .../selectabletext/SelectableTextPatch.kt | 44 - .../twitter/misc/settings/SettingsPatch.kt | 149 - .../misc/settings/SettingsResourcePatch.kt | 97 - .../fingerprints/AuthorizeAppActivity.kt | 10 - .../fingerprints/SettingsFingerprint.kt | 13 - .../SettingsStatusLoadFingerprint.kt | 20 - .../fingerprints/UrlInterpreterActivity.kt | 10 - .../misc/shareMenu/debugMenu/DebugMenu.kt | 24 - .../fingerprints/ActionEnumsFingerprint.kt | 116 - .../ShareMenuButtonFuncCallFingerprint.kt | 103 - .../shareMenu/hooks/ShareMenuButtonAddHook.kt | 42 - .../hooks/ShareMenuButtonInitHook.kt | 91 - .../nativeDownloader/NativeDownloaderPatch.kt | 238 - .../nativeReaderMode/NativeReaderModePatch.kt | 121 - .../nativeTranslator/NativeTranslatorPatch.kt | 119 - .../twitter/models/ExtMediaEntityPatch.kt | 57 - .../patches/twitter/models/Fingerprints.kt | 118 - .../twitter/models/TweetEntityPatch.kt | 107 - .../twitter/models/TweetInfoEntityPatch.kt | 49 - .../customAppIcon/CustomAppIconPatch.kt | 60 - .../enableForcePip/EnableForcePipPatch.kt | 66 - .../readermode/EnableReaderModePatch.kt | 84 - .../EnableReaderMode1Fingerprint.kt | 11 - .../EnableReaderMode2Fingerprint.kt | 12 - .../premium/redirectBMNavBar/RedirectBMTab.kt | 48 - .../premium/undoposts/EnableUndoPostPatch.kt | 64 - .../fingerprints/UndoPost1Fingerprint.kt | 18 - .../fingerprints/UndoPost2Fingerprint.kt | 14 - .../fingerprints/UndoPost3Fingerprint.kt | 10 - .../premium/unlockdownloads/DownloadPatch.kt | 109 - .../fingerprints/DownloadPatchFingerprint.kt | 17 - .../fingerprints/FIleDownloaderFingerprint.kt | 12 - .../ImmersiveBottomSheetPatchFingerprint.kt | 12 - .../fingerprints/MediaEntityFingerprint.kt | 16 - .../timeline/banner/HideBannerPatch.kt | 46 - .../fingerprints/HideBannerFingerprint.kt | 12 - .../deleteFromDatabasePatch.kt | 23 - .../DisableAutoScrollPatch.kt | 52 - .../EnableVidAutoAdvancePatch.kt | 63 - .../twitter/timeline/forceHD/ForceHDPatch.kt | 67 - .../timeline/forceHD/VideoEntityPatch.kt | 59 - .../hideCommunityBadge/HideCommunityBadge.kt | 61 - .../HideHiddenRepliesPatch.kt | 56 - .../hideNudgeButtons/HideNudgeButtonPatch.kt | 71 - .../hideSocialProof/HideSoialProofPatch.kt | 59 - .../timeline/live/HideLiveThreadsPatch.kt | 46 - .../HideLiveThreadsFingerprint.kt | 13 - .../RemovePremiumUpsellPatch.kt | 47 - .../ShowSensitiveMediaPatch.kt | 48 - .../ShowSensitiveMediaPatchFingerprint.kt | 10 - .../showSourceLabel/ShowSourceLabel.kt | 74 - .../showpollresults/ShowPollResultsPatch.kt | 45 - .../JsonCardInstanceDataFingerprint.kt | 12 - .../timeline/tweetinfo/ForceTranslate.kt | 23 - .../timeline/tweetinfo/HideCommunityNotes.kt | 22 - .../timeline/tweetinfo/HidePromoteButton.kt | 23 - .../timeline/tweetinfo/TweetInfoHook.kt | 48 - 440 files changed, 26094 insertions(+), 9676 deletions(-) create mode 100644 extensions/proguard-rules.pro create mode 100644 extensions/shared/build.gradle.kts create mode 100644 extensions/shared/library/build.gradle.kts create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/CheckEnvironmentPatch.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/PatchInfo.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/BaseFixRedgifsApiPatch.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/redgifs/RedgifsTokenManager.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/slink/BaseFixSLinksPatch.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/fixes/slink/ResolveResult.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Requester.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/requests/Route.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/AppLanguage.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BooleanSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/EnumSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/FloatSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/IntegerSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/LongSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/Setting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/StringSetting.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerView.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/CustomDialogListPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ImportExportPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/LogBufferManager.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ReVancedAboutPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SharedPrefCategory.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/SortedListPreference.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/ClientType.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/PlayerRoutes.java create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java create mode 100644 extensions/shared/src/main/AndroidManifest.xml create mode 100644 extensions/twitter/build.gradle.kts create mode 100644 extensions/twitter/library/build.gradle.kts create mode 100644 extensions/twitter/src/main/AndroidManifest.xml create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/Pref.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/Utils.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/Debug.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/ExtMediaEntities.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/Media.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/Tweet.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/TweetInfo.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/model/Video.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/DatabasePatch.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/DownloadPatch.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/FeatureSwitchPatch.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/TimelineEntry.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/TweetInfo.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/customise/Customise.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/links/HandleCustomDeepLinksPatch.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/links/UnshortenUrlsPatch.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/loggers/ResponseLogger.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/NativeDownloader.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/emoji/CachedEmojiLoader.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/readerMode/ReaderModeFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/readerMode/ReaderModeTemplate.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/readerMode/ReaderModeUtils.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/Constants.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/DialogBox.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/NativeTranslator.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/providers/GTranslate.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/providers/GTranslateV2.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/nativeFeatures/translator/providers/Translate.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/patches/tweet/TweetInfoAPI.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/ActivityHook.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/DeepLink.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/ScreenBuilder.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/Settings.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/SettingsStatus.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/featureflags/FeatureFlag.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/featureflags/FeatureFlagAdapter.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/featureflags/FeatureFlagSearchAdapter.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/featureflags/FeatureFlagsFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/fragments/BackupPrefFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/fragments/PageFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/fragments/RestorePrefFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/fragments/SettingsAboutFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/fragments/SettingsFragment.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/ButtonPref.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/EditTextPref.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/Helper.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/ListPref.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/MultiSelectListPref.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/PikoSettingsButton.java create mode 100644 extensions/twitter/src/main/java/app/revanced/extension/twitter/settings/widgets/SwitchPref.java create mode 100644 extensions/twitter/stub/build.gradle.kts create mode 100644 extensions/twitter/stub/src/main/AndroidManifest.xml create mode 100644 extensions/twitter/stub/src/main/java/android/support/constraint/ConstraintLayout.java create mode 100644 extensions/twitter/stub/src/main/java/android/support/v7/widget/RecyclerView.java create mode 100644 extensions/twitter/stub/src/main/java/androidx/emoji/text/EmojiCompat.java create mode 100644 extensions/twitter/stub/src/main/java/androidx/emoji/text/MetadataRepo.java create mode 100644 extensions/twitter/stub/src/main/java/androidx/emoji/text/MetadataRepoLoader.java create mode 100644 extensions/twitter/stub/src/main/java/app/revanced/integrations/BuildConfig.java create mode 100644 extensions/twitter/stub/src/main/java/com/bytedance/ies/ugc/aweme/commercialize/compliance/personalization/AdPersonalizationActivity.java create mode 100644 extensions/twitter/stub/src/main/java/com/google/android/apps/youtube/app/ui/SlimMetadataScrollableButtonContainerLayout.java create mode 100644 extensions/twitter/stub/src/main/java/com/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar.java create mode 100644 extensions/twitter/stub/src/main/java/com/google/android/material/floatingactionbutton/FloatingActionButton.java create mode 100644 extensions/twitter/stub/src/main/java/com/google/android/material/tabs/TabLayout$g.java create mode 100644 extensions/twitter/stub/src/main/java/com/reddit/domain/model/ILink.java create mode 100644 extensions/twitter/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java create mode 100644 extensions/twitter/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/AwemeStatistics.java create mode 100644 extensions/twitter/stub/src/main/java/com/ss/android/ugc/aweme/feed/model/FeedItemList.java create mode 100644 extensions/twitter/stub/src/main/java/com/tumblr/rumblr/model/TimelineObject.java create mode 100644 extensions/twitter/stub/src/main/java/com/tumblr/rumblr/model/TimelineObjectType.java create mode 100644 extensions/twitter/stub/src/main/java/com/tumblr/rumblr/model/Timelineable.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/api/model/json/core/JsonApiTweet.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/card/JsonBindingValue.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/card/JsonCardInstanceData.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/core/JsonSensitiveMediaWarning.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/core/JsonTweetQuickPromoteEligibility.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/core/JsonUrlEntity.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/search/JsonTypeaheadResponse.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/timeline/urt/JsonTimelineEntry.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/model/json/timeline/urt/JsonTimelineModuleItem.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/ui/widget/LegacyTwitterPreferenceCategory.java create mode 100644 extensions/twitter/stub/src/main/java/com/twitter/util/user/UserIdentifier.java create mode 100644 extensions/twitter/stub/src/main/java/org/chromium/net/UrlRequest.java create mode 100644 extensions/twitter/stub/src/main/java/org/chromium/net/UrlResponseInfo.java create mode 100644 extensions/twitter/stub/src/main/java/org/chromium/net/impl/CronetUrlRequest.java create mode 100644 extensions/twitter/stub/src/main/java/tv/twitch/android/feature/settings/menu/SettingsMenuGroup.java create mode 100644 extensions/twitter/stub/src/main/java/tv/twitch/android/settings/SettingsActivity.java create mode 100644 extensions/twitter/stub/src/main/java/tv/twitch/android/shared/chat/util/ClickableUsernameSpan.java create mode 100644 patches/api/patches.api create mode 100644 patches/build.gradle.kts create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideAds.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideCTJ.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideCTS.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideDetailedPosts.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideGoogleAds.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideMainEvent.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HidePinnedByFollowers.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HidePremiumPrompt.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideRevistBmk.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideSuperHeroEvent.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideTodaysNews.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideTopPeopleSearch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideVideosForYou.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/HideWTF.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/TimelineEntryHookPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/timelineEntryHook/TimelineModuleItemHookPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/ads/trends/HidePromotedTrendPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/DisableChirpFontPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/FeatureFlagPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/FeatureFlagResourcePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/HideBookmarkInTimelinePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/HideFABMenuButtonsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/HideImmersivePlayer.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/RemoveViewCountPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/fingerprints/CustomAdapterFingerprint.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/fingerprints/FeatureFlagFingerprint.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/featureFlag/fingerprints/FeatureFlagLoadFingerprint.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/interaction/downloads/changedirectory/ChangeDownloadDirPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/interaction/downloads/copyMediaLink/CopyMediaLink.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/cleartrackingparams/ClearTrackingParamsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/customDeeplinks/CustomDeepLinksPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/customDeeplinks/HandleCustomDeepLinksPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/customDeeplinks/fingerprints/UrlInterpreterActivityCreateFingerprint.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/customsharingdomain/CustomSharingDomainPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/link/unshorten/NoShortenedUrlPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/logging/responseLogging/ResponseLogging.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/exploretabs/CustomiseExploreTabsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/inlinebar/CustomiseInlineBarPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/navbar/CustomiseNavBarPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/postFontSize/CustomizePostFontSize.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/profiletabs/CustomiseProfileTabsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/replySorting/DefaultReplySortingPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/searchtabs/CustomiseSearchTabsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/sidebar/CustomiseSideBarPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/timelinetabs/CustomiseTimelineTabsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/customize/typeAheadResponse/CustomiseTypeAheadResponsePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/extension/ExtensionPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/extension/Hooks.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/fab/HideFABPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/recommendedusers/HideRecommendedUsersPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/roundOffNumbers/RoundOffNumbersPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/selectabletext/SelectableTextPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/selectabletext/SelectableTextResource.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/settings/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/settings/SettingsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/misc/settings/SettingsResourcePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/models/ExtMediaEntityPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/models/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/models/TweetEntityPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/models/TweetInfoEntityPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/banner/HideBannerPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/deleteFromDatabase/DeleteFromDatabasePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/disableAutoScroll/DisableAutoScrollPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/enableVidAutoAdvance/EnableVidAutoAdvancePatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/forceHD/ForceHDPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/hideCommunityBadge/HideCommunityBadge.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/hideHiddenReplies/HideHiddenRepliesPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/hideNudgeButtons/HideNudgeButtonPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/hideSocialProof/HideSoialProofPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/live/HideLiveThreadsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/removePremiumUpsell/RemovePremiumUpsellPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/sensitivemediasettings/ShowSensitiveMediaPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/showSourceLabel/ShowSourceLabel.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/showpollresults/ShowPollResultsPatch.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/tweetinfo/ForceTranslate.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/tweetinfo/HideCommunityNotes.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/tweetinfo/HidePromoteButton.kt create mode 100644 patches/src/main/kotlin/app/crimera/patches/twitter/timeline/tweetinfo/TweetInfoHook.kt create mode 100644 patches/src/main/kotlin/app/crimera/utils/Utils.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/resources/AddResourcesPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/MethodCall.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/TransformInstructionsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/hex/HexPatchBuilder.kt rename {src => patches/src}/main/kotlin/app/revanced/patches/shared/misc/mapping/ResourceMappingPatch.kt (54%) create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreferenceScreen.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/InputType.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/IntentPreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/ListPreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/NonInteractivePreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceCategory.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceScreenPreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/SummaryType.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/SwitchPreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/TextPreference.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/string/ReplaceStringPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt create mode 100644 patches/src/main/kotlin/app/revanced/util/CrowdinPreprocessor.kt create mode 100644 patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt create mode 100644 patches/src/main/kotlin/app/revanced/util/Utils.kt rename {src/main/kotlin/app => patches/src/main/kotlin/app/revanced}/util/resource/ArrayResource.kt (100%) rename {src/main/kotlin/app => patches/src/main/kotlin/app/revanced}/util/resource/BaseResource.kt (100%) create mode 100644 patches/src/main/kotlin/app/revanced/util/resource/StringResource.kt rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable-hdpi/ic_stat_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable-mdpi/ic_stat_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable-xhdpi/ic_stat_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable-xxhdpi/ic_stat_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable-xxxhdpi/ic_stat_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable/ic_vector_home.xml (99%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable/ic_vector_home_stroke.xml (99%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable/ic_vector_twitter.xml (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable/ic_vector_twitter_white.xml (99%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/drawable/splash_screen_icon.xml (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-hdpi/ic_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-hdpi/ic_launcher_twitter_foreground.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-hdpi/ic_launcher_twitter_round.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-mdpi/ic_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-mdpi/ic_launcher_twitter_foreground.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-mdpi/ic_launcher_twitter_round.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xhdpi/ic_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xhdpi/ic_launcher_twitter_foreground.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xhdpi/ic_launcher_twitter_round.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxhdpi/fg_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxhdpi/ic_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxhdpi/ic_launcher_twitter_foreground.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxhdpi/ic_launcher_twitter_round.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxxhdpi/ic_launcher_twitter.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxxhdpi/ic_launcher_twitter_foreground.webp (100%) rename {src => patches/src}/main/resources/twitter/bringbacktwitter/mipmap-xxxhdpi/ic_launcher_twitter_round.webp (100%) rename {src => patches/src}/main/resources/twitter/settings/layout/feature_flags_view.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/layout/item_row.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/layout/search_dialog.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/layout/search_item_row.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-ar/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-es/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-fr/strings.xml (98%) rename {src => patches/src}/main/resources/twitter/settings/values-hi/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-in/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-it/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-ja/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-ko/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-pl/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-pt-rBR/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-ru/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-tr/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-uk/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-v21/arrays.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-v21/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-vi/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-zh-rCN/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values-zh-rTW/strings.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values/arrays.xml (100%) rename {src => patches/src}/main/resources/twitter/settings/values/strings.xml (100%) create mode 100644 patches/stub/build.gradle.kts create mode 100644 patches/stub/src/main/java/android/os/Build.java delete mode 100644 src/main/kotlin/app/revanced/generator/JsonPatchesFileGenerator.kt delete mode 100644 src/main/kotlin/app/revanced/generator/Main.kt delete mode 100644 src/main/kotlin/app/revanced/generator/PatchesFileGenerator.kt delete mode 100644 src/main/kotlin/app/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch.kt delete mode 100644 src/main/kotlin/app/revanced/patches/shared/misc/integrations/fingerprint/IntegrationsUtilsFingerprint.kt delete mode 100644 src/main/kotlin/app/revanced/patches/twitter/misc/dynamiccolor/DynamicColorPatch.kt delete mode 100644 src/main/kotlin/app/util/PikoUtils.kt delete mode 100644 src/main/kotlin/app/util/ResourceUtils.kt delete mode 100644 src/main/kotlin/app/util/resource/StringResource.kt delete mode 100644 src/main/kotlin/crimera/patches/all/activity/exportall/ExportAllActivitiesPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/all/appDowngrading/AppDowngradingPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideAds.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideCTJ.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideCTS.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideDetailedPosts.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideGoogleAds.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideMainEvent.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HidePinnedByFollowers.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HidePremiumPrompt.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideRevistBmk.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideSuperHeroEvent.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideTodaysNews.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideTopPeopleSearch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideVideosForYou.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/HideWTF.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/TimelineEntryHookPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/timelineEntryHook/TimelineModuleItemHookPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/trends/HidePromotedTrendPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/ads/trends/fingerprints/HidePromotedTrendFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/DisableChirpFontPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/FeatureFlagPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/FeatureFlagResourcePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/HideBookmarkInTimelinePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/HideFABMenuButtonsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/HideImmersivePlayer.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/RemoveViewCountPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/fingerprints/CustomAdapterFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/fingerprints/FeatureFlagFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/featureFlag/fingerprints/FeatureFlagLoadFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/interaction/downloads/changedirectory/ChangeDownloadDirPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/interaction/downloads/copyMediaLink/CopyMediaLink.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/cleartrackingparams/ClearTrackingParamsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/cleartrackingparams/fingerprints/AddSessionTokenFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/customDeeplinks/CustomDeepLinksPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/customDeeplinks/HandleCustomDeepLinksPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/customDeeplinks/fingerprints/UrlInterpreterActivityCreateFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/customsharingdomain/CustomSharingDomainPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/customsharingdomain/fingerprints/CustomSharingDomainFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/unshorten/NoShortenedUrlPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/link/unshorten/fingerprints/JsonObjectMapperFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/logging/responseLogging/ResponseLogging.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/BringBackTwitterResourcePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/custromstringsupdater/ja.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/custromstringsupdater/pt_rBR.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/StringsMap.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/en_rGB.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/hi.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/ru.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/tr.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/zh_rCN.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/bringbacktwitter/strings/zh_rTW.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/exploretabs/CustomiseExploreTabsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/inlinebar/CustomiseInlineBarPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/navbar/CustomiseNavBarPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/postFontSize/CustomizePostFontSize.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/profiletabs/CustomiseProfileTabsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/replySorting/DefaultReplySortingPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/searchtabs/CustomiseSearchTabsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/sidebar/CustomiseSideBarPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/timelinetabs/CustomiseTimelineTabsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/customize/typeAheadResponse/CustomiseTypeAheadResponsePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/fab/HideFABPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/fab/fingerprints/HideFABFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/integrations/IntegrationsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/integrations/fingerprints/InitFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/integrations/fingerprints/ReVancedUtilsPatchesVersionFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/recommendedusers/HideRecommendedUsersPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/recommendedusers/fingerprints/HideRecommendedUsersFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/roundOffNumbers/RoundOffNumbersPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/selectabletext/SelectableTextPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/SettingsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/SettingsResourcePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/fingerprints/AuthorizeAppActivity.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/fingerprints/SettingsFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/fingerprints/SettingsStatusLoadFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/settings/fingerprints/UrlInterpreterActivity.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/debugMenu/DebugMenu.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/fingerprints/ActionEnumsFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/fingerprints/ShareMenuButtonFuncCallFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/hooks/ShareMenuButtonAddHook.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/hooks/ShareMenuButtonInitHook.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/nativeDownloader/NativeDownloaderPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/nativeReaderMode/NativeReaderModePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/misc/shareMenu/nativeTranslator/NativeTranslatorPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/models/ExtMediaEntityPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/models/Fingerprints.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/models/TweetEntityPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/models/TweetInfoEntityPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/customAppIcon/CustomAppIconPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/enableForcePip/EnableForcePipPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/readermode/EnableReaderModePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/readermode/fingerprints/EnableReaderMode1Fingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/readermode/fingerprints/EnableReaderMode2Fingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/redirectBMNavBar/RedirectBMTab.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/undoposts/EnableUndoPostPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/undoposts/fingerprints/UndoPost1Fingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/undoposts/fingerprints/UndoPost2Fingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/undoposts/fingerprints/UndoPost3Fingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/unlockdownloads/DownloadPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/unlockdownloads/fingerprints/DownloadPatchFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/unlockdownloads/fingerprints/FIleDownloaderFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/unlockdownloads/fingerprints/ImmersiveBottomSheetPatchFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/premium/unlockdownloads/fingerprints/MediaEntityFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/banner/HideBannerPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/banner/fingerprints/HideBannerFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/deleteFromDatabase/deleteFromDatabasePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/disableAutoScroll/DisableAutoScrollPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/enableVidAutoAdvance/EnableVidAutoAdvancePatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/forceHD/ForceHDPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/forceHD/VideoEntityPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/hideCommunityBadge/HideCommunityBadge.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/hideHiddenReplies/HideHiddenRepliesPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/hideNudgeButtons/HideNudgeButtonPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/hideSocialProof/HideSoialProofPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/live/HideLiveThreadsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/live/fingerprints/HideLiveThreadsFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/removePremiumUpsell/RemovePremiumUpsellPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/sensitivemediasettings/ShowSensitiveMediaPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/sensitivemediasettings/fingerprints/ShowSensitiveMediaPatchFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/showSourceLabel/ShowSourceLabel.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/showpollresults/ShowPollResultsPatch.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/showpollresults/fingerprints/JsonCardInstanceDataFingerprint.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/tweetinfo/ForceTranslate.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/tweetinfo/HideCommunityNotes.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/tweetinfo/HidePromoteButton.kt delete mode 100644 src/main/kotlin/crimera/patches/twitter/timeline/tweetinfo/TweetInfoHook.kt diff --git a/.gitignore b/.gitignore index 899b7cc6..291fa32c 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,8 @@ node_modules/ # Ignore IDEA files .idea/ /src/main/kotlin/crimera/patches/twitter/test* -bin/ \ No newline at end of file +bin/ + +*.bak + +/local.properties diff --git a/build.gradle.kts b/build.gradle.kts index 166bf5e9..fcaeeb54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,120 +1,3 @@ -import org.gradle.kotlin.dsl.support.listFilesOrdered - plugins { - kotlin("jvm") version "2.0.0" - `maven-publish` -} - -group = "crimera" - -repositories { - mavenCentral() - mavenLocal() - google() - maven { url = uri("https://jitpack.io") } -} - -dependencies { - implementation(libs.revanced.patcher) - implementation(libs.smali) - // TODO: Required because build fails without it. Find a way to remove this dependency. - implementation(libs.guava) - // Used in JsonGenerator. - implementation(libs.gson) - - // A dependency to the Android library unfortunately fails the build, which is why this is required. - compileOnly(project("dummy")) -} - -kotlin { - jvmToolchain(11) -} - -tasks.withType(Jar::class) { - manifest { - attributes["Name"] = "Piko" - attributes["Description"] = "Patches for ReVanced." - attributes["Version"] = version - attributes["Timestamp"] = System.currentTimeMillis().toString() - attributes["Source"] = "git@github.com:you/revanced-patches.git" - attributes["Author"] = "crimera" - attributes["Contact"] = "contact@your.homepage" - attributes["Origin"] = "https://your.homepage" - attributes["License"] = "GNU General Public License v3.0" - } -} - -tasks { - register("generateBundle") { - description = "Generate DEX files and add them in the JAR file" - - dependsOn(build) - - doLast { - val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools") - .listFilesOrdered().last().resolve("d8").absolutePath - - val artifacts = configurations.archives.get().allArtifacts.files.files.first().absolutePath - val workingDirectory = layout.buildDirectory.dir("libs").get().asFile - - exec { - workingDir = workingDirectory - commandLine = listOf(d8, artifacts) - } - - exec { - workingDir = workingDirectory - commandLine = listOf("zip", "-u", artifacts, "classes.dex") - } - } - } - - register("generatePatchesFiles") { - description = "Generate patches files" - - dependsOn(build) - - classpath = sourceSets["main"].runtimeClasspath - mainClass.set("app.revanced.generator.MainKt") - } - - // Required to run tasks because Gradle semantic-release plugin runs the publish task. - // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 - named("publish") { - dependsOn("generateBundle") - dependsOn("generatePatchesFiles") - } -} - -publishing { - publications { - create("revanced-patches-publication") { - from(components["java"]) - - pom { - name = "Piko" - description = "Patches for ReVanced." - url = "https://your.homepage" - - licenses { - license { - name = "GNU General Public License v3.0" - url = "https://www.gnu.org/licenses/gpl-3.0.en.html" - } - } - developers { - developer { - id = "Your ID" - name = "crimera" - email = "contact@your.homepage" - } - } - scm { - connection = "scm:git:git://github.com/crimera/piko.git" - developerConnection = "scm:git:git@github.com:crimera/piko.git" - url = "https://github.com/crimera/piko" - } - } - } - } + alias(libs.plugins.android.library) apply false } diff --git a/extensions/proguard-rules.pro b/extensions/proguard-rules.pro new file mode 100644 index 00000000..06a543ce --- /dev/null +++ b/extensions/proguard-rules.pro @@ -0,0 +1,10 @@ +-dontobfuscate +-dontoptimize +-keepattributes * +-keep class app.revanced.** { + *; +} + +-keep class com.google.** { + *; +} diff --git a/extensions/shared/build.gradle.kts b/extensions/shared/build.gradle.kts new file mode 100644 index 00000000..8f037894 --- /dev/null +++ b/extensions/shared/build.gradle.kts @@ -0,0 +1,4 @@ +dependencies { + implementation(project(":extensions:shared:library")) + compileOnly(libs.okhttp) +} diff --git a/extensions/shared/library/build.gradle.kts b/extensions/shared/library/build.gradle.kts new file mode 100644 index 00000000..bf4d78cd --- /dev/null +++ b/extensions/shared/library/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "app.revanced.extension" + compileSdk = 34 + + defaultConfig { + minSdk = 23 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +dependencies { + compileOnly(libs.annotation) + compileOnly(libs.okhttp) +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java new file mode 100644 index 00000000..3f9f0af1 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/GmsCoreSupport.java @@ -0,0 +1,256 @@ +package app.revanced.extension.shared; + +import static app.revanced.extension.shared.StringRef.str; +import static app.revanced.extension.shared.requests.Route.Method.GET; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.PowerManager; +import android.provider.Settings; +import android.util.Pair; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; + +import app.revanced.extension.shared.requests.Requester; +import app.revanced.extension.shared.requests.Route; + +@SuppressWarnings("unused") +public class GmsCoreSupport { + private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube"; + private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music"; + + private static final String GMS_CORE_PACKAGE_NAME + = getGmsCoreVendorGroupId() + ".android.gms"; + private static final Uri GMS_CORE_PROVIDER + = Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix"); + private static final String DONT_KILL_MY_APP_URL + = "https://dontkillmyapp.com/"; + private static final Route DONT_KILL_MY_APP_MANUFACTURER_API + = new Route(GET, "/api/v2/{manufacturer}.json"); + private static final String DONT_KILL_MY_APP_NAME_PARAMETER + = "?app=MicroG"; + private static final String BUILD_MANUFACTURER + = Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-"); + + /** + * If a manufacturer specific page exists on DontKillMyApp. + */ + @Nullable + private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED; + + private static void open(String queryOrLink) { + Logger.printInfo(() -> "Opening link: " + queryOrLink); + + Intent intent; + try { + // Check if queryOrLink is a valid URL. + new URL(queryOrLink); + + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink)); + } catch (MalformedURLException e) { + intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.putExtra(SearchManager.QUERY, queryOrLink); + } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Utils.getContext().startActivity(intent); + + // Gracefully exit, otherwise the broken app will continue to run. + System.exit(0); + } + + private static void showBatteryOptimizationDialog(Activity context, + String dialogMessageRef, + String positiveButtonTextRef, + DialogInterface.OnClickListener onPositiveClickListener) { + // Use a delay to allow the activity to finish initializing. + // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. + Utils.runOnMainThreadDelayed(() -> { + // Create the custom dialog. + Pair dialogPair = Utils.createCustomDialog( + context, + str("gms_core_dialog_title"), // Title. + str(dialogMessageRef), // Message. + null, // No EditText. + str(positiveButtonTextRef), // OK button text. + () -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable. + null, // No Cancel button action. + null, // No Neutral button text. + null, // No Neutral button action. + true // Dismiss dialog when onNeutralClick. + ); + + Dialog dialog = dialogPair.first; + + // Do not set cancelable to false, to allow using back button to skip the action, + // just in case the battery change can never be satisfied. + dialog.setCancelable(true); + + // Show the dialog + Utils.showDialog(context, dialog); + }, 100); + } + + /** + * Injection point. + */ + public static void checkGmsCore(Activity context) { + try { + // Verify the user has not included GmsCore for a root installation. + // GmsCore Support changes the package name, but with a mounted installation + // all manifest changes are ignored and the original package name is used. + String packageName = context.getPackageName(); + if (packageName.equals(PACKAGE_NAME_YOUTUBE) || packageName.equals(PACKAGE_NAME_YOUTUBE_MUSIC)) { + Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included"); + // Cannot use localize text here, since the app will load + // resources from the unpatched app and all patch strings are missing. + Utils.showToastLong("The 'GmsCore support' patch breaks mount installations"); + + // Do not exit. If the app exits before launch completes (and without + // opening another activity), then on some devices such as Pixel phone Android 10 + // no toast will be shown and the app will continually relaunch + // with the appearance of a hung app. + } + + // Verify GmsCore is installed. + try { + PackageManager manager = context.getPackageManager(); + manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException exception) { + Logger.printInfo(() -> "GmsCore was not found"); + // Cannot show a dialog and must show a toast, + // because on some installations the app crashes before a dialog can be displayed. + Utils.showToastLong(str("gms_core_toast_not_installed_message")); + open(getGmsCoreDownload()); + return; + } + + // Check if GmsCore is whitelisted from battery optimizations. + if (isAndroidAutomotive(context)) { + // Ignore Android Automotive devices (Google built-in), + // as there is no way to disable battery optimizations. + Logger.printDebug(() -> "Device is Android Automotive"); + } else if (batteryOptimizationsEnabled(context)) { + Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations"); + + showBatteryOptimizationDialog(context, + "gms_core_dialog_not_whitelisted_using_battery_optimizations_message", + "gms_core_dialog_continue_text", + (dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context)); + return; + } + + // Check if GmsCore is currently running in the background. + var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER); + //noinspection TryFinallyCanBeTryWithResources + try { + if (client == null) { + Logger.printInfo(() -> "GmsCore is not running in the background"); + checkIfDontKillMyAppSupportsManufacturer(); + + showBatteryOptimizationDialog(context, + "gms_core_dialog_not_whitelisted_not_allowed_in_background_message", + "gms_core_dialog_open_website_text", + (dialog, id) -> openDontKillMyApp()); + } + } finally { + if (client != null) client.close(); + } + } catch (Exception ex) { + Logger.printException(() -> "checkGmsCore failure", ex); + } + } + + @SuppressLint("BatteryLife") // Permission is part of GmsCore + private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null)); + activity.startActivityForResult(intent, 0); + } + + private static void checkIfDontKillMyAppSupportsManufacturer() { + Utils.runOnBackgroundThread(() -> { + try { + final long start = System.currentTimeMillis(); + HttpURLConnection connection = Requester.getConnectionFromRoute( + DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + final boolean supported = connection.getResponseCode() == 200; + Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ") + + "listed on DontKillMyApp: " + BUILD_MANUFACTURER + + " fetch took: " + (System.currentTimeMillis() - start) + "ms"); + DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported; + } catch (Exception ex) { + Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: " + + BUILD_MANUFACTURER, ex); + DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null; + } + }); + } + + private static void openDontKillMyApp() { + final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED; + + String manufacturerPageToOpen; + if (manufacturerSupported == null) { + // Fetch has not completed yet. Only happens on extremely slow internet connections + // and the user spends less than 1 second reading what's on screen. + // Instead of waiting for the fetch (which may timeout), + // open the website without a vendor. + manufacturerPageToOpen = ""; + } else if (manufacturerSupported) { + manufacturerPageToOpen = BUILD_MANUFACTURER; + } else { + // No manufacturer specific page exists. Open the general page. + manufacturerPageToOpen = "general"; + } + + open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER); + } + + /** + * @return If GmsCore is not whitelisted from battery optimizations. + */ + private static boolean batteryOptimizationsEnabled(Context context) { + //noinspection ObsoleteSdkInt + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Android 5.0 does not have battery optimization settings. + return false; + } + var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME); + } + + private static boolean isAndroidAutomotive(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + + private static String getGmsCoreDownload() { + final var vendorGroupId = getGmsCoreVendorGroupId(); + //noinspection SwitchStatementWithTooFewBranches + return switch (vendorGroupId) { + case "app.revanced" -> "https://github.com/revanced/gmscore/releases/latest"; + default -> vendorGroupId + ".android.gms"; + }; + } + + // Modified by a patch. Do not touch. + private static String getGmsCoreVendorGroupId() { + return "app.revanced"; + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java new file mode 100644 index 00000000..47f6da3e --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Logger.java @@ -0,0 +1,214 @@ +package app.revanced.extension.shared; + +import static app.revanced.extension.shared.settings.BaseSettings.DEBUG; +import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE; +import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.preference.LogBufferManager; + +/** + * ReVanced specific logger. Logging is done to standard device log (accessible thru ADB), + * and additionally accessible thru {@link LogBufferManager}. + * + * All methods are thread safe, and are safe to call even + * if {@link Utils#getContext()} is not available. + */ +public class Logger { + + /** + * Log messages using lambdas. + */ + @FunctionalInterface + public interface LogMessage { + /** + * @return Logger string message. This method is only called if logging is enabled. + */ + @NonNull + String buildMessageString(); + } + + private enum LogLevel { + DEBUG, + INFO, + ERROR + } + + /** + * Log tag prefix. Only used for system logging. + */ + private static final String REVANCED_LOG_TAG_PREFIX = "revanced: "; + + private static final String LOGGER_CLASS_NAME = Logger.class.getName(); + + /** + * @return For outer classes, this returns {@link Class#getSimpleName()}. + * For static, inner, or anonymous classes, this returns the simple name of the enclosing class. + *
+ * For example, each of these classes returns 'SomethingView': + * + * com.company.SomethingView + * com.company.SomethingView$StaticClass + * com.company.SomethingView$1 + * + */ + private static String getOuterClassSimpleName(Object obj) { + Class logClass = obj.getClass(); + String fullClassName = logClass.getName(); + final int dollarSignIndex = fullClassName.indexOf('$'); + if (dollarSignIndex < 0) { + return logClass.getSimpleName(); // Already an outer class. + } + + // Class is inner, static, or anonymous. + // Parse the simple name full name. + // A class with no package returns index of -1, but incrementing gives index zero which is correct. + final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; + return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); + } + + /** + * Internal method to handle logging to Android Log and {@link LogBufferManager}. + * Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer + * with class name but without 'revanced:' prefix. + * + * @param logLevel The log level. + * @param message Log message object. + * @param ex Optional exception. + * @param includeStackTrace If the current stack should be included. + * @param showToast If a toast is to be shown. + */ + private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex, + boolean includeStackTrace, boolean showToast) { + // It's very important that no Settings are used in this method, + // as this code is used when a context is not set and thus referencing + // a setting will crash the app. + String messageString = message.buildMessageString(); + String className = getOuterClassSimpleName(message); + + String logText = messageString; + + // Append exception message if present. + if (ex != null) { + var exceptionMessage = ex.getMessage(); + if (exceptionMessage != null) { + logText += "\nException: " + exceptionMessage; + } + } + + if (includeStackTrace) { + var sw = new StringWriter(); + new Throwable().printStackTrace(new PrintWriter(sw)); + String stackTrace = sw.toString(); + // Remove the stacktrace elements of this class. + final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME); + final int loggerBegins = stackTrace.indexOf('\n', loggerIndex); + logText += stackTrace.substring(loggerBegins); + } + + // Do not include "revanced:" prefix in clipboard logs. + String managerToastString = className + ": " + logText; + LogBufferManager.appendToLogBuffer(managerToastString); + + String logTag = REVANCED_LOG_TAG_PREFIX + className; + switch (logLevel) { + case DEBUG: + if (ex == null) Log.d(logTag, logText); + else Log.d(logTag, logText, ex); + break; + case INFO: + if (ex == null) Log.i(logTag, logText); + else Log.i(logTag, logText, ex); + break; + case ERROR: + if (ex == null) Log.e(logTag, logText); + else Log.e(logTag, logText, ex); + break; + } + + if (showToast) { + Utils.showToastLong(managerToastString); + } + } + + private static boolean shouldLogDebug() { + // If the app is still starting up and the context is not yet set, + // then allow debug logging regardless what the debug setting actually is. + return Utils.context == null || DEBUG.get(); + } + + private static boolean shouldShowErrorToast() { + return Utils.context != null && DEBUG_TOAST_ON_ERROR.get(); + } + + private static boolean includeStackTrace() { + return Utils.context != null && DEBUG_STACKTRACE.get(); + } + + /** + * Logs debug messages under the outer class name of the code calling this method. + *

+ * Whenever possible, the log string should be constructed entirely inside + * {@link LogMessage#buildMessageString()} so the performance cost of + * building strings is paid only if {@link BaseSettings#DEBUG} is enabled. + */ + public static void printDebug(LogMessage message) { + printDebug(message, null); + } + + /** + * Logs debug messages under the outer class name of the code calling this method. + *

+ * Whenever possible, the log string should be constructed entirely inside + * {@link LogMessage#buildMessageString()} so the performance cost of + * building strings is paid only if {@link BaseSettings#DEBUG} is enabled. + */ + public static void printDebug(LogMessage message, @Nullable Exception ex) { + if (shouldLogDebug()) { + logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false); + } + } + + /** + * Logs information messages using the outer class name of the code calling this method. + */ + public static void printInfo(LogMessage message) { + printInfo(message, null); + } + + /** + * Logs information messages using the outer class name of the code calling this method. + */ + public static void printInfo(LogMessage message, @Nullable Exception ex) { + logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false); + } + + /** + * Logs exceptions under the outer class name of the code calling this method. + * Appends the log message, exception (if present), and toast message (if enabled) to logBuffer. + */ + public static void printException(LogMessage message) { + printException(message, null); + } + + /** + * Logs exceptions under the outer class name of the code calling this method. + *

+ * If the calling code is showing it's own error toast, + * instead use {@link #printInfo(LogMessage, Exception)} + * + * @param message log message + * @param ex exception (optional) + */ + public static void printException(LogMessage message, @Nullable Throwable ex) { + logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast()); + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java new file mode 100644 index 00000000..4390137d --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/StringRef.java @@ -0,0 +1,122 @@ +package app.revanced.extension.shared; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.annotation.NonNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class StringRef { + private static Resources resources; + private static String packageName; + + // must use a thread safe map, as this class is used both on and off the main thread + private static final Map strings = Collections.synchronizedMap(new HashMap<>()); + + /** + * Returns a cached instance. + * Should be used if the same String could be loaded more than once. + * + * @param id string resource name/id + * @see #sf(String) + */ + @NonNull + public static StringRef sfc(@NonNull String id) { + StringRef ref = strings.get(id); + if (ref == null) { + ref = new StringRef(id); + strings.put(id, ref); + } + return ref; + } + + /** + * Creates a new instance, but does not cache the value. + * Should be used for Strings that are loaded exactly once. + * + * @param id string resource name/id + * @see #sfc(String) + */ + @NonNull + public static StringRef sf(@NonNull String id) { + return new StringRef(id); + } + + /** + * Gets string value by string id, shorthand for sfc(id).toString() + * + * @param id string resource name/id + * @return String value from string.xml + */ + @NonNull + public static String str(@NonNull String id) { + return sfc(id).toString(); + } + + /** + * Gets string value by string id, shorthand for sfc(id).toString() and formats the string + * with given args. + * + * @param id string resource name/id + * @param args the args to format the string with + * @return String value from string.xml formatted with given args + */ + @NonNull + public static String str(@NonNull String id, Object... args) { + return String.format(str(id), args); + } + + /** + * Creates a StringRef object that'll not change it's value + * + * @param value value which toString() method returns when invoked on returned object + * @return Unique StringRef instance, its value will never change + */ + @NonNull + public static StringRef constant(@NonNull String value) { + final StringRef ref = new StringRef(value); + ref.resolved = true; + return ref; + } + + /** + * Shorthand for constant("") + * Its value always resolves to empty string + */ + @NonNull + public static final StringRef empty = constant(""); + + @NonNull + private String value; + private boolean resolved; + + public StringRef(@NonNull String resName) { + this.value = resName; + } + + @Override + @NonNull + public String toString() { + if (!resolved) { + if (resources == null || packageName == null) { + Context context = Utils.getContext(); + resources = context.getResources(); + packageName = context.getPackageName(); + } + resolved = true; + if (resources != null) { + final int identifier = resources.getIdentifier(value, "string", packageName); + if (identifier == 0) + Logger.printException(() -> "Resource not found: " + value); + else + value = resources.getString(identifier); + } else { + Logger.printException(() -> "Could not resolve resources!"); + } + } + return value; + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java new file mode 100644 index 00000000..f412faa0 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -0,0 +1,1540 @@ +package app.revanced.extension.shared; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.net.ConnectivityManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.util.DisplayMetrics; +import android.util.Pair; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.Toolbar; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.text.Bidi; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import app.revanced.extension.shared.settings.AppLanguage; +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.BooleanSetting; +import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference; + +public class Utils { + + @SuppressLint("StaticFieldLeak") + public static Context context; + private static String versionName; + private static String applicationLabel; + + @ColorInt + private static int darkColor = Color.BLACK; + @ColorInt + private static int lightColor = Color.WHITE; + + @Nullable + private static Boolean isDarkModeEnabled; + + private Utils() { + } // utility class + + + /** + * Injection point. + **/ + public static void load() { + } + /** + * @return The manifest 'Version' entry of the patches.jar used during patching. + */ + @SuppressWarnings("SameReturnValue") + public static String getPatchesReleaseVersion() { + return ""; // Value is replaced during patching. + } + + private static PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException { + final var packageName = Objects.requireNonNull(getContext()).getPackageName(); + + PackageManager packageManager = context.getPackageManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return packageManager.getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(0) + ); + } + + return packageManager.getPackageInfo( + packageName, + 0 + ); + } + + + /** + * @return The version name of the app, such as 19.11.43 + */ + public static String getAppVersionName() { + if (versionName == null) { + try { + versionName = getPackageInfo().versionName; + } catch (Exception ex) { + Logger.printException(() -> "Failed to get package info", ex); + versionName = "Unknown"; + } + } + + return versionName; + } + + public static String getApplicationName() { + if (applicationLabel == null) { + try { + ApplicationInfo applicationInfo = getPackageInfo().applicationInfo; + applicationLabel = (String) applicationInfo.loadLabel(context.getPackageManager()); + } catch (Exception ex) { + Logger.printException(() -> "Failed to get application name", ex); + applicationLabel = "Unknown"; + } + } + + return applicationLabel; + } + + /** + * Hide a view by setting its layout height and width to 1dp. + * + * @param condition The setting to check for hiding the view. + * @param view The view to hide. + */ + public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) { + if (hideViewBy0dpUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); + } + } + + /** + * Hide a view by setting its layout height and width to 0dp. + * + * @param condition The setting to check for hiding the view. + * @param view The view to hide. + */ + public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) { + if (condition) { + hideViewByLayoutParams(view); + return true; + } + + return false; + } + + /** + * Hide a view by setting its visibility to GONE. + * + * @param condition The setting to check for hiding the view. + * @param view The view to hide. + */ + public static void hideViewUnderCondition(BooleanSetting condition, View view) { + if (hideViewUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); + } + } + + /** + * Hide a view by setting its visibility to GONE. + * + * @param condition The setting to check for hiding the view. + * @param view The view to hide. + */ + public static boolean hideViewUnderCondition(boolean condition, View view) { + if (condition) { + view.setVisibility(View.GONE); + return true; + } + + return false; + } + + public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) { + if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) { + Logger.printDebug(() -> "View hidden by setting: " + condition); + } + } + + public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) { + if (setting) { + ViewParent parent = view.getParent(); + if (parent instanceof ViewGroup parentGroup) { + parentGroup.removeView(view); + return true; + } + } + + return false; + } + + /** + * General purpose pool for network calls and other background tasks. + * All tasks run at max thread priority. + */ + private static final ThreadPoolExecutor backgroundThreadPool = new ThreadPoolExecutor( + 3, // 3 threads always ready to go. + Integer.MAX_VALUE, + 10, // For any threads over the minimum, keep them alive 10 seconds after they go idle. + TimeUnit.SECONDS, + new SynchronousQueue<>(), + r -> { // ThreadFactory + Thread t = new Thread(r); + t.setPriority(Thread.MAX_PRIORITY); // Run at max priority. + return t; + }); + + public static void runOnBackgroundThread(Runnable task) { + backgroundThreadPool.execute(task); + } + + public static Future submitOnBackgroundThread(Callable call) { + return backgroundThreadPool.submit(call); + } + + /** + * Simulates a delay by doing meaningless calculations. + * Used for debugging to verify UI timeout logic. + */ + @SuppressWarnings("UnusedReturnValue") + public static long doNothingForDuration(long amountOfTimeToWaste) { + final long timeCalculationStarted = System.currentTimeMillis(); + Logger.printDebug(() -> "Artificially creating delay of: " + amountOfTimeToWaste + "ms"); + + long meaninglessValue = 0; + while (System.currentTimeMillis() - timeCalculationStarted < amountOfTimeToWaste) { + // Could do a thread sleep, but that will trigger an exception if the thread is interrupted. + meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random())); + } + // Return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work, + // leaving an empty loop that hammers on the System.currentTimeMillis native call. + return meaninglessValue; + } + + public static boolean containsAny(String value, String... targets) { + return indexOfFirstFound(value, targets) >= 0; + } + + public static int indexOfFirstFound(String value, String... targets) { + for (String string : targets) { + if (!string.isEmpty()) { + final int indexOf = value.indexOf(string); + if (indexOf >= 0) return indexOf; + } + } + return -1; + } + + /** + * @return zero, if the resource is not found. + */ + @SuppressLint("DiscouragedApi") + public static int getResourceIdentifier(Context context, String resourceIdentifierName, String type) { + return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName()); + } + + /** + * @return zero, if the resource is not found. + */ + public static int getResourceIdentifier(String resourceIdentifierName, String type) { + return getResourceIdentifier(getContext(), resourceIdentifierName, type); + } + + public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getInteger(getResourceIdentifier(resourceIdentifierName, "integer")); + } + + public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException { + return AnimationUtils.loadAnimation(getContext(), getResourceIdentifier(resourceIdentifierName, "anim")); + } + + @ColorInt + public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException { + //noinspection deprecation + return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color")); + } + + public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getDimensionPixelSize(getResourceIdentifier(resourceIdentifierName, "dimen")); + } + + public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen")); + } + + public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getStringArray(getResourceIdentifier(resourceIdentifierName, "array")); + } + + public static String getResourceString(@NonNull String resourceIdentifierName) throws Resources.NotFoundException { + return getContext().getResources().getString(getResourceIdentifier(resourceIdentifierName, "string")); + } + + public interface MatchFilter { + boolean matches(T object); + } + + /** + * Includes sub children. + */ + public static R getChildViewByResourceName(View view, String str) { + var child = view.findViewById(Utils.getResourceIdentifier(str, "id")); + if (child != null) { + //noinspection unchecked + return (R) child; + } + + throw new IllegalArgumentException("View with resource name not found: " + str); + } + + /** + * @param searchRecursively If children ViewGroups should also be + * recursively searched using depth first search. + * @return The first child view that matches the filter. + */ + @Nullable + public static T getChildView(ViewGroup viewGroup, boolean searchRecursively, + MatchFilter filter) { + for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) { + View childAt = viewGroup.getChildAt(i); + + if (filter.matches(childAt)) { + //noinspection unchecked + return (T) childAt; + } + // Must do recursive after filter check, in case the filter is looking for a ViewGroup. + if (searchRecursively && childAt instanceof ViewGroup) { + T match = getChildView((ViewGroup) childAt, true, filter); + if (match != null) return match; + } + } + + return null; + } + + @Nullable + public static ViewParent getParentView(View view, int nthParent) { + ViewParent parent = view.getParent(); + + int currentDepth = 0; + while (++currentDepth < nthParent && parent != null) { + parent = parent.getParent(); + } + + if (currentDepth == nthParent) { + return parent; + } + + final int currentDepthLog = currentDepth; + Logger.printDebug(() -> "Could not find parent view of depth: " + nthParent + + " and instead found at: " + currentDepthLog + " view: " + view); + return null; + } + + public static void restartApp(Context context) { + String packageName = context.getPackageName(); + Intent intent = Objects.requireNonNull(context.getPackageManager().getLaunchIntentForPackage(packageName)); + Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent()); + // Required for API 34 and later + // Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents + mainIntent.setPackage(packageName); + context.startActivity(mainIntent); + System.exit(0); + } + + public static Context getContext() { + if (context == null) { + Logger.printException(() -> "Context is not set by extension hook, returning null", null); + } + return context; + } + + public static void setContext(Context appContext) { + // Intentionally use logger before context is set, + // to expose any bugs in the 'no context available' logger code. + Logger.printInfo(() -> "Set context: " + appContext); + // Must initially set context to check the app language. + context = appContext; + + AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); + if (language != AppLanguage.DEFAULT) { + // Create a new context with the desired language. + Logger.printDebug(() -> "Using app language: " + language); + Configuration config = new Configuration(appContext.getResources().getConfiguration()); + config.setLocale(language.getLocale()); + context = appContext.createConfigurationContext(config); + } + + setThemeLightColor(getThemeColor(getThemeLightColorResourceName(), Color.WHITE)); + setThemeDarkColor(getThemeColor(getThemeDarkColorResourceName(), Color.BLACK)); + + load(); + } + + public static void setClipboard(CharSequence text) { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context + .getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); + clipboard.setPrimaryClip(clip); + } + + public static boolean isTablet() { + return context.getResources().getConfiguration().smallestScreenWidthDp >= 600; + } + + @Nullable + private static Boolean isRightToLeftTextLayout; + + /** + * @return If the device language uses right to left text layout (Hebrew, Arabic, etc). + * If this should match any ReVanced language override then instead use + * {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}. + * This is the default locale of the device, which may differ if + * {@link BaseSettings#REVANCED_LANGUAGE} is set to a different language. + */ + public static boolean isRightToLeftLocale() { + if (isRightToLeftTextLayout == null) { + isRightToLeftTextLayout = isRightToLeftLocale(Locale.getDefault()); + } + return isRightToLeftTextLayout; + } + + public static void shareText(String txt) { + final String appPackageName = context.getPackageName(); + Intent sendIntent = new Intent(); + sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, txt); + sendIntent.setType("text/plain"); + context.startActivity(sendIntent); + } + + /** + * @return If the locale uses right to left text layout (Hebrew, Arabic, etc). + */ + public static boolean isRightToLeftLocale(Locale locale) { + String displayLanguage = locale.getDisplayLanguage(); + return new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft(); + } + + /** + * @return A UTF8 string containing a left-to-right or right-to-left + * character of the device locale. If this should match any ReVanced language + * override then instead use {@link #getTextDirectionString(Locale)} with + * {@link BaseSettings#REVANCED_LANGUAGE}. + */ + public static String getTextDirectionString() { + return getTextDirectionString(isRightToLeftLocale()); + } + + public static String getTextDirectionString(Locale locale) { + return getTextDirectionString(isRightToLeftLocale(locale)); + } + + private static String getTextDirectionString(boolean isRightToLeft) { + return isRightToLeft + ? "\u200F" // u200F = right to left character. + : "\u200E"; // u200E = left to right character. + } + + /** + * @return if the text contains at least 1 number character, + * including any unicode numbers such as Arabic. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean containsNumber(CharSequence text) { + for (int index = 0, length = text.length(); index < length;) { + final int codePoint = Character.codePointAt(text, index); + if (Character.isDigit(codePoint)) { + return true; + } + index += Character.charCount(codePoint); + } + + return false; + } + + /** + * Ignore this class. It must be public to satisfy Android requirements. + */ + @SuppressWarnings("deprecation") + public static final class DialogFragmentWrapper extends DialogFragment { + + private Dialog dialog; + @Nullable + private DialogFragmentOnStartAction onStartAction; + + @Override + public void onSaveInstanceState(Bundle outState) { + // Do not call super method to prevent state saving. + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return dialog; + } + + @Override + public void onStart() { + try { + super.onStart(); + + if (onStartAction != null) { + onStartAction.onStart(dialog); + } + } catch (Exception ex) { + Logger.printException(() -> "onStart failure: " + dialog.getClass().getSimpleName(), ex); + } + } + } + + /** + * Interface for {@link #showDialog(Activity, Dialog, boolean, DialogFragmentOnStartAction)}. + */ + @FunctionalInterface + public interface DialogFragmentOnStartAction { + void onStart(Dialog dialog); + } + + public static void showDialog(Activity activity, Dialog dialog) { + showDialog(activity, dialog, true, null); + } + + /** + * Utility method to allow showing a Dialog on top of other dialogs. + * Calling this will always display the dialog on top of all other dialogs + * previously called using this method. + *

+ * Be aware the on start action can be called multiple times for some situations, + * such as the user switching apps without dismissing the dialog then switching back to this app. + *

+ * This method is only useful during app startup and multiple patches may show their own dialog, + * and the most important dialog can be called last (using a delay) so it's always on top. + *

+ * For all other situations it's better to not use this method and + * call {@link Dialog#show()} on the dialog. + */ + @SuppressWarnings("deprecation") + public static void showDialog(Activity activity, + Dialog dialog, + boolean isCancelable, + @Nullable DialogFragmentOnStartAction onStartAction) { + verifyOnMainThread(); + + DialogFragmentWrapper fragment = new DialogFragmentWrapper(); + fragment.dialog = dialog; + fragment.onStartAction = onStartAction; + fragment.setCancelable(isCancelable); + + fragment.show(activity.getFragmentManager(), null); + } + + /** + * Safe to call from any thread. + */ + public static void showToastShort(String messageToToast) { + showToast(messageToToast, Toast.LENGTH_SHORT); + } + + /** + * Safe to call from any thread. + */ + public static void showToastLong(String messageToToast) { + showToast(messageToToast, Toast.LENGTH_LONG); + } + + private static void showToast(String messageToToast, int toastDuration) { + Objects.requireNonNull(messageToToast); + runOnMainThreadNowOrLater(() -> { + Context currentContext = context; + + if (currentContext == null) { + Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast); + } else { + Logger.printDebug(() -> "Showing toast: " + messageToToast); + Toast.makeText(currentContext, messageToToast, toastDuration).show(); + } + }); + } + + /** + * @return The current dark mode as set by any patch. + * Or if none is set, then the system dark mode status is returned. + */ + public static boolean isDarkModeEnabled() { + Boolean isDarkMode = isDarkModeEnabled; + if (isDarkMode != null) { + return isDarkMode; + } + + Configuration config = Resources.getSystem().getConfiguration(); + final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; + return currentNightMode == Configuration.UI_MODE_NIGHT_YES; + } + + /** + * Overrides dark mode status as returned by {@link #isDarkModeEnabled()}. + */ + public static void setIsDarkModeEnabled(boolean isDarkMode) { + isDarkModeEnabled = isDarkMode; + Logger.printDebug(() -> "Dark mode status: " + isDarkMode); + } + + public static boolean isLandscapeOrientation() { + final int orientation = Resources.getSystem().getConfiguration().orientation; + return orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + /** + * Automatically logs any exceptions the runnable throws. + * + * @see #runOnMainThreadNowOrLater(Runnable) + */ + public static void runOnMainThread(Runnable runnable) { + runOnMainThreadDelayed(runnable, 0); + } + + /** + * Automatically logs any exceptions the runnable throws. + */ + public static void runOnMainThreadDelayed(Runnable runnable, long delayMillis) { + Runnable loggingRunnable = () -> { + try { + runnable.run(); + } catch (Exception ex) { + Logger.printException(() -> runnable.getClass().getSimpleName() + ": " + ex.getMessage(), ex); + } + }; + new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis); + } + + /** + * If called from the main thread, the code is run immediately. + * If called off the main thread, this is the same as {@link #runOnMainThread(Runnable)}. + */ + public static void runOnMainThreadNowOrLater(Runnable runnable) { + if (isCurrentlyOnMainThread()) { + runnable.run(); + } else { + runOnMainThread(runnable); + } + } + + /** + * @return if the calling thread is on the main thread. + */ + public static boolean isCurrentlyOnMainThread() { + return Looper.getMainLooper().isCurrentThread(); + } + + /** + * @throws IllegalStateException if the calling thread is _off_ the main thread. + */ + public static void verifyOnMainThread() throws IllegalStateException { + if (!isCurrentlyOnMainThread()) { + throw new IllegalStateException("Must call _on_ the main thread"); + } + } + + /** + * @throws IllegalStateException if the calling thread is _on_ the main thread. + */ + public static void verifyOffMainThread() throws IllegalStateException { + if (isCurrentlyOnMainThread()) { + throw new IllegalStateException("Must call _off_ the main thread"); + } + } + + public enum NetworkType { + NONE, + MOBILE, + OTHER, + } + + /** + * Calling extension code must ensure the un-patched app has the permission + * android.permission.ACCESS_NETWORK_STATE, + * otherwise the app will crash if this method is used. + */ + public static boolean isNetworkConnected() { + NetworkType networkType = getNetworkType(); + return networkType == NetworkType.MOBILE + || networkType == NetworkType.OTHER; + } + + /** + * Calling extension code must ensure the un-patched app has the permission + * android.permission.ACCESS_NETWORK_STATE, + * otherwise the app will crash if this method is used. + */ + @SuppressWarnings({"MissingPermission", "deprecation"}) + public static NetworkType getNetworkType() { + Context networkContext = getContext(); + if (networkContext == null) { + return NetworkType.NONE; + } + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + var networkInfo = cm.getActiveNetworkInfo(); + + if (networkInfo == null || !networkInfo.isConnected()) { + return NetworkType.NONE; + } + var type = networkInfo.getType(); + return (type == ConnectivityManager.TYPE_MOBILE) + || (type == ConnectivityManager.TYPE_BLUETOOTH) ? NetworkType.MOBILE : NetworkType.OTHER; + } + + /** + * Hide a view by setting its layout params to 0x0 + * @param view The view to hide. + */ + public static void hideViewByLayoutParams(View view) { + if (view instanceof LinearLayout) { + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams); + } else if (view instanceof FrameLayout) { + FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams2); + } else if (view instanceof RelativeLayout) { + RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams3); + } else if (view instanceof Toolbar) { + Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0); + view.setLayoutParams(layoutParams4); + } else if (view instanceof ViewGroup) { + ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0); + view.setLayoutParams(layoutParams5); + } else { + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.width = 0; + params.height = 0; + view.setLayoutParams(params); + } + } + + /** + * Creates a custom dialog with a styled layout, including a title, message, buttons, and an + * optional EditText. The dialog's appearance adapts to the app's dark mode setting, with + * rounded corners and customizable button actions. Buttons adjust dynamically to their text + * content and are arranged in a single row if they fit within 80% of the screen width, + * with the Neutral button aligned to the left and OK/Cancel buttons centered on the right. + * If buttons do not fit, each is placed on a separate row, all aligned to the right. + * + * @param context Context used to create the dialog. + * @param title Title text of the dialog. + * @param message Message text of the dialog (supports Spanned for HTML), or null if replaced by EditText. + * @param editText EditText to include in the dialog, or null if no EditText is needed. + * @param okButtonText OK button text, or null to use the default "OK" string. + * @param onOkClick Action to perform when the OK button is clicked. + * @param onCancelClick Action to perform when the Cancel button is clicked, or null if no Cancel button is needed. + * @param neutralButtonText Neutral button text, or null if no Neutral button is needed. + * @param onNeutralClick Action to perform when the Neutral button is clicked, or null if no Neutral button is needed. + * @param dismissDialogOnNeutralClick If the dialog should be dismissed when the Neutral button is clicked. + * @return The Dialog and its main LinearLayout container. + */ + @SuppressWarnings("ExtractMethodRecommender") + public static Pair createCustomDialog( + Context context, String title, CharSequence message, @Nullable EditText editText, + String okButtonText, Runnable onOkClick, Runnable onCancelClick, + @Nullable String neutralButtonText, @Nullable Runnable onNeutralClick, + boolean dismissDialogOnNeutralClick + ) { + Logger.printDebug(() -> "Creating custom dialog with title: " + title); + + Dialog dialog = new Dialog(context); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar. + + // Preset size constants. + final int dip4 = dipToPixels(4); + final int dip8 = dipToPixels(8); + final int dip16 = dipToPixels(16); + final int dip24 = dipToPixels(24); + + // Create main layout. + LinearLayout mainLayout = new LinearLayout(context); + mainLayout.setOrientation(LinearLayout.VERTICAL); + mainLayout.setPadding(dip24, dip16, dip24, dip24); + // Set rounded rectangle background. + ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape( + createCornerRadii(28), null, null)); + mainBackground.getPaint().setColor(getDialogBackgroundColor()); // Dialog background. + mainLayout.setBackground(mainBackground); + + // Title. + if (!TextUtils.isEmpty(title)) { + TextView titleView = new TextView(context); + titleView.setText(title); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + titleView.setTextSize(18); + titleView.setTextColor(getAppForegroundColor()); + titleView.setGravity(Gravity.CENTER); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + layoutParams.setMargins(0, 0, 0, dip16); + titleView.setLayoutParams(layoutParams); + mainLayout.addView(titleView); + } + + // Create content container (message/EditText) inside a ScrollView only if message or editText is provided. + ScrollView contentScrollView = null; + LinearLayout contentContainer; + if (message != null || editText != null) { + contentScrollView = new ScrollView(context); + contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar. + contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); + if (editText != null) { + ShapeDrawable scrollViewBackground = new ShapeDrawable(new RoundRectShape( + createCornerRadii(10), null, null)); + scrollViewBackground.getPaint().setColor(getEditTextBackground()); + contentScrollView.setPadding(dip8, dip8, dip8, dip8); + contentScrollView.setBackground(scrollViewBackground); + contentScrollView.setClipToOutline(true); + } + LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 0, + 1.0f // Weight to take available space. + ); + contentScrollView.setLayoutParams(contentParams); + contentContainer = new LinearLayout(context); + contentContainer.setOrientation(LinearLayout.VERTICAL); + contentScrollView.addView(contentContainer); + + // Message (if not replaced by EditText). + if (editText == null) { + TextView messageView = new TextView(context); + messageView.setText(message); // Supports Spanned (HTML). + messageView.setTextSize(16); + messageView.setTextColor(getAppForegroundColor()); + // Enable HTML link clicking if the message contains links. + if (message instanceof Spanned) { + messageView.setMovementMethod(LinkMovementMethod.getInstance()); + } + LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + messageView.setLayoutParams(messageParams); + contentContainer.addView(messageView); + } + + // EditText (if provided). + if (editText != null) { + // Remove EditText from its current parent, if any. + ViewGroup parent = (ViewGroup) editText.getParent(); + if (parent != null) { + parent.removeView(editText); + } + // Style the EditText to match the dialog theme. + editText.setTextColor(getAppForegroundColor()); + editText.setBackgroundColor(Color.TRANSPARENT); + editText.setPadding(0, 0, 0, 0); + LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + contentContainer.addView(editText, editTextParams); + } + } + + // Button container. + LinearLayout buttonContainer = new LinearLayout(context); + buttonContainer.setOrientation(LinearLayout.VERTICAL); + buttonContainer.removeAllViews(); + LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + buttonContainerParams.setMargins(0, dip16, 0, 0); + buttonContainer.setLayoutParams(buttonContainerParams); + + // Lists to track buttons. + List