Compare commits
6 commits
main
...
github/for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a73e730f | ||
|
|
6c2b836aa8 | ||
|
|
e4cfa15efc | ||
|
|
c17c4e9050 | ||
|
|
ff16079369 | ||
|
|
0ad2464cba |
1100 changed files with 98385 additions and 114274 deletions
3
.editorconfig
Normal file
3
.editorconfig
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[*.{kt,kts}]
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_standard_no-wildcard-imports = disabled
|
||||||
16
.github/workflows/build_pull_request.yml
vendored
16
.github/workflows/build_pull_request.yml
vendored
|
|
@ -2,10 +2,6 @@ name: Build pull request
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
pr:
|
|
||||||
description: "PR to build"
|
|
||||||
required: true
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
|
@ -14,14 +10,9 @@ jobs:
|
||||||
release:
|
release:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
|
||||||
ref: ${{ inputs.pr && format('refs/pull/{0}/merge', inputs.pr) || github.ref }}
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
|
|
@ -34,13 +25,12 @@ jobs:
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
|
||||||
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew :patches:buildAndroid --no-daemon
|
run: ./gradlew :patches:buildAndroid --no-daemon
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: revanced-patches
|
name: revanced-patches
|
||||||
path: patches/build/libs
|
path: patches/build/libs
|
||||||
archive: false
|
|
||||||
|
|
|
||||||
2
.github/workflows/open_pull_request.yml
vendored
2
.github/workflows/open_pull_request.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
|
|
|
||||||
5
.github/workflows/pull_strings.yml
vendored
5
.github/workflows/pull_strings.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
@ -32,8 +32,7 @@ jobs:
|
||||||
|
|
||||||
- name: Process strings
|
- name: Process strings
|
||||||
run: |
|
run: |
|
||||||
chmod -R 777 patches/src/main/resources
|
gradlew processStringsFromCrowdin
|
||||||
./gradlew processStringsFromCrowdin
|
|
||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
2
.github/workflows/push_strings.yml
vendored
2
.github/workflows/push_strings.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Process strings
|
- name: Process strings
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
|
|
@ -15,11 +15,10 @@ jobs:
|
||||||
packages: write
|
packages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
attestations: write
|
attestations: write
|
||||||
artifact-metadata: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
|
|
@ -62,7 +61,7 @@ jobs:
|
||||||
|
|
||||||
- name: Attest
|
- name: Attest
|
||||||
if: steps.release.outputs.new_release_published == 'true'
|
if: steps.release.outputs.new_release_published == 'true'
|
||||||
uses: actions/attest@v4
|
uses: actions/attest-build-provenance@v3
|
||||||
with:
|
with:
|
||||||
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
||||||
subject-path: patches/build/libs/patches-*.rvp
|
subject-path: patches/build/libs/patches-*.rvp
|
||||||
|
|
|
||||||
4
.github/workflows/update-gradle-wrapper.yml
vendored
4
.github/workflows/update-gradle-wrapper.yml
vendored
|
|
@ -10,9 +10,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Update Gradle Wrapper
|
- name: Update Gradle Wrapper
|
||||||
uses: gradle-update/update-gradle-wrapper-action@v2
|
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||||
with:
|
with:
|
||||||
target-branch: dev
|
target-branch: dev
|
||||||
|
|
|
||||||
610
CHANGELOG.md
610
CHANGELOG.md
|
|
@ -1,613 +1,3 @@
|
||||||
# [6.1.0](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.1.0) (2026-03-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757))
|
|
||||||
* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c))
|
|
||||||
* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468))
|
|
||||||
* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24))
|
|
||||||
* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7))
|
|
||||||
* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be))
|
|
||||||
|
|
||||||
# [6.1.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.3...v6.1.0-dev.4) (2026-03-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c))
|
|
||||||
* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7))
|
|
||||||
|
|
||||||
# [6.1.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.2...v6.1.0-dev.3) (2026-03-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be))
|
|
||||||
|
|
||||||
# [6.1.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.1...v6.1.0-dev.2) (2026-03-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468))
|
|
||||||
|
|
||||||
# [6.1.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.2-dev.1...v6.1.0-dev.1) (2026-03-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24))
|
|
||||||
|
|
||||||
## [6.0.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.0.2-dev.1) (2026-03-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757))
|
|
||||||
|
|
||||||
## [6.0.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.0...v6.0.1) (2026-03-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** use `prefixOrReplace` for non-matching APP_AUTHORITIES in content URL transformation ([#6801](https://github.com/ReVanced/revanced-patches/issues/6801)) ([8f6f128](https://github.com/ReVanced/revanced-patches/commit/8f6f128d718c20c56668ed3801b434a5cbb04dfd))
|
|
||||||
* **YouTube Music - Hide buttons:** Crashes on startup due to null LayoutParams ([#6799](https://github.com/ReVanced/revanced-patches/issues/6799)) ([3e32c38](https://github.com/ReVanced/revanced-patches/commit/3e32c387328b061f33b361ed022ae18e447a7904))
|
|
||||||
* **YouTube:** Use correct query parameters for DeArrow requests ([#6780](https://github.com/ReVanced/revanced-patches/issues/6780)) ([02a48e7](https://github.com/ReVanced/revanced-patches/commit/02a48e7a5f2b1ffd64a80651b49666de27ab7014))
|
|
||||||
|
|
||||||
## [6.0.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.0.1-dev.2...v6.0.1-dev.3) (2026-03-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** use `prefixOrReplace` for non-matching APP_AUTHORITIES in content URL transformation ([#6801](https://github.com/ReVanced/revanced-patches/issues/6801)) ([8f6f128](https://github.com/ReVanced/revanced-patches/commit/8f6f128d718c20c56668ed3801b434a5cbb04dfd))
|
|
||||||
|
|
||||||
## [6.0.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v6.0.1-dev.1...v6.0.1-dev.2) (2026-03-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube Music - Hide buttons:** Crashes on startup due to null LayoutParams ([#6799](https://github.com/ReVanced/revanced-patches/issues/6799)) ([3e32c38](https://github.com/ReVanced/revanced-patches/commit/3e32c387328b061f33b361ed022ae18e447a7904))
|
|
||||||
|
|
||||||
## [6.0.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v6.0.0...v6.0.1-dev.1) (2026-03-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube:** Use correct query parameters for DeArrow requests ([#6780](https://github.com/ReVanced/revanced-patches/issues/6780)) ([02a48e7](https://github.com/ReVanced/revanced-patches/commit/02a48e7a5f2b1ffd64a80651b49666de27ab7014))
|
|
||||||
|
|
||||||
# [6.0.0](https://github.com/ReVanced/revanced-patches/compare/v5.50.2...v6.0.0) (2026-03-14)
|
|
||||||
|
|
||||||
|
|
||||||
* build(Needs bump)!: Update to ReVanced Patcher v22 ([#6542](https://github.com/ReVanced/revanced-patches/issues/6542)) ([ab2ac36](https://github.com/ReVanced/revanced-patches/commit/ab2ac36e3041cda87b659924ea2b75089f0bdb6e))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Add minSdk to all extension projects ([#6778](https://github.com/ReVanced/revanced-patches/issues/6778)) ([7517f57](https://github.com/ReVanced/revanced-patches/commit/7517f57ac7a54e1c914e8dd8cc3e1aa908e28e54))
|
|
||||||
* **Check environment:** Use another (also more suitable) API to circumvent a bug ([393700f](https://github.com/ReVanced/revanced-patches/commit/393700f74ac141bfa109988202707b40d35a64ea))
|
|
||||||
* **Custom branding:** Fix defaults ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e00a99](https://github.com/ReVanced/revanced-patches/commit/3e00a99c1bb3af24f9e8420e8c7c2bbaeb003c6c))
|
|
||||||
* **Custom branding:** Resolve background playback crash with custom branded root installation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([6aba2d1](https://github.com/ReVanced/revanced-patches/commit/6aba2d127472643c346108d481513442fa9a3fde))
|
|
||||||
* **Enable debugging:** Add missing preference to log protocol buffer ([26d8a9e](https://github.com/ReVanced/revanced-patches/commit/26d8a9e5f891e08fe3c23601e8238de6a301b8df))
|
|
||||||
* Fix return type check to match method successfully ([0a73452](https://github.com/ReVanced/revanced-patches/commit/0a734528dc4407571ae1dba3e80347bc9f236e3e))
|
|
||||||
* **GmsCore support:** Handle GmsCore flavors when checking for updates ([2aa19f5](https://github.com/ReVanced/revanced-patches/commit/2aa19f5995fd050c40b15331a77d58144a5a1f69))
|
|
||||||
* **GmsCore support:** Insert check after another missing necessary context hook ([3c0c5a8](https://github.com/ReVanced/revanced-patches/commit/3c0c5a86d8e24b47b1c30bc5a7fe994240014e2d))
|
|
||||||
* **GmsCore support:** Insert check after necessary context hook ([03e8e3d](https://github.com/ReVanced/revanced-patches/commit/03e8e3d75cb3b03987299885cea5eb615a5cef23))
|
|
||||||
* **GmsCore support:** Rename MicroG GmsCore specific strings as well and rename app specific strings correctly ([c2ac1f0](https://github.com/ReVanced/revanced-patches/commit/c2ac1f04a0ac180555a9d19e7ff41525487fbc6d))
|
|
||||||
* **GmsCore support:** Try replacing in strings before prefixing to handle more edge cases ([4d94a41](https://github.com/ReVanced/revanced-patches/commit/4d94a41c46f2d4e1bf33debc95b8aa84a64964bb))
|
|
||||||
* **Hex patch:** Fix bug in implementation of Boyer-Moore algorithm ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f59323c](https://github.com/ReVanced/revanced-patches/commit/f59323c87d8da36b39e19936c8ed5c07d3903b16))
|
|
||||||
* **Hex:** Add back name, which was accidentally removed from the patch ([6a547a9](https://github.com/ReVanced/revanced-patches/commit/6a547a97e52b7914bb6602f3ecc2c6cecd50e946))
|
|
||||||
* **Instagram:** Update fingerprints for version `417.0.0.54.77` ([#6734](https://github.com/ReVanced/revanced-patches/issues/6734)) ([55f510d](https://github.com/ReVanced/revanced-patches/commit/55f510dbedd28678411b4f11d9bbdd303fa68a0d))
|
|
||||||
* Move strings to correct patch ([4dfe3fb](https://github.com/ReVanced/revanced-patches/commit/4dfe3fb08812ed572e01e58a8604c1be9e989438))
|
|
||||||
* **ProtonVPN - Remove delay:** Make it work on latest version by patching the correct class ([#6757](https://github.com/ReVanced/revanced-patches/issues/6757)) ([e0dc009](https://github.com/ReVanced/revanced-patches/commit/e0dc009780afea9c2f393c4f348cda5ca9c3cbbf))
|
|
||||||
* **Reddit clients:** Fix patching broken during patcher migration by searching for strings with contains([#6681](https://github.com/ReVanced/revanced-patches/issues/6681)) ([00da402](https://github.com/ReVanced/revanced-patches/commit/00da4027707068f06ee7041b53d1316a7b218d5d))
|
|
||||||
* Rename string keys correctly ([16e00ab](https://github.com/ReVanced/revanced-patches/commit/16e00ab4c0ff10e58adea40c7de72658788fcd97))
|
|
||||||
* **Spotify - Sanitize sharing links:** Update patch to latest app versions ([#6685](https://github.com/ReVanced/revanced-patches/issues/6685)) ([bb7448b](https://github.com/ReVanced/revanced-patches/commit/bb7448bc9d789843371d16bfccc9815662913333))
|
|
||||||
* Use correct string key ([9d55d00](https://github.com/ReVanced/revanced-patches/commit/9d55d00ff46a2cd18111a91a98dbc8e3137dd0ed))
|
|
||||||
* Use custom comparison block for strings in `anyOf` ([56a087d](https://github.com/ReVanced/revanced-patches/commit/56a087dbacf331ccadfe753cbc1ced77e318fc27))
|
|
||||||
* Use positional substitutes in strings where multiple are present ([aa8c87f](https://github.com/ReVanced/revanced-patches/commit/aa8c87f8650bd5def5f726f02be5d62d72a3007b))
|
|
||||||
* **YouTube - Enable Debugging Patch:** Use correct Protocolbuffer setting name ([#6711](https://github.com/ReVanced/revanced-patches/issues/6711)) ([f934022](https://github.com/ReVanced/revanced-patches/commit/f934022f37ba178ac23abfa9bcd59a0c12abe43f))
|
|
||||||
* **YouTube - Exit fullscreen mode:** Handle exiting fullscreen on first opened video ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88724d4](https://github.com/ReVanced/revanced-patches/commit/88724d47b13d56a90384b0a2588ba82ccdd5b101))
|
|
||||||
* **YouTube - Hide ads:** Empty space left when ads are hidden on tablets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1c2aa9](https://github.com/ReVanced/revanced-patches/commit/c1c2aa98b2d7ce900eb152bc736f3c1a5558d9fc))
|
|
||||||
* **YouTube - Hide ads:** Fix "Hide YouTube Premium promotions" hiding YouTube Doodles ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d26e352](https://github.com/ReVanced/revanced-patches/commit/d26e352850c2659a65b13ff1ba50dcd18278603a))
|
|
||||||
* **YouTube - Hide ads:** Hide new type of general ad, movie ad and web search result ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9b12dd1](https://github.com/ReVanced/revanced-patches/commit/9b12dd106546d94004c971b887ffa7627ae5a8d4))
|
|
||||||
* **YouTube - Hide ads:** Hide new type of player ad ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c97aefc](https://github.com/ReVanced/revanced-patches/commit/c97aefc272b83b522e5ac393ec41d03630cee6fb))
|
|
||||||
* **YouTube - Hide ads:** Hide video ads does not hide Shorts ads ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([8d274a7](https://github.com/ReVanced/revanced-patches/commit/8d274a7afc3abfafc2b702b27f022316c854dae6))
|
|
||||||
* **YouTube - Hide ads:** Support Hide fullscreen ads on Android 13+ devices ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([02b405e](https://github.com/ReVanced/revanced-patches/commit/02b405e6ac5beeff81c7705379e6c6eb1561270d))
|
|
||||||
* **YouTube - Hide ads:** YouTube Doodles unclickable when Hide ads is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5d45b6d](https://github.com/ReVanced/revanced-patches/commit/5d45b6da74165ca69a336aa36e90daafaaf87411))
|
|
||||||
* **YouTube - Hide end screen cards:** Resolve patching 20.31.4x ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3ff303f](https://github.com/ReVanced/revanced-patches/commit/3ff303f045c4fbda0331e3f1e9fbba50f97dedab))
|
|
||||||
* **YouTube - Hide layout components:** Ensure featured places also hide watch history shelf ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d639faf](https://github.com/ReVanced/revanced-patches/commit/d639faf71f476bcd7fffa08bfbb0e77c02450c9f))
|
|
||||||
* **YouTube - Hide layout components:** Fix certain description components not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1bf64eb](https://github.com/ReVanced/revanced-patches/commit/1bf64eb8b06435dea9cd292376c5feda6683e0a6))
|
|
||||||
* **YouTube - Hide layout components:** Fix empty space issues (subscribed channels bar, show more button, landscape mode) ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([22ef700](https://github.com/ReVanced/revanced-patches/commit/22ef7002e07df919c30e9274a2479925a4be69c0))
|
|
||||||
* **YouTube - Hide layout components:** Fix side effect of Disable translucent status bar ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5760c58](https://github.com/ReVanced/revanced-patches/commit/5760c5860ac2dc6a41821cc66f849a58e44bf3e7))
|
|
||||||
* **YouTube - Hide layout components:** Resolve "Hide community posts" not working in search results ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3153222](https://github.com/ReVanced/revanced-patches/commit/315322220d6a09814406394414bcfcff61ead786))
|
|
||||||
* **YouTube - Hide layout components:** Resolve community posts sometimes showing in player suggestions ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([828df77](https://github.com/ReVanced/revanced-patches/commit/828df77810b551c70e03d888dc0fe1555c488f51))
|
|
||||||
* **YouTube - Hide Shorts components:** Action buttons not hidden in 20.22+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a90a0b1](https://github.com/ReVanced/revanced-patches/commit/a90a0b1199e66cace3eb1b8c827314ceaf514ecf))
|
|
||||||
* **YouTube - Hide Shorts components:** Do not hide channel page headers when hiding shorts ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1246e43](https://github.com/ReVanced/revanced-patches/commit/1246e430f2104bc4a33881fa4dbb188201c02202))
|
|
||||||
* **YouTube - Hide Shorts components:** Find resource id only for 21.05+ ([63161e9](https://github.com/ReVanced/revanced-patches/commit/63161e9fb357387685294e4a80de94cb351c6713))
|
|
||||||
* **YouTube - Hide Shorts components:** Fix sound metadata label hiding other components ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([49d1f65](https://github.com/ReVanced/revanced-patches/commit/49d1f65fcae5b6732b768f6184969a6c796bc5e3))
|
|
||||||
* **YouTube - Hide Shorts components:** Hide new type of sound metadata label ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6b8d2f](https://github.com/ReVanced/revanced-patches/commit/a6b8d2f1039b7896b21826a46f3f13b32d16b51d))
|
|
||||||
* **YouTube - Hide Shorts components:** Resolve hiding Shorts not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ae69bdc](https://github.com/ReVanced/revanced-patches/commit/ae69bdc1d376a05b6854401586408cb6a9bda7eb))
|
|
||||||
* **YouTube - Loop video:** Enable loop video not working in playlist ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([06dbf7e](https://github.com/ReVanced/revanced-patches/commit/06dbf7ee80c836404e3698c9db6176da9a2ab8e1))
|
|
||||||
* **YouTube - Loop video:** Fix looping button state ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([14d0135](https://github.com/ReVanced/revanced-patches/commit/14d0135b3c41bb0c06fb8cd6569a489c41e51105))
|
|
||||||
* **YouTube - Loop video:** Wrong icon applied ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b34adf6](https://github.com/ReVanced/revanced-patches/commit/b34adf6437294b0b28500c207b5f29ddd2ed294d))
|
|
||||||
* **YouTube - Open Shorts in regular player:** Fix back behavior with 20.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([46ec3d3](https://github.com/ReVanced/revanced-patches/commit/46ec3d3bdd7d0368e1503a1b1be815eaf9b56525))
|
|
||||||
* **YouTube - Open Shorts in regular player:** Resolve back button closing app instead of exiting fullscreen ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b787c46](https://github.com/ReVanced/revanced-patches/commit/b787c469fd856dff74870fcb61bb3fc3dc5514b7))
|
|
||||||
* **YouTube - Playback speed:** Use correct extension method name ([b8b4cfb](https://github.com/ReVanced/revanced-patches/commit/b8b4cfbd016058a158364f4549e7ef6ed4d154e0))
|
|
||||||
* **YouTube - Remove background playback restrictions:** Fix background playback not working with certain offline videos ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2d098f2](https://github.com/ReVanced/revanced-patches/commit/2d098f2352b7dc1f0dc185ac65074443289ef2de))
|
|
||||||
* **YouTube - Remove viewer discretion dialog:** Not working on 20.14.43+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([64c397e](https://github.com/ReVanced/revanced-patches/commit/64c397eb1c46bdd77f2b05d03c22a841971bea81))
|
|
||||||
* **YouTube - Return YouTube Dislike:** Fix incorrect dislike counts after cancel ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ad10d76](https://github.com/ReVanced/revanced-patches/commit/ad10d760354dba1e8f470972955a706da9b85c02))
|
|
||||||
* **YouTube - ReturnYouTubeDislike:** Fix dislikes not showing with 20.31+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2033883](https://github.com/ReVanced/revanced-patches/commit/203388329484616cc83aef2c3bda38a3069839ca))
|
|
||||||
* **YouTube - Settings:** Icon not drawn correctly on some systems ([#6683](https://github.com/ReVanced/revanced-patches/issues/6683)) ([ddb6396](https://github.com/ReVanced/revanced-patches/commit/ddb6396b3f3f7a2c29b9fa171e189f9931ba0e02))
|
|
||||||
* **YouTube - SponsorBlock:** Do not show context toast when auto skipping in feed ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88157ac](https://github.com/ReVanced/revanced-patches/commit/88157ac5b791d4d56e8347203a02f5c78014235b))
|
|
||||||
* **YouTube - SponsorBlock:** Resolve segments not fetching on experimental app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2067799](https://github.com/ReVanced/revanced-patches/commit/206779942d9b4e8131c4df1acb1e7eab63ec75a0))
|
|
||||||
* **YouTube - SponsorBlock:** Show correct nested skip segment when seeking ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f5ef68b](https://github.com/ReVanced/revanced-patches/commit/f5ef68b61a5880a574f6d0f06e4b96c00daf11bb))
|
|
||||||
* **YouTube - Spoof app version:** Remove target `19.35.36` no longer supported by YouTube ([#6717](https://github.com/ReVanced/revanced-patches/issues/6717)) ([46fb366](https://github.com/ReVanced/revanced-patches/commit/46fb3669ee59534327d7c3d78e07b813d8a2badb))
|
|
||||||
* **YouTube - Spoof video streams:** Make it work on 21.x ([#6705](https://github.com/ReVanced/revanced-patches/issues/6705)) ([fdfed3c](https://github.com/ReVanced/revanced-patches/commit/fdfed3c9dd46f477c1cc1b9db0f08054ffa32293))
|
|
||||||
* **YouTube Music - Navigation bar:** Hide library tab with 8.24+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([cfcae43](https://github.com/ReVanced/revanced-patches/commit/cfcae434652b747345cb31b66748f0cc3220eb4a))
|
|
||||||
* **YouTube Music:** Prevent crash on bold icons loading ([#6712](https://github.com/ReVanced/revanced-patches/issues/6712)) ([e9bfb7c](https://github.com/ReVanced/revanced-patches/commit/e9bfb7ca9bcd1499f1abe8872999aefff10cd187))
|
|
||||||
* **YouTube:** Add back missing custom filter by adding the preference to the correct screen ([2a10489](https://github.com/ReVanced/revanced-patches/commit/2a10489a869cbab1ed01502bc6fe9330c4052e06))
|
|
||||||
* **YouTube:** Change recommended version to 20.37.48 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3dd305c](https://github.com/ReVanced/revanced-patches/commit/3dd305ca5d092144a924e150a668443b8f7ec3d8))
|
|
||||||
* **YouTube:** Changes the default values for some settings ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([dce204b](https://github.com/ReVanced/revanced-patches/commit/dce204b41beb13b675d04afea3129df73a182172))
|
|
||||||
* **YouTube:** Do not show bold icons if old settings menus is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([30bd852](https://github.com/ReVanced/revanced-patches/commit/30bd852ba5236ca25a7cc49fc23f987def27d23a))
|
|
||||||
* **YouTube:** Fix patching unsupported 20.13.41 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ed45375](https://github.com/ReVanced/revanced-patches/commit/ed453751057310a053600c4d50c87532a3f94989))
|
|
||||||
* **YouTube:** Ignore cairo flag in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([093497c](https://github.com/ReVanced/revanced-patches/commit/093497c34f7d6c431ce7958d6b0f85b9dd0373cd))
|
|
||||||
* **YouTube:** Remove 19.43.41 that YouTube no longer supports ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a8526dc](https://github.com/ReVanced/revanced-patches/commit/a8526dc8ae325b3b3d386ad1d23670b05a48da51))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add overlay buttons animation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f6fc6aa](https://github.com/ReVanced/revanced-patches/commit/f6fc6aa5ac6364dc2806e62618c300a8542b3cb0))
|
|
||||||
* **Check environment patch:** Support another ReVanced Manager debug variant package name ([e4dea68](https://github.com/ReVanced/revanced-patches/commit/e4dea682c6640ce817d5e30cfddec953fe85436f))
|
|
||||||
* **Custom branding:** Default to user-provided icon and name when provided ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f19c35e](https://github.com/ReVanced/revanced-patches/commit/f19c35e21cc77e8f6f746f7f910d520f86981dd5))
|
|
||||||
* **Enable debugging:** Allow overriding String/long/double flags in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1f91bc8](https://github.com/ReVanced/revanced-patches/commit/1f91bc8a20134c5519b8e031badfa741f7cac7a7))
|
|
||||||
* **GMX Mail:** Add `Force enable Freephone` patch ([#6650](https://github.com/ReVanced/revanced-patches/issues/6650)) ([997b5d6](https://github.com/ReVanced/revanced-patches/commit/997b5d63d1fc1684bea9e5b265f3aca53ad5fd88))
|
|
||||||
* **GMX Mail:** Add `Hide ads` and `Hide Premium upgrade button` patches ([#6583](https://github.com/ReVanced/revanced-patches/issues/6583)) ([2976ea3](https://github.com/ReVanced/revanced-patches/commit/2976ea3ddd09d26eeedf646f0a1020fa582d0ec0))
|
|
||||||
* Handle multiple branch conditionals jumping to the same instruction index ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2f7b57d](https://github.com/ReVanced/revanced-patches/commit/2f7b57d071d316985a1fec215045b6b78ede6212))
|
|
||||||
* **Instagram:** Add `Disable Reels auto-scroll` patch ([#6736](https://github.com/ReVanced/revanced-patches/issues/6736)) ([806d6c7](https://github.com/ReVanced/revanced-patches/commit/806d6c799fb67c0fb630ae954ef615ff01597b1f))
|
|
||||||
* Perform full search of free registers ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([01ef43a](https://github.com/ReVanced/revanced-patches/commit/01ef43ababdf015f1ad3edaf45445da0e72199f2))
|
|
||||||
* **Photoshop Mix:** Add `Bypass login` patch ([#6745](https://github.com/ReVanced/revanced-patches/issues/6745)) ([24caae9](https://github.com/ReVanced/revanced-patches/commit/24caae98b7b4d61b388f644cc1512438e408e6b1))
|
|
||||||
* Update YouTube & YouTube Music patches ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88d33b8](https://github.com/ReVanced/revanced-patches/commit/88d33b847de4d2ad834a4940ee257e06e3c3ad31))
|
|
||||||
* Use more informative patch error if the same APK is patched twice ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([26e5ce1](https://github.com/ReVanced/revanced-patches/commit/26e5ce1a325c2a6e78a5486d661f7750ecc792a3))
|
|
||||||
* **YouTube - Disable haptic feedback:** Add Disable tap and hold haptics setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f135122](https://github.com/ReVanced/revanced-patches/commit/f135122df1a5e6a8b822652abb2451ea4e4a3d08))
|
|
||||||
* **YouTube - Hide ads:** Add Hide player popup ads setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([487a95d](https://github.com/ReVanced/revanced-patches/commit/487a95d3efa878d9b41f1b719924c5504e0a1d0a))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide channel tab filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([0adcd8c](https://github.com/ReVanced/revanced-patches/commit/0adcd8c62e12619d5adaac5ee9886613deb53ca4))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide collapse button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1554fd9](https://github.com/ReVanced/revanced-patches/commit/1554fd916d1bcc9c67319d55b21072423926fc32))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide comments section in Home feed" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5278434](https://github.com/ReVanced/revanced-patches/commit/5278434534653ea741e67cc1e5258abb7ca0e21e))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide course progress" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1927564](https://github.com/ReVanced/revanced-patches/commit/192756443a1b2ede413e2d4ae55eed2bd9d57aac))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide explore this course" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e24762](https://github.com/ReVanced/revanced-patches/commit/3e24762c1847dfc467a5d6bf65cc1c3c0931ca0f))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide featured links", "Hide featured videos", "Hide join button", and "Hide subscribe button" options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f9e843d](https://github.com/ReVanced/revanced-patches/commit/f9e843d75641d4a87dfbe05fa8fd407ccc0345d6))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide feed flyout menu filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a93de46](https://github.com/ReVanced/revanced-patches/commit/a93de46572a7bd1ff30a1fb653e3f7afb1c67571))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide fullscreen button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b07b160](https://github.com/ReVanced/revanced-patches/commit/b07b1609e4bd9341611d6aa0194c9764616719b4))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide latest videos button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ebfdd8d](https://github.com/ReVanced/revanced-patches/commit/ebfdd8df2c5323290f6e655ebf0dd1db683f33dd))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide live chat replay button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6bd311](https://github.com/ReVanced/revanced-patches/commit/a6bd3116f97e539482c752e8e4e1b1e8e90ed464))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide quizzes" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([70b9e10](https://github.com/ReVanced/revanced-patches/commit/70b9e103aea817bed1d0972444c7b0726214c69c))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide search box trending results" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([21bf455](https://github.com/ReVanced/revanced-patches/commit/21bf455c3f61e5fd19f97a1580ecb26ac40dcdce))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide subscribed channels bar" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e41a40f](https://github.com/ReVanced/revanced-patches/commit/e41a40f0d754397f9cea09f387cc901f0397787e))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide video title" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2cfbe08](https://github.com/ReVanced/revanced-patches/commit/2cfbe08b2137b2520dd37927202a4586af8326ff))
|
|
||||||
* **YouTube - Hide layout components:** Apply hide search suggestions only to more recent app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a43c0e1](https://github.com/ReVanced/revanced-patches/commit/a43c0e111bfe290f7dec3c9b75b882ea9dc5630f))
|
|
||||||
* **YouTube - Hide layout components:** Replace "Hide search suggestions" with "Hide You may like section" ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([80f6b01](https://github.com/ReVanced/revanced-patches/commit/80f6b01c64971881bb9144cada0e91bb78b9f38d))
|
|
||||||
* **YouTube - Hide Shorts components:** Add "Hide AI button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([99aace4](https://github.com/ReVanced/revanced-patches/commit/99aace4178ccc9aeaaeb0b19cd6f520c10ef7df2))
|
|
||||||
* **YouTube - Hide Shorts components:** Add "Hide in video description" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e0a8b7b](https://github.com/ReVanced/revanced-patches/commit/e0a8b7bc59113ce57e5b8b358bad9171a4ea1f99))
|
|
||||||
* **YouTube - Navigation bar:** Add settings to hide toolbar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d72e39f](https://github.com/ReVanced/revanced-patches/commit/d72e39f2a8fc0894667546826ef07cb3edf78e50))
|
|
||||||
* **YouTube - Navigation buttons:** Add setting to use narrow navigation bar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e48a5d7](https://github.com/ReVanced/revanced-patches/commit/e48a5d76f7651b0edcdb5a9b27e596df41e9c6af))
|
|
||||||
* **YouTube - SponsorBlock:** Show skip button if player overlay controls are active ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([35ec655](https://github.com/ReVanced/revanced-patches/commit/35ec655f83ffe7ab661dca07107a74f2f9617037))
|
|
||||||
* **YouTube - Theme:** Add "Hide splash screen" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ca6e184](https://github.com/ReVanced/revanced-patches/commit/ca6e184172e67cca48ea4c70cfe6371e806dd793))
|
|
||||||
* **YouTube - Video quality:** Add Hide Premium video quality setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a2b67](https://github.com/ReVanced/revanced-patches/commit/50a2b67ef6e6382894636acdc1c2fcf7236ab4ee))
|
|
||||||
* **YouTube Music:** Add experimental support for 9.02.50 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a102d](https://github.com/ReVanced/revanced-patches/commit/50a102d8afc573936f790991381b0a8d2f8dd54d))
|
|
||||||
* **YouTube Music:** Add experimental support for 9.03.52 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d5b9c0c](https://github.com/ReVanced/revanced-patches/commit/d5b9c0c03d334ff31c9601a48a3beb1a4db98310))
|
|
||||||
* **YouTube Music:** Change recommended version to 8.37.56 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d1e7900](https://github.com/ReVanced/revanced-patches/commit/d1e7900793ceef7b53b140ba9efe25025a8aac01))
|
|
||||||
* **YouTube Music:** Support version 8.40.54 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([62f130c](https://github.com/ReVanced/revanced-patches/commit/62f130cc883d69d40c364cac45158012dd01272f))
|
|
||||||
* **YouTube Music:** Unofficial support of 8.50.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1d7cae](https://github.com/ReVanced/revanced-patches/commit/c1d7caeee2cfa425769571b0ebff2da86e709ef9))
|
|
||||||
* **YouTube:** Add experimental support for 21.02.32 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([7904b60](https://github.com/ReVanced/revanced-patches/commit/7904b60dbea526af45b4a69dc349c6250453b385))
|
|
||||||
* **YouTube:** Add experimental support for 21.03.34 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1ae36a1](https://github.com/ReVanced/revanced-patches/commit/1ae36a1cc72f0fb29d592206f74fcd40e37acaba))
|
|
||||||
* **YouTube:** Add experimental support for 21.04.221 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([66e113a](https://github.com/ReVanced/revanced-patches/commit/66e113a96639d0c99126749125adf234a9b10cab))
|
|
||||||
* **YouTube:** Add experimental support for 21.05.264 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f646c82](https://github.com/ReVanced/revanced-patches/commit/f646c820d7d6027cf013e0968189a1e2cfd9e641))
|
|
||||||
* **YouTube:** Add experimental support for 21.06.251 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([44b17d4](https://github.com/ReVanced/revanced-patches/commit/44b17d47588251b9fab5c801a49ace2ce371fa99))
|
|
||||||
* **YouTube:** Add experimental support for 21.06.257 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([abb703d](https://github.com/ReVanced/revanced-patches/commit/abb703dcb2ac96f30e699a33d3a896b775bb0851))
|
|
||||||
* **YouTube:** Add experimental support for 21.07.240 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79b0c1f](https://github.com/ReVanced/revanced-patches/commit/79b0c1f72ff5b52b162f3f861d5e10c657efa097))
|
|
||||||
* **YouTube:** Add Hide autoplay preview patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79e3955](https://github.com/ReVanced/revanced-patches/commit/79e3955fde7068eac90ae404b3869c27f17bd5f7))
|
|
||||||
* **YouTube:** Add more double tap to seek length options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([fb04071](https://github.com/ReVanced/revanced-patches/commit/fb04071528683d38913c57f628cbab64bf0ef6a4))
|
|
||||||
* **YouTube:** Remove obsolete seekbar thumbnail patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9909fc1](https://github.com/ReVanced/revanced-patches/commit/9909fc1e5d490e9edb59894d66c6a929fbaebb3b))
|
|
||||||
* **YouTube:** Support version 20.40.45 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([96c85d0](https://github.com/ReVanced/revanced-patches/commit/96c85d03712e79217dc8f97bcda5f38c0e47f064))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Deprecated APIs have been removed, and various APIs now use the updated ReVanced Patcher v22 APIs.
|
|
||||||
|
|
||||||
# [6.0.0-dev.26](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.25...v6.0.0-dev.26) (2026-03-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Add minSdk to all extension projects ([#6778](https://github.com/ReVanced/revanced-patches/issues/6778)) ([7517f57](https://github.com/ReVanced/revanced-patches/commit/7517f57ac7a54e1c914e8dd8cc3e1aa908e28e54))
|
|
||||||
|
|
||||||
# [6.0.0-dev.25](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.24...v6.0.0-dev.25) (2026-03-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Instagram:** Add `Disable Reels auto-scroll` patch ([#6736](https://github.com/ReVanced/revanced-patches/issues/6736)) ([806d6c7](https://github.com/ReVanced/revanced-patches/commit/806d6c799fb67c0fb630ae954ef615ff01597b1f))
|
|
||||||
|
|
||||||
# [6.0.0-dev.24](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.23...v6.0.0-dev.24) (2026-03-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Photoshop Mix:** Add `Bypass login` patch ([#6745](https://github.com/ReVanced/revanced-patches/issues/6745)) ([24caae9](https://github.com/ReVanced/revanced-patches/commit/24caae98b7b4d61b388f644cc1512438e408e6b1))
|
|
||||||
|
|
||||||
# [6.0.0-dev.23](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.22...v6.0.0-dev.23) (2026-03-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **ProtonVPN - Remove delay:** Make it work on latest version by patching the correct class ([#6757](https://github.com/ReVanced/revanced-patches/issues/6757)) ([e0dc009](https://github.com/ReVanced/revanced-patches/commit/e0dc009780afea9c2f393c4f348cda5ca9c3cbbf))
|
|
||||||
|
|
||||||
# [6.0.0-dev.22](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.21...v6.0.0-dev.22) (2026-03-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Settings:** Icon not drawn correctly on some systems ([#6683](https://github.com/ReVanced/revanced-patches/issues/6683)) ([ddb6396](https://github.com/ReVanced/revanced-patches/commit/ddb6396b3f3f7a2c29b9fa171e189f9931ba0e02))
|
|
||||||
|
|
||||||
# [6.0.0-dev.21](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.20...v6.0.0-dev.21) (2026-03-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Instagram:** Update fingerprints for version `417.0.0.54.77` ([#6734](https://github.com/ReVanced/revanced-patches/issues/6734)) ([55f510d](https://github.com/ReVanced/revanced-patches/commit/55f510dbedd28678411b4f11d9bbdd303fa68a0d))
|
|
||||||
* **Spotify - Sanitize sharing links:** Update patch to latest app versions ([#6685](https://github.com/ReVanced/revanced-patches/issues/6685)) ([bb7448b](https://github.com/ReVanced/revanced-patches/commit/bb7448bc9d789843371d16bfccc9815662913333))
|
|
||||||
|
|
||||||
# [6.0.0-dev.20](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.19...v6.0.0-dev.20) (2026-03-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Enable Debugging Patch:** Use correct Protocolbuffer setting name ([#6711](https://github.com/ReVanced/revanced-patches/issues/6711)) ([f934022](https://github.com/ReVanced/revanced-patches/commit/f934022f37ba178ac23abfa9bcd59a0c12abe43f))
|
|
||||||
|
|
||||||
# [6.0.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.18...v6.0.0-dev.19) (2026-03-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Hex:** Add back name, which was accidentally removed from the patch ([6a547a9](https://github.com/ReVanced/revanced-patches/commit/6a547a97e52b7914bb6602f3ecc2c6cecd50e946))
|
|
||||||
|
|
||||||
# [6.0.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.17...v6.0.0-dev.18) (2026-03-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Hide Shorts components:** Find resource id only for 21.05+ ([63161e9](https://github.com/ReVanced/revanced-patches/commit/63161e9fb357387685294e4a80de94cb351c6713))
|
|
||||||
|
|
||||||
# [6.0.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.16...v6.0.0-dev.17) (2026-03-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Spoof video streams:** Make it work on 21.x ([#6705](https://github.com/ReVanced/revanced-patches/issues/6705)) ([fdfed3c](https://github.com/ReVanced/revanced-patches/commit/fdfed3c9dd46f477c1cc1b9db0f08054ffa32293))
|
|
||||||
|
|
||||||
# [6.0.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.15...v6.0.0-dev.16) (2026-03-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Spoof app version:** Remove target `19.35.36` no longer supported by YouTube ([#6717](https://github.com/ReVanced/revanced-patches/issues/6717)) ([46fb366](https://github.com/ReVanced/revanced-patches/commit/46fb3669ee59534327d7c3d78e07b813d8a2badb))
|
|
||||||
* **YouTube Music:** Prevent crash on bold icons loading ([#6712](https://github.com/ReVanced/revanced-patches/issues/6712)) ([e9bfb7c](https://github.com/ReVanced/revanced-patches/commit/e9bfb7ca9bcd1499f1abe8872999aefff10cd187))
|
|
||||||
|
|
||||||
# [6.0.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.14...v6.0.0-dev.15) (2026-03-05)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Check environment:** Use another (also more suitable) API to circumvent a bug ([393700f](https://github.com/ReVanced/revanced-patches/commit/393700f74ac141bfa109988202707b40d35a64ea))
|
|
||||||
|
|
||||||
# [6.0.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.13...v6.0.0-dev.14) (2026-03-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube - Playback speed:** Use correct extension method name ([b8b4cfb](https://github.com/ReVanced/revanced-patches/commit/b8b4cfbd016058a158364f4549e7ef6ed4d154e0))
|
|
||||||
|
|
||||||
# [6.0.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.12...v6.0.0-dev.13) (2026-03-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Use custom comparison block for strings in `anyOf` ([56a087d](https://github.com/ReVanced/revanced-patches/commit/56a087dbacf331ccadfe753cbc1ced77e318fc27))
|
|
||||||
|
|
||||||
# [6.0.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.11...v6.0.0-dev.12) (2026-03-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Fix return type check to match method successfully ([0a73452](https://github.com/ReVanced/revanced-patches/commit/0a734528dc4407571ae1dba3e80347bc9f236e3e))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Check environment patch:** Support another ReVanced Manager debug variant package name ([e4dea68](https://github.com/ReVanced/revanced-patches/commit/e4dea682c6640ce817d5e30cfddec953fe85436f))
|
|
||||||
|
|
||||||
# [6.0.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.10...v6.0.0-dev.11) (2026-03-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Use correct string key ([9d55d00](https://github.com/ReVanced/revanced-patches/commit/9d55d00ff46a2cd18111a91a98dbc8e3137dd0ed))
|
|
||||||
|
|
||||||
# [6.0.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.9...v6.0.0-dev.10) (2026-03-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Enable debugging:** Add missing preference to log protocol buffer ([26d8a9e](https://github.com/ReVanced/revanced-patches/commit/26d8a9e5f891e08fe3c23601e8238de6a301b8df))
|
|
||||||
|
|
||||||
# [6.0.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.8...v6.0.0-dev.9) (2026-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **YouTube:** Add back missing custom filter by adding the preference to the correct screen ([2a10489](https://github.com/ReVanced/revanced-patches/commit/2a10489a869cbab1ed01502bc6fe9330c4052e06))
|
|
||||||
|
|
||||||
# [6.0.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.7...v6.0.0-dev.8) (2026-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** Try replacing in strings before prefixing to handle more edge cases ([4d94a41](https://github.com/ReVanced/revanced-patches/commit/4d94a41c46f2d4e1bf33debc95b8aa84a64964bb))
|
|
||||||
|
|
||||||
# [6.0.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.6...v6.0.0-dev.7) (2026-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Rename string keys correctly ([16e00ab](https://github.com/ReVanced/revanced-patches/commit/16e00ab4c0ff10e58adea40c7de72658788fcd97))
|
|
||||||
|
|
||||||
# [6.0.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.5...v6.0.0-dev.6) (2026-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Move strings to correct patch ([4dfe3fb](https://github.com/ReVanced/revanced-patches/commit/4dfe3fb08812ed572e01e58a8604c1be9e989438))
|
|
||||||
|
|
||||||
# [6.0.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.4...v6.0.0-dev.5) (2026-02-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Reddit clients:** Fix patching broken during patcher migration by searching for strings with contains([#6681](https://github.com/ReVanced/revanced-patches/issues/6681)) ([00da402](https://github.com/ReVanced/revanced-patches/commit/00da4027707068f06ee7041b53d1316a7b218d5d))
|
|
||||||
|
|
||||||
# [6.0.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.0.0-dev.3...v6.0.0-dev.4) (2026-02-27)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Custom branding:** Fix defaults ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e00a99](https://github.com/ReVanced/revanced-patches/commit/3e00a99c1bb3af24f9e8420e8c7c2bbaeb003c6c))
|
|
||||||
* **Custom branding:** Resolve background playback crash with custom branded root installation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([6aba2d1](https://github.com/ReVanced/revanced-patches/commit/6aba2d127472643c346108d481513442fa9a3fde))
|
|
||||||
* **Hex patch:** Fix bug in implementation of Boyer-Moore algorithm ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f59323c](https://github.com/ReVanced/revanced-patches/commit/f59323c87d8da36b39e19936c8ed5c07d3903b16))
|
|
||||||
* **YouTube - Exit fullscreen mode:** Handle exiting fullscreen on first opened video ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88724d4](https://github.com/ReVanced/revanced-patches/commit/88724d47b13d56a90384b0a2588ba82ccdd5b101))
|
|
||||||
* **YouTube - Hide ads:** Empty space left when ads are hidden on tablets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1c2aa9](https://github.com/ReVanced/revanced-patches/commit/c1c2aa98b2d7ce900eb152bc736f3c1a5558d9fc))
|
|
||||||
* **YouTube - Hide ads:** Fix "Hide YouTube Premium promotions" hiding YouTube Doodles ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d26e352](https://github.com/ReVanced/revanced-patches/commit/d26e352850c2659a65b13ff1ba50dcd18278603a))
|
|
||||||
* **YouTube - Hide ads:** Hide new type of general ad, movie ad and web search result ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9b12dd1](https://github.com/ReVanced/revanced-patches/commit/9b12dd106546d94004c971b887ffa7627ae5a8d4))
|
|
||||||
* **YouTube - Hide ads:** Hide new type of player ad ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c97aefc](https://github.com/ReVanced/revanced-patches/commit/c97aefc272b83b522e5ac393ec41d03630cee6fb))
|
|
||||||
* **YouTube - Hide ads:** Hide video ads does not hide Shorts ads ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([8d274a7](https://github.com/ReVanced/revanced-patches/commit/8d274a7afc3abfafc2b702b27f022316c854dae6))
|
|
||||||
* **YouTube - Hide ads:** Support Hide fullscreen ads on Android 13+ devices ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([02b405e](https://github.com/ReVanced/revanced-patches/commit/02b405e6ac5beeff81c7705379e6c6eb1561270d))
|
|
||||||
* **YouTube - Hide ads:** YouTube Doodles unclickable when Hide ads is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5d45b6d](https://github.com/ReVanced/revanced-patches/commit/5d45b6da74165ca69a336aa36e90daafaaf87411))
|
|
||||||
* **YouTube - Hide end screen cards:** Resolve patching 20.31.4x ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3ff303f](https://github.com/ReVanced/revanced-patches/commit/3ff303f045c4fbda0331e3f1e9fbba50f97dedab))
|
|
||||||
* **YouTube - Hide layout components:** Ensure featured places also hide watch history shelf ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d639faf](https://github.com/ReVanced/revanced-patches/commit/d639faf71f476bcd7fffa08bfbb0e77c02450c9f))
|
|
||||||
* **YouTube - Hide layout components:** Fix certain description components not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1bf64eb](https://github.com/ReVanced/revanced-patches/commit/1bf64eb8b06435dea9cd292376c5feda6683e0a6))
|
|
||||||
* **YouTube - Hide layout components:** Fix empty space issues (subscribed channels bar, show more button, landscape mode) ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([22ef700](https://github.com/ReVanced/revanced-patches/commit/22ef7002e07df919c30e9274a2479925a4be69c0))
|
|
||||||
* **YouTube - Hide layout components:** Fix side effect of Disable translucent status bar ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5760c58](https://github.com/ReVanced/revanced-patches/commit/5760c5860ac2dc6a41821cc66f849a58e44bf3e7))
|
|
||||||
* **YouTube - Hide layout components:** Resolve "Hide community posts" not working in search results ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3153222](https://github.com/ReVanced/revanced-patches/commit/315322220d6a09814406394414bcfcff61ead786))
|
|
||||||
* **YouTube - Hide layout components:** Resolve community posts sometimes showing in player suggestions ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([828df77](https://github.com/ReVanced/revanced-patches/commit/828df77810b551c70e03d888dc0fe1555c488f51))
|
|
||||||
* **YouTube - Hide Shorts components:** Action buttons not hidden in 20.22+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a90a0b1](https://github.com/ReVanced/revanced-patches/commit/a90a0b1199e66cace3eb1b8c827314ceaf514ecf))
|
|
||||||
* **YouTube - Hide Shorts components:** Do not hide channel page headers when hiding shorts ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1246e43](https://github.com/ReVanced/revanced-patches/commit/1246e430f2104bc4a33881fa4dbb188201c02202))
|
|
||||||
* **YouTube - Hide Shorts components:** Fix sound metadata label hiding other components ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([49d1f65](https://github.com/ReVanced/revanced-patches/commit/49d1f65fcae5b6732b768f6184969a6c796bc5e3))
|
|
||||||
* **YouTube - Hide Shorts components:** Hide new type of sound metadata label ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6b8d2f](https://github.com/ReVanced/revanced-patches/commit/a6b8d2f1039b7896b21826a46f3f13b32d16b51d))
|
|
||||||
* **YouTube - Hide Shorts components:** Resolve hiding Shorts not working ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ae69bdc](https://github.com/ReVanced/revanced-patches/commit/ae69bdc1d376a05b6854401586408cb6a9bda7eb))
|
|
||||||
* **YouTube - Loop video:** Enable loop video not working in playlist ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([06dbf7e](https://github.com/ReVanced/revanced-patches/commit/06dbf7ee80c836404e3698c9db6176da9a2ab8e1))
|
|
||||||
* **YouTube - Loop video:** Fix looping button state ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([14d0135](https://github.com/ReVanced/revanced-patches/commit/14d0135b3c41bb0c06fb8cd6569a489c41e51105))
|
|
||||||
* **YouTube - Loop video:** Wrong icon applied ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b34adf6](https://github.com/ReVanced/revanced-patches/commit/b34adf6437294b0b28500c207b5f29ddd2ed294d))
|
|
||||||
* **YouTube - Open Shorts in regular player:** Fix back behavior with 20.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([46ec3d3](https://github.com/ReVanced/revanced-patches/commit/46ec3d3bdd7d0368e1503a1b1be815eaf9b56525))
|
|
||||||
* **YouTube - Open Shorts in regular player:** Resolve back button closing app instead of exiting fullscreen ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b787c46](https://github.com/ReVanced/revanced-patches/commit/b787c469fd856dff74870fcb61bb3fc3dc5514b7))
|
|
||||||
* **YouTube - Remove background playback restrictions:** Fix background playback not working with certain offline videos ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2d098f2](https://github.com/ReVanced/revanced-patches/commit/2d098f2352b7dc1f0dc185ac65074443289ef2de))
|
|
||||||
* **YouTube - Remove viewer discretion dialog:** Not working on 20.14.43+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([64c397e](https://github.com/ReVanced/revanced-patches/commit/64c397eb1c46bdd77f2b05d03c22a841971bea81))
|
|
||||||
* **YouTube - Return YouTube Dislike:** Fix incorrect dislike counts after cancel ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ad10d76](https://github.com/ReVanced/revanced-patches/commit/ad10d760354dba1e8f470972955a706da9b85c02))
|
|
||||||
* **YouTube - ReturnYouTubeDislike:** Fix dislikes not showing with 20.31+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2033883](https://github.com/ReVanced/revanced-patches/commit/203388329484616cc83aef2c3bda38a3069839ca))
|
|
||||||
* **YouTube - SponsorBlock:** Do not show context toast when auto skipping in feed ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88157ac](https://github.com/ReVanced/revanced-patches/commit/88157ac5b791d4d56e8347203a02f5c78014235b))
|
|
||||||
* **YouTube - SponsorBlock:** Resolve segments not fetching on experimental app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2067799](https://github.com/ReVanced/revanced-patches/commit/206779942d9b4e8131c4df1acb1e7eab63ec75a0))
|
|
||||||
* **YouTube - SponsorBlock:** Show correct nested skip segment when seeking ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f5ef68b](https://github.com/ReVanced/revanced-patches/commit/f5ef68b61a5880a574f6d0f06e4b96c00daf11bb))
|
|
||||||
* **YouTube Music - Navigation bar:** Hide library tab with 8.24+ ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([cfcae43](https://github.com/ReVanced/revanced-patches/commit/cfcae434652b747345cb31b66748f0cc3220eb4a))
|
|
||||||
* **YouTube:** Change recommended version to 20.37.48 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3dd305c](https://github.com/ReVanced/revanced-patches/commit/3dd305ca5d092144a924e150a668443b8f7ec3d8))
|
|
||||||
* **YouTube:** Changes the default values for some settings ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([dce204b](https://github.com/ReVanced/revanced-patches/commit/dce204b41beb13b675d04afea3129df73a182172))
|
|
||||||
* **YouTube:** Do not show bold icons if old settings menus is enabled ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([30bd852](https://github.com/ReVanced/revanced-patches/commit/30bd852ba5236ca25a7cc49fc23f987def27d23a))
|
|
||||||
* **YouTube:** Fix patching unsupported 20.13.41 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ed45375](https://github.com/ReVanced/revanced-patches/commit/ed453751057310a053600c4d50c87532a3f94989))
|
|
||||||
* **YouTube:** Ignore cairo flag in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([093497c](https://github.com/ReVanced/revanced-patches/commit/093497c34f7d6c431ce7958d6b0f85b9dd0373cd))
|
|
||||||
* **YouTube:** Remove 19.43.41 that YouTube no longer supports ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a8526dc](https://github.com/ReVanced/revanced-patches/commit/a8526dc8ae325b3b3d386ad1d23670b05a48da51))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add overlay buttons animation ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f6fc6aa](https://github.com/ReVanced/revanced-patches/commit/f6fc6aa5ac6364dc2806e62618c300a8542b3cb0))
|
|
||||||
* **Custom branding:** Default to user-provided icon and name when provided ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f19c35e](https://github.com/ReVanced/revanced-patches/commit/f19c35e21cc77e8f6f746f7f910d520f86981dd5))
|
|
||||||
* **Enable debugging:** Allow overriding String/long/double flags in debug flag manager ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1f91bc8](https://github.com/ReVanced/revanced-patches/commit/1f91bc8a20134c5519b8e031badfa741f7cac7a7))
|
|
||||||
* Handle multiple branch conditionals jumping to the same instruction index ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2f7b57d](https://github.com/ReVanced/revanced-patches/commit/2f7b57d071d316985a1fec215045b6b78ede6212))
|
|
||||||
* Perform full search of free registers ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([01ef43a](https://github.com/ReVanced/revanced-patches/commit/01ef43ababdf015f1ad3edaf45445da0e72199f2))
|
|
||||||
* Update YouTube & YouTube Music patches ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([88d33b8](https://github.com/ReVanced/revanced-patches/commit/88d33b847de4d2ad834a4940ee257e06e3c3ad31))
|
|
||||||
* Use more informative patch error if the same APK is patched twice ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([26e5ce1](https://github.com/ReVanced/revanced-patches/commit/26e5ce1a325c2a6e78a5486d661f7750ecc792a3))
|
|
||||||
* **YouTube - Disable haptic feedback:** Add Disable tap and hold haptics setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f135122](https://github.com/ReVanced/revanced-patches/commit/f135122df1a5e6a8b822652abb2451ea4e4a3d08))
|
|
||||||
* **YouTube - Hide ads:** Add Hide player popup ads setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([487a95d](https://github.com/ReVanced/revanced-patches/commit/487a95d3efa878d9b41f1b719924c5504e0a1d0a))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide channel tab filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([0adcd8c](https://github.com/ReVanced/revanced-patches/commit/0adcd8c62e12619d5adaac5ee9886613deb53ca4))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide collapse button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1554fd9](https://github.com/ReVanced/revanced-patches/commit/1554fd916d1bcc9c67319d55b21072423926fc32))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide comments section in Home feed" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([5278434](https://github.com/ReVanced/revanced-patches/commit/5278434534653ea741e67cc1e5258abb7ca0e21e))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide course progress" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1927564](https://github.com/ReVanced/revanced-patches/commit/192756443a1b2ede413e2d4ae55eed2bd9d57aac))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide explore this course" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([3e24762](https://github.com/ReVanced/revanced-patches/commit/3e24762c1847dfc467a5d6bf65cc1c3c0931ca0f))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide featured links", "Hide featured videos", "Hide join button", and "Hide subscribe button" options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f9e843d](https://github.com/ReVanced/revanced-patches/commit/f9e843d75641d4a87dfbe05fa8fd407ccc0345d6))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide feed flyout menu filter" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a93de46](https://github.com/ReVanced/revanced-patches/commit/a93de46572a7bd1ff30a1fb653e3f7afb1c67571))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide fullscreen button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([b07b160](https://github.com/ReVanced/revanced-patches/commit/b07b1609e4bd9341611d6aa0194c9764616719b4))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide latest videos button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ebfdd8d](https://github.com/ReVanced/revanced-patches/commit/ebfdd8df2c5323290f6e655ebf0dd1db683f33dd))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide live chat replay button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a6bd311](https://github.com/ReVanced/revanced-patches/commit/a6bd3116f97e539482c752e8e4e1b1e8e90ed464))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide quizzes" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([70b9e10](https://github.com/ReVanced/revanced-patches/commit/70b9e103aea817bed1d0972444c7b0726214c69c))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide search box trending results" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([21bf455](https://github.com/ReVanced/revanced-patches/commit/21bf455c3f61e5fd19f97a1580ecb26ac40dcdce))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide subscribed channels bar" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e41a40f](https://github.com/ReVanced/revanced-patches/commit/e41a40f0d754397f9cea09f387cc901f0397787e))
|
|
||||||
* **YouTube - Hide layout components:** Add "Hide video title" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([2cfbe08](https://github.com/ReVanced/revanced-patches/commit/2cfbe08b2137b2520dd37927202a4586af8326ff))
|
|
||||||
* **YouTube - Hide layout components:** Apply hide search suggestions only to more recent app targets ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([a43c0e1](https://github.com/ReVanced/revanced-patches/commit/a43c0e111bfe290f7dec3c9b75b882ea9dc5630f))
|
|
||||||
* **YouTube - Hide layout components:** Replace "Hide search suggestions" with "Hide You may like section" ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([80f6b01](https://github.com/ReVanced/revanced-patches/commit/80f6b01c64971881bb9144cada0e91bb78b9f38d))
|
|
||||||
* **YouTube - Hide Shorts components:** Add "Hide AI button" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([99aace4](https://github.com/ReVanced/revanced-patches/commit/99aace4178ccc9aeaaeb0b19cd6f520c10ef7df2))
|
|
||||||
* **YouTube - Hide Shorts components:** Add "Hide in video description" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e0a8b7b](https://github.com/ReVanced/revanced-patches/commit/e0a8b7bc59113ce57e5b8b358bad9171a4ea1f99))
|
|
||||||
* **YouTube - Navigation bar:** Add settings to hide toolbar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d72e39f](https://github.com/ReVanced/revanced-patches/commit/d72e39f2a8fc0894667546826ef07cb3edf78e50))
|
|
||||||
* **YouTube - Navigation buttons:** Add setting to use narrow navigation bar buttons ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([e48a5d7](https://github.com/ReVanced/revanced-patches/commit/e48a5d76f7651b0edcdb5a9b27e596df41e9c6af))
|
|
||||||
* **YouTube - SponsorBlock:** Show skip button if player overlay controls are active ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([35ec655](https://github.com/ReVanced/revanced-patches/commit/35ec655f83ffe7ab661dca07107a74f2f9617037))
|
|
||||||
* **YouTube - Theme:** Add "Hide splash screen" setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([ca6e184](https://github.com/ReVanced/revanced-patches/commit/ca6e184172e67cca48ea4c70cfe6371e806dd793))
|
|
||||||
* **YouTube - Video quality:** Add Hide Premium video quality setting ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a2b67](https://github.com/ReVanced/revanced-patches/commit/50a2b67ef6e6382894636acdc1c2fcf7236ab4ee))
|
|
||||||
* **YouTube Music:** Add experimental support for 9.02.50 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([50a102d](https://github.com/ReVanced/revanced-patches/commit/50a102d8afc573936f790991381b0a8d2f8dd54d))
|
|
||||||
* **YouTube Music:** Add experimental support for 9.03.52 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d5b9c0c](https://github.com/ReVanced/revanced-patches/commit/d5b9c0c03d334ff31c9601a48a3beb1a4db98310))
|
|
||||||
* **YouTube Music:** Change recommended version to 8.37.56 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([d1e7900](https://github.com/ReVanced/revanced-patches/commit/d1e7900793ceef7b53b140ba9efe25025a8aac01))
|
|
||||||
* **YouTube Music:** Support version 8.40.54 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([62f130c](https://github.com/ReVanced/revanced-patches/commit/62f130cc883d69d40c364cac45158012dd01272f))
|
|
||||||
* **YouTube Music:** Unofficial support of 8.50.51 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([c1d7cae](https://github.com/ReVanced/revanced-patches/commit/c1d7caeee2cfa425769571b0ebff2da86e709ef9))
|
|
||||||
* **YouTube:** Add experimental support for 21.02.32 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([7904b60](https://github.com/ReVanced/revanced-patches/commit/7904b60dbea526af45b4a69dc349c6250453b385))
|
|
||||||
* **YouTube:** Add experimental support for 21.03.34 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([1ae36a1](https://github.com/ReVanced/revanced-patches/commit/1ae36a1cc72f0fb29d592206f74fcd40e37acaba))
|
|
||||||
* **YouTube:** Add experimental support for 21.04.221 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([66e113a](https://github.com/ReVanced/revanced-patches/commit/66e113a96639d0c99126749125adf234a9b10cab))
|
|
||||||
* **YouTube:** Add experimental support for 21.05.264 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([f646c82](https://github.com/ReVanced/revanced-patches/commit/f646c820d7d6027cf013e0968189a1e2cfd9e641))
|
|
||||||
* **YouTube:** Add experimental support for 21.06.251 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([44b17d4](https://github.com/ReVanced/revanced-patches/commit/44b17d47588251b9fab5c801a49ace2ce371fa99))
|
|
||||||
* **YouTube:** Add experimental support for 21.06.257 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([abb703d](https://github.com/ReVanced/revanced-patches/commit/abb703dcb2ac96f30e699a33d3a896b775bb0851))
|
|
||||||
* **YouTube:** Add experimental support for 21.07.240 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79b0c1f](https://github.com/ReVanced/revanced-patches/commit/79b0c1f72ff5b52b162f3f861d5e10c657efa097))
|
|
||||||
* **YouTube:** Add Hide autoplay preview patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([79e3955](https://github.com/ReVanced/revanced-patches/commit/79e3955fde7068eac90ae404b3869c27f17bd5f7))
|
|
||||||
* **YouTube:** Add more double tap to seek length options ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([fb04071](https://github.com/ReVanced/revanced-patches/commit/fb04071528683d38913c57f628cbab64bf0ef6a4))
|
|
||||||
* **YouTube:** Remove obsolete seekbar thumbnail patch ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([9909fc1](https://github.com/ReVanced/revanced-patches/commit/9909fc1e5d490e9edb59894d66c6a929fbaebb3b))
|
|
||||||
* **YouTube:** Support version 20.40.45 ([#6571](https://github.com/ReVanced/revanced-patches/issues/6571)) ([96c85d0](https://github.com/ReVanced/revanced-patches/commit/96c85d03712e79217dc8f97bcda5f38c0e47f064))
|
|
||||||
|
|
||||||
# [6.0.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.51.0-dev.2...v6.0.0-dev.1) (2026-02-27)
|
|
||||||
|
|
||||||
|
|
||||||
* build(Needs bump)!: Update to ReVanced Patcher v22 ([#6542](https://github.com/ReVanced/revanced-patches/issues/6542)) ([ab2ac36](https://github.com/ReVanced/revanced-patches/commit/ab2ac36e3041cda87b659924ea2b75089f0bdb6e))
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Deprecated APIs have been removed, and various APIs now use the updated ReVanced Patcher v22 APIs.
|
|
||||||
|
|
||||||
# [5.51.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.51.0-dev.1...v5.51.0-dev.2) (2026-02-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **GMX Mail:** Add `Hide ads` and `Hide Premium upgrade button` patches ([#6583](https://github.com/ReVanced/revanced-patches/issues/6583)) ([2976ea3](https://github.com/ReVanced/revanced-patches/commit/2976ea3ddd09d26eeedf646f0a1020fa582d0ec0))
|
|
||||||
|
|
||||||
# [5.51.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.4...v5.51.0-dev.1) (2026-02-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **GMX Mail:** Add `Force enable Freephone` patch ([#6650](https://github.com/ReVanced/revanced-patches/issues/6650)) ([997b5d6](https://github.com/ReVanced/revanced-patches/commit/997b5d63d1fc1684bea9e5b265f3aca53ad5fd88))
|
|
||||||
|
|
||||||
## [5.50.3-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.3...v5.50.3-dev.4) (2026-02-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** Insert check after another missing necessary context hook ([3c0c5a8](https://github.com/ReVanced/revanced-patches/commit/3c0c5a86d8e24b47b1c30bc5a7fe994240014e2d))
|
|
||||||
|
|
||||||
## [5.50.3-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.2...v5.50.3-dev.3) (2026-02-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** Insert check after necessary context hook ([03e8e3d](https://github.com/ReVanced/revanced-patches/commit/03e8e3d75cb3b03987299885cea5eb615a5cef23))
|
|
||||||
|
|
||||||
## [5.50.3-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.3-dev.1...v5.50.3-dev.2) (2026-02-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** Handle GmsCore flavors when checking for updates ([2aa19f5](https://github.com/ReVanced/revanced-patches/commit/2aa19f5995fd050c40b15331a77d58144a5a1f69))
|
|
||||||
* Use positional substitutes in strings where multiple are present ([aa8c87f](https://github.com/ReVanced/revanced-patches/commit/aa8c87f8650bd5def5f726f02be5d62d72a3007b))
|
|
||||||
|
|
||||||
## [5.50.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.2...v5.50.3-dev.1) (2026-02-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **GmsCore support:** Rename MicroG GmsCore specific strings as well and rename app specific strings correctly ([c2ac1f0](https://github.com/ReVanced/revanced-patches/commit/c2ac1f04a0ac180555a9d19e7ff41525487fbc6d))
|
|
||||||
|
|
||||||
## [5.50.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.1...v5.50.2) (2026-02-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Add missing patch option descriptions ([16e42a7](https://github.com/ReVanced/revanced-patches/commit/16e42a75ecbf51e06432f1f6c96758f8d9bdb771))
|
|
||||||
|
|
||||||
## [5.50.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.0...v5.50.1) (2026-02-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Fix broken release by bumping to v5.50.1 ([d416609](https://github.com/ReVanced/revanced-patches/commit/d4166092571b542925a59328d3d59fbc42eb29e3))
|
|
||||||
|
|
||||||
## [5.50.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.50.0...v5.50.1-dev.1) (2026-02-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Fix broken release by bumping to v5.50.1 ([d416609](https://github.com/ReVanced/revanced-patches/commit/d4166092571b542925a59328d3d59fbc42eb29e3))
|
|
||||||
|
|
||||||
# [5.49.0](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.49.0) (2026-02-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e))
|
|
||||||
* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](https://github.com/ReVanced/revanced-patches/commit/f681a6ffd45f05a61743e7d272cd68c4b743be42))
|
|
||||||
* **Instagram:** Make `Change link sharing domain` and `Sanitize sharing links` work with latest versions again ([#6518](https://github.com/ReVanced/revanced-patches/issues/6518)) ([85a9079](https://github.com/ReVanced/revanced-patches/commit/85a9079c25760d0329e518e379eeefe3beeea143))
|
|
||||||
* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](https://github.com/ReVanced/revanced-patches/commit/80c34b9d74a42018a0cd52b4a584ee71206bf963))
|
|
||||||
* Process strings from Crowdin to strip the app/patch prefixes again ([e566efc](https://github.com/ReVanced/revanced-patches/commit/e566efc51fca45c6284406245a360685a8e90d74))
|
|
||||||
* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](https://github.com/ReVanced/revanced-patches/commit/dc9e68ba574dd9f35cd742cb63193c5d875addde))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **FotMob:** Add `Hide ads` patch ([#6566](https://github.com/ReVanced/revanced-patches/issues/6566)) ([4b0b737](https://github.com/ReVanced/revanced-patches/commit/4b0b7374f21d13599ef2f1e2f5880e7589b0874e))
|
|
||||||
* **GmsCore support:** Reduce amount of necessary changes and add update check ([#6582](https://github.com/ReVanced/revanced-patches/issues/6582)) ([650e6a2](https://github.com/ReVanced/revanced-patches/commit/650e6a271075b57368432cd9d4294fd1ce26cceb))
|
|
||||||
* **Instagram:** Add `Disable analytics` patch ([#6531](https://github.com/ReVanced/revanced-patches/issues/6531)) ([ad92864](https://github.com/ReVanced/revanced-patches/commit/ad92864483a21d7eae7952c8f8429cde3d44e848))
|
|
||||||
* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb))
|
|
||||||
* **Kleinanzeigen:** Add `Hide PUR` patch ([#6558](https://github.com/ReVanced/revanced-patches/issues/6558)) ([4958ecf](https://github.com/ReVanced/revanced-patches/commit/4958ecf10c880e9e7f15dd2e58ebaefbf49e417a))
|
|
||||||
* **Microsoft Lens:** Remove migration to OneDrive ([#6551](https://github.com/ReVanced/revanced-patches/issues/6551)) ([e389632](https://github.com/ReVanced/revanced-patches/commit/e389632afd52403aba26b6981d098b93cea45e00))
|
|
||||||
* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c))
|
|
||||||
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5))
|
|
||||||
* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0))
|
|
||||||
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80))
|
|
||||||
|
|
||||||
# [5.50.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.7...v5.50.0-dev.8) (2026-02-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **GmsCore support:** Reduce amount of necessary changes and add update check ([#6582](https://github.com/ReVanced/revanced-patches/issues/6582)) ([650e6a2](https://github.com/ReVanced/revanced-patches/commit/650e6a271075b57368432cd9d4294fd1ce26cceb))
|
|
||||||
|
|
||||||
# [5.50.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.6...v5.50.0-dev.7) (2026-02-12)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **Instagram:** Make `Change link sharing domain` and `Sanitize sharing links` work with latest versions again ([#6518](https://github.com/ReVanced/revanced-patches/issues/6518)) ([85a9079](https://github.com/ReVanced/revanced-patches/commit/85a9079c25760d0329e518e379eeefe3beeea143))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Instagram:** Add `Disable analytics` patch ([#6531](https://github.com/ReVanced/revanced-patches/issues/6531)) ([ad92864](https://github.com/ReVanced/revanced-patches/commit/ad92864483a21d7eae7952c8f8429cde3d44e848))
|
|
||||||
* **Kleinanzeigen:** Add `Hide PUR` patch ([#6558](https://github.com/ReVanced/revanced-patches/issues/6558)) ([4958ecf](https://github.com/ReVanced/revanced-patches/commit/4958ecf10c880e9e7f15dd2e58ebaefbf49e417a))
|
|
||||||
* **Microsoft Lens:** Remove migration to OneDrive ([#6551](https://github.com/ReVanced/revanced-patches/issues/6551)) ([e389632](https://github.com/ReVanced/revanced-patches/commit/e389632afd52403aba26b6981d098b93cea45e00))
|
|
||||||
|
|
||||||
# [5.50.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.5...v5.50.0-dev.6) (2026-02-06)
|
# [5.50.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.5...v5.50.0-dev.6) (2026-02-06)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
android {
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
namespace = "app.revanced.extension"
|
||||||
minSdk = 23
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
android {
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,7 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
||||||
private static final String[] directoryColumns =
|
private static final String[] directoryColumns =
|
||||||
{"document_id", "mime_type", "_display_name", "last_modified", "flags",
|
{"document_id", "mime_type", "_display_name", "last_modified", "flags",
|
||||||
"_size", "full_path", "lstat_info"};
|
"_size", "full_path", "lstat_info"};
|
||||||
@SuppressWarnings("OctalInteger")
|
private static final int S_IFLNK = 0x8000;
|
||||||
private static final int S_IFMT = 0170000;
|
|
||||||
@SuppressWarnings("OctalInteger")
|
|
||||||
private static final int S_IFLNK = 0120000;
|
|
||||||
|
|
||||||
private String packageName;
|
private String packageName;
|
||||||
private File dataDirectory;
|
private File dataDirectory;
|
||||||
|
|
@ -50,7 +47,7 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
||||||
if (root.isDirectory()) {
|
if (root.isDirectory()) {
|
||||||
try {
|
try {
|
||||||
// Only delete recursively if the directory is not a symlink
|
// Only delete recursively if the directory is not a symlink
|
||||||
if ((Os.lstat(root.getPath()).st_mode & S_IFMT) != S_IFLNK) {
|
if ((Os.lstat(root.getPath()).st_mode & S_IFLNK) != S_IFLNK) {
|
||||||
File[] files = root.listFiles();
|
File[] files = root.listFiles();
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
|
|
@ -327,7 +324,7 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
||||||
sb.append(";");
|
sb.append(";");
|
||||||
sb.append(lstat.st_gid);
|
sb.append(lstat.st_gid);
|
||||||
// Append symlink target if it is a symlink
|
// Append symlink target if it is a symlink
|
||||||
if ((lstat.st_mode & S_IFMT) == S_IFLNK) {
|
if ((lstat.st_mode & S_IFLNK) == S_IFLNK) {
|
||||||
sb.append(";");
|
sb.append(";");
|
||||||
sb.append(Os.readlink(path));
|
sb.append(Os.readlink(path));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
android {
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
aidl = true
|
aidl = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package app.revanced.extension.play;
|
package app.revanced.extension.playintegrity;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
android {
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
android {
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,3 @@ dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 22
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,3 @@ dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,3 @@ dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:cricbuzz:stub"))
|
compileOnly(project(":extensions:cricbuzz:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 26
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ public final class SanitizeSharingLinksPatch {
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeURLString(url);
|
return sanitizer.sanitizeUrlString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ public final class SanitizeSharingLinksPatch {
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeURLString(url);
|
return sanitizer.sanitizeUrlString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package app.revanced.extension.music;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
|
|
||||||
public class VersionCheckUtils {
|
|
||||||
private static boolean isVersionOrGreater(String version) {
|
|
||||||
return Utils.getAppVersionName().compareTo(version) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final boolean IS_8_40_OR_GREATER = isVersionOrGreater("8.40.00");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -5,15 +5,14 @@ import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import app.revanced.extension.music.settings.Settings;
|
import app.revanced.extension.music.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class NavigationBarPatch {
|
public class NavigationBarPatch {
|
||||||
|
@NonNull
|
||||||
private static String lastYTNavigationEnumName = "";
|
private static String lastYTNavigationEnumName = "";
|
||||||
|
|
||||||
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||||
|
|
@ -26,7 +25,7 @@ public class NavigationBarPatch {
|
||||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
|
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideNavigationButton(View view) {
|
public static void hideNavigationButton(@NonNull View view) {
|
||||||
// Hide entire navigation bar.
|
// Hide entire navigation bar.
|
||||||
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
|
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
|
||||||
hideViewUnderCondition(true, (View) view.getParent());
|
hideViewUnderCondition(true, (View) view.getParent());
|
||||||
|
|
@ -35,7 +34,7 @@ public class NavigationBarPatch {
|
||||||
|
|
||||||
// Hide navigation buttons based on their type.
|
// Hide navigation buttons based on their type.
|
||||||
for (NavigationButton button : NavigationButton.values()) {
|
for (NavigationButton button : NavigationButton.values()) {
|
||||||
if (button.ytEnumNames.contains(lastYTNavigationEnumName)) {
|
if (button.ytEnumNames.equals(lastYTNavigationEnumName)) {
|
||||||
hideViewUnderCondition(button.hidden, view);
|
hideViewUnderCondition(button.hidden, view);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -44,41 +43,30 @@ public class NavigationBarPatch {
|
||||||
|
|
||||||
private enum NavigationButton {
|
private enum NavigationButton {
|
||||||
HOME(
|
HOME(
|
||||||
Arrays.asList(
|
"TAB_HOME",
|
||||||
"TAB_HOME"
|
|
||||||
),
|
|
||||||
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
|
||||||
),
|
),
|
||||||
SAMPLES(
|
SAMPLES(
|
||||||
Arrays.asList(
|
"TAB_SAMPLES",
|
||||||
"TAB_SAMPLES"
|
|
||||||
),
|
|
||||||
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
|
||||||
),
|
),
|
||||||
EXPLORE(
|
EXPLORE(
|
||||||
Arrays.asList(
|
"TAB_EXPLORE",
|
||||||
"TAB_EXPLORE"
|
|
||||||
),
|
|
||||||
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
|
||||||
),
|
),
|
||||||
LIBRARY(
|
LIBRARY(
|
||||||
Arrays.asList(
|
|
||||||
"LIBRARY_MUSIC",
|
"LIBRARY_MUSIC",
|
||||||
"TAB_BOOKMARK" // YouTube Music 8.24+
|
|
||||||
),
|
|
||||||
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
|
||||||
),
|
),
|
||||||
UPGRADE(
|
UPGRADE(
|
||||||
Arrays.asList(
|
"TAB_MUSIC_PREMIUM",
|
||||||
"TAB_MUSIC_PREMIUM"
|
|
||||||
),
|
|
||||||
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
|
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
private final List<String> ytEnumNames;
|
private final String ytEnumNames;
|
||||||
private final boolean hidden;
|
private final boolean hidden;
|
||||||
|
|
||||||
NavigationButton(List<String> ytEnumNames, boolean hidden) {
|
NavigationButton(@NonNull String ytEnumNames, boolean hidden) {
|
||||||
this.ytEnumNames = ytEnumNames;
|
this.ytEnumNames = ytEnumNames;
|
||||||
this.hidden = hidden;
|
this.hidden = hidden;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package app.revanced.extension.music.patches.spoof;
|
package app.revanced.extension.music.patches.spoof;
|
||||||
|
|
||||||
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
|
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
|
||||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_REEL;
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
|
||||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||||
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||||
|
|
@ -18,8 +18,8 @@ public class SpoofVideoStreamsPatch {
|
||||||
*/
|
*/
|
||||||
public static void setClientOrderToUse() {
|
public static void setClientOrderToUse() {
|
||||||
List<ClientType> availableClients = List.of(
|
List<ClientType> availableClients = List.of(
|
||||||
ANDROID_REEL,
|
|
||||||
ANDROID_VR_1_43_32,
|
ANDROID_VR_1_43_32,
|
||||||
|
ANDROID_NO_SDK,
|
||||||
VISIONOS,
|
VISIONOS,
|
||||||
ANDROID_VR_1_61_48
|
ANDROID_VR_1_61_48
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,9 @@ import android.preference.PreferenceFragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import app.revanced.extension.music.VersionCheckUtils;
|
|
||||||
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
|
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
|
||||||
import app.revanced.extension.music.settings.search.MusicSearchViewController;
|
import app.revanced.extension.music.settings.search.MusicSearchViewController;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||||
|
|
||||||
|
|
@ -24,24 +22,6 @@ public class MusicActivityHook extends BaseActivityHook {
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public static MusicSearchViewController searchViewController;
|
public static MusicSearchViewController searchViewController;
|
||||||
|
|
||||||
/**
|
|
||||||
* How much time has passed since the first launch of the app. Simple check to prevent
|
|
||||||
* forcing bold icons on first launch where the settings menu is partially broken
|
|
||||||
* due to missing icon resources the client has not yet received.
|
|
||||||
*
|
|
||||||
* @see app.revanced.extension.youtube.settings.YouTubeActivityHook#MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS
|
|
||||||
*/
|
|
||||||
private static final long MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS = 30 * 1000; // 30 seconds.
|
|
||||||
|
|
||||||
static {
|
|
||||||
final boolean useBoldIcons = VersionCheckUtils.IS_8_40_OR_GREATER
|
|
||||||
&& !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
|
|
||||||
&& (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get())
|
|
||||||
> MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS;
|
|
||||||
|
|
||||||
Utils.setAppIsUsingBoldIcons(useBoldIcons);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|
@ -66,7 +46,15 @@ public class MusicActivityHook extends BaseActivityHook {
|
||||||
// Override the default YouTube Music theme to increase start padding of list items.
|
// Override the default YouTube Music theme to increase start padding of list items.
|
||||||
// Custom style located in resources/music/values/style.xml
|
// Custom style located in resources/music/values/style.xml
|
||||||
activity.setTheme(Utils.getResourceIdentifierOrThrow(
|
activity.setTheme(Utils.getResourceIdentifierOrThrow(
|
||||||
ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings"));
|
"Theme.ReVanced.YouTubeMusic.Settings", "style"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resource ID for the YouTube Music settings layout.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected int getContentViewResourceId() {
|
||||||
|
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -135,14 +123,4 @@ public class MusicActivityHook extends BaseActivityHook {
|
||||||
public static boolean handleFinish() {
|
public static boolean handleFinish() {
|
||||||
return MusicSearchViewController.handleFinish(searchViewController);
|
return MusicSearchViewController.handleFinish(searchViewController);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
* <p>
|
|
||||||
* Decides whether to use bold icons.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static boolean useBoldIcons(boolean original) {
|
|
||||||
return Utils.appIsUsingBoldIcons();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
||||||
ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
|
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ dependencies {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 23
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,9 +2,3 @@ dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:nunl:stub"))
|
compileOnly(project(":extensions:nunl:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 26
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,3 @@ dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:primevideo:stub"))
|
compileOnly(project(":extensions:primevideo:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:reddit:stub"))
|
compileOnly(project(":extensions:reddit:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 28
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,3 @@ dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:samsung:radio:stub"))
|
compileOnly(project(":extensions:samsung:radio:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 26
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,3 @@ dependencies {
|
||||||
implementation(project(":extensions:shared:library"))
|
implementation(project(":extensions:shared:library"))
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 23
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,4 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
compileOnly(libs.protobuf.javalite)
|
|
||||||
implementation(project(":extensions:shared:protobuf", configuration = "shadowRuntimeElements"))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package app.revanced.extension.shared;
|
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.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
|
@ -17,96 +20,21 @@ import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
|
||||||
import app.revanced.extension.shared.requests.Route;
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
import app.revanced.extension.shared.requests.Route;
|
||||||
|
import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class GmsCoreSupport {
|
public class GmsCoreSupport {
|
||||||
private static GmsCore gmsCore = GmsCore.UNKNOWN;
|
private static final String GMS_CORE_PACKAGE_NAME
|
||||||
|
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||||
static {
|
private static final Uri GMS_CORE_PROVIDER
|
||||||
for (GmsCore core : GmsCore.values()) {
|
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
||||||
if (core.getGroupId().equals(getGmsCoreVendorGroupId())) {
|
|
||||||
GmsCoreSupport.gmsCore = core;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void checkGmsCore(Activity context) {
|
|
||||||
gmsCore.check(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getOriginalPackageName() {
|
|
||||||
return null; // Modified during patching.
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getGmsCoreVendorGroupId() {
|
|
||||||
return "app.revanced"; // Modified during patching.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return If the current package name is the same as the original unpatched app.
|
|
||||||
* If `GmsCore support` was not included during patching, this returns true;
|
|
||||||
*/
|
|
||||||
public static boolean isPackageNameOriginal() {
|
|
||||||
String originalPackageName = getOriginalPackageName();
|
|
||||||
return originalPackageName == null
|
|
||||||
|| originalPackageName.equals(Utils.getContext().getPackageName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum GmsCore {
|
|
||||||
REVANCED("app.revanced", "https://github.com/revanced/gmscore/releases/latest", () -> {
|
|
||||||
try {
|
|
||||||
HttpURLConnection connection = Requester.getConnectionFromRoute(
|
|
||||||
"https://api.github.com",
|
|
||||||
new Route(GET, "/repos/revanced/gmscore/releases/latest")
|
|
||||||
);
|
|
||||||
connection.setConnectTimeout(5000);
|
|
||||||
connection.setReadTimeout(5000);
|
|
||||||
|
|
||||||
int responseCode = connection.getResponseCode();
|
|
||||||
if (responseCode != 200) {
|
|
||||||
Logger.printDebug(() -> "GitHub API returned status code: " + responseCode);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the response
|
|
||||||
JSONObject releaseData = Requester.parseJSONObject(connection);
|
|
||||||
String tagName = releaseData.optString("tag_name", "");
|
|
||||||
connection.disconnect();
|
|
||||||
|
|
||||||
if (tagName.isEmpty()) {
|
|
||||||
Logger.printDebug(() -> "No tag_name found in GitHub release data");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagName.startsWith("v")) tagName = tagName.substring(1);
|
|
||||||
|
|
||||||
return tagName;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printInfo(() -> "Failed to fetch latest GmsCore version from GitHub", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
UNKNOWN(getGmsCoreVendorGroupId(), getGmsCoreVendorGroupId() + "android.gms", () -> null);
|
|
||||||
|
|
||||||
private static final String DONT_KILL_MY_APP_URL
|
private static final String DONT_KILL_MY_APP_URL
|
||||||
= "https://dontkillmyapp.com/";
|
= "https://dontkillmyapp.com/";
|
||||||
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
|
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
|
||||||
|
|
@ -120,145 +48,23 @@ public class GmsCoreSupport {
|
||||||
* If a manufacturer specific page exists on DontKillMyApp.
|
* If a manufacturer specific page exists on DontKillMyApp.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile Boolean dontKillMyAppManufacturerSupported;
|
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
||||||
|
|
||||||
private final String groupId;
|
private static String getOriginalPackageName() {
|
||||||
private final String packageName;
|
return null; // Modified during patching.
|
||||||
private final String downloadQuery;
|
|
||||||
private final GetLatestVersion getLatestVersion;
|
|
||||||
private final Uri gmsCoreProvider;
|
|
||||||
|
|
||||||
GmsCore(String groupId, String downloadQuery, GetLatestVersion getLatestVersion) {
|
|
||||||
this.groupId = groupId;
|
|
||||||
this.packageName = groupId + ".android.gms";
|
|
||||||
this.gmsCoreProvider = Uri.parse("content://" + groupId + ".android.gsf.gservices/prefix");
|
|
||||||
|
|
||||||
this.downloadQuery = downloadQuery;
|
|
||||||
this.getLatestVersion = getLatestVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getGroupId() {
|
/**
|
||||||
return groupId;
|
* @return If the current package name is the same as the original unpatched app.
|
||||||
|
* If `GmsCore support` was not included during patching, this returns true;
|
||||||
|
*/
|
||||||
|
public static boolean isPackageNameOriginal() {
|
||||||
|
String originalPackageName = getOriginalPackageName();
|
||||||
|
return originalPackageName == null
|
||||||
|
|| originalPackageName.equals(Utils.getContext().getPackageName());
|
||||||
}
|
}
|
||||||
|
|
||||||
void check(Activity context) {
|
private static void open(String queryOrLink) {
|
||||||
checkInstallation(context);
|
|
||||||
checkUpdates(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkInstallation(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.
|
|
||||||
if (isPackageNameOriginal()) {
|
|
||||||
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(packageName, 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("revanced_gms_core_toast_not_installed_message"));
|
|
||||||
|
|
||||||
open(downloadQuery);
|
|
||||||
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,
|
|
||||||
"revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
|
||||||
"revanced_gms_core_dialog_continue_text",
|
|
||||||
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if GmsCore is currently running in the background.
|
|
||||||
var client = context.getContentResolver().acquireContentProviderClient(gmsCoreProvider);
|
|
||||||
//noinspection TryFinallyCanBeTryWithResources
|
|
||||||
try {
|
|
||||||
if (client == null) {
|
|
||||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
|
||||||
checkIfDontKillMyAppSupportsManufacturer();
|
|
||||||
|
|
||||||
showBatteryOptimizationDialog(context,
|
|
||||||
"revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
|
||||||
"gmsrevanced_gms_core_log_open_website_text",
|
|
||||||
(dialog, id) -> openDontKillMyApp());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (client != null) client.close();
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkUpdates(Activity context) {
|
|
||||||
if (!BaseSettings.GMS_CORE_CHECK_UPDATES.get()) {
|
|
||||||
Logger.printDebug(() -> "GmsCore update check is disabled in settings");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
|
||||||
try {
|
|
||||||
PackageManager manager = context.getPackageManager();
|
|
||||||
var installedVersion = manager.getPackageInfo(packageName, 0).versionName;
|
|
||||||
|
|
||||||
// GmsCore adds suffixes for flavor builds. Remove the suffix for version comparison.
|
|
||||||
int suffixIndex = installedVersion.indexOf('-');
|
|
||||||
if (suffixIndex != -1)
|
|
||||||
installedVersion = installedVersion.substring(0, suffixIndex);
|
|
||||||
String finalInstalledVersion = installedVersion;
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Installed GmsCore version: " + finalInstalledVersion);
|
|
||||||
|
|
||||||
var latestVersion = getLatestVersion.get();
|
|
||||||
|
|
||||||
if (latestVersion == null || latestVersion.isEmpty()) {
|
|
||||||
Logger.printDebug(() -> "Could not get latest GmsCore version");
|
|
||||||
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.printDebug(() -> "Latest GmsCore version on GitHub: " + latestVersion);
|
|
||||||
|
|
||||||
// Compare versions
|
|
||||||
if (!installedVersion.equals(latestVersion)) {
|
|
||||||
Logger.printInfo(() -> "GmsCore update available. Installed: " + finalInstalledVersion
|
|
||||||
+ ", Latest: " + latestVersion);
|
|
||||||
|
|
||||||
showUpdateDialog(context, installedVersion, latestVersion);
|
|
||||||
} else {
|
|
||||||
Logger.printDebug(() -> "GmsCore is up to date");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printInfo(() -> "Could not check GmsCore updates", ex);
|
|
||||||
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void open(String queryOrLink) {
|
|
||||||
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||||
|
|
||||||
Intent intent;
|
Intent intent;
|
||||||
|
|
@ -278,34 +84,6 @@ public class GmsCoreSupport {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showUpdateDialog(Activity context, String installedVersion, String latestVersion) {
|
|
||||||
// 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(() -> {
|
|
||||||
try {
|
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
|
||||||
context,
|
|
||||||
str("revanced_gms_core_dialog_title"),
|
|
||||||
String.format(str("revanced_gms_core_update_available_message"), latestVersion, installedVersion),
|
|
||||||
null,
|
|
||||||
str("revanced_gms_core_dialog_open_website_text"),
|
|
||||||
() -> open(downloadQuery),
|
|
||||||
() -> {
|
|
||||||
},
|
|
||||||
str("revanced_gms_core_dialog_cancel_text"),
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
Dialog dialog = dialogPair.first;
|
|
||||||
dialog.setCancelable(true);
|
|
||||||
Utils.showDialog(context, dialog);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "Failed to show GmsCore update dialog", ex);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showBatteryOptimizationDialog(Activity context,
|
private static void showBatteryOptimizationDialog(Activity context,
|
||||||
String dialogMessageRef,
|
String dialogMessageRef,
|
||||||
String positiveButtonTextRef,
|
String positiveButtonTextRef,
|
||||||
|
|
@ -316,7 +94,7 @@ public class GmsCoreSupport {
|
||||||
// Create the custom dialog.
|
// Create the custom dialog.
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
context,
|
context,
|
||||||
str("revanced_gms_core_dialog_title"), // Title.
|
str("gms_core_dialog_title"), // Title.
|
||||||
str(dialogMessageRef), // Message.
|
str(dialogMessageRef), // Message.
|
||||||
null, // No EditText.
|
null, // No EditText.
|
||||||
str(positiveButtonTextRef), // OK button text.
|
str(positiveButtonTextRef), // OK button text.
|
||||||
|
|
@ -329,7 +107,7 @@ public class GmsCoreSupport {
|
||||||
|
|
||||||
Dialog dialog = dialogPair.first;
|
Dialog dialog = dialogPair.first;
|
||||||
|
|
||||||
// Do not set cancelable to false to allow using back button to skip the action,
|
// 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.
|
// just in case the battery change can never be satisfied.
|
||||||
dialog.setCancelable(true);
|
dialog.setCancelable(true);
|
||||||
|
|
||||||
|
|
@ -338,14 +116,83 @@ public class GmsCoreSupport {
|
||||||
}, 100);
|
}, 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.
|
||||||
|
if (isPackageNameOriginal()) {
|
||||||
|
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
|
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
||||||
private void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
||||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
intent.setData(Uri.fromParts("package", packageName, null));
|
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
|
||||||
activity.startActivityForResult(intent, 0);
|
activity.startActivityForResult(intent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkIfDontKillMyAppSupportsManufacturer() {
|
private static void checkIfDontKillMyAppSupportsManufacturer() {
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
|
|
@ -358,17 +205,17 @@ public class GmsCoreSupport {
|
||||||
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
|
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
|
||||||
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
|
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
|
||||||
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
|
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
|
||||||
dontKillMyAppManufacturerSupported = supported;
|
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
|
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
|
||||||
+ BUILD_MANUFACTURER, ex);
|
+ BUILD_MANUFACTURER, ex);
|
||||||
dontKillMyAppManufacturerSupported = null;
|
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openDontKillMyApp() {
|
private static void openDontKillMyApp() {
|
||||||
final Boolean manufacturerSupported = dontKillMyAppManufacturerSupported;
|
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
||||||
|
|
||||||
String manufacturerPageToOpen;
|
String manufacturerPageToOpen;
|
||||||
if (manufacturerSupported == null) {
|
if (manufacturerSupported == null) {
|
||||||
|
|
@ -390,23 +237,30 @@ public class GmsCoreSupport {
|
||||||
/**
|
/**
|
||||||
* @return If GmsCore is not whitelisted from battery optimizations.
|
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||||
*/
|
*/
|
||||||
private boolean batteryOptimizationsEnabled(Context context) {
|
private static boolean batteryOptimizationsEnabled(Context context) {
|
||||||
//noinspection ObsoleteSdkInt
|
//noinspection ObsoleteSdkInt
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||||
// Android 5.0 does not have battery optimization settings.
|
// Android 5.0 does not have battery optimization settings.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
return !powerManager.isIgnoringBatteryOptimizations(packageName);
|
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAndroidAutomotive(Context context) {
|
private static boolean isAndroidAutomotive(Context context) {
|
||||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
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";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
private static String getGmsCoreVendorGroupId() {
|
||||||
private interface GetLatestVersion {
|
return "app.revanced"; // Modified during patching.
|
||||||
String get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReVanced specific logger. Logging is done to standard device log (accessible through ADB),
|
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||||
* and additionally accessible through {@link LogBufferManager}.
|
* and additionally accessible thru {@link LogBufferManager}.
|
||||||
*
|
*
|
||||||
* All methods are thread safe, and are safe to call even
|
* All methods are thread safe, and are safe to call even
|
||||||
* if {@link Utils#getContext()} is not available.
|
* if {@link Utils#getContext()} is not available.
|
||||||
|
|
@ -202,7 +202,7 @@ public class Logger {
|
||||||
/**
|
/**
|
||||||
* Logs exceptions under the outer class name of the code calling this method.
|
* Logs exceptions under the outer class name of the code calling this method.
|
||||||
* <p>
|
* <p>
|
||||||
* If the calling code is showing its own error toast,
|
* If the calling code is showing it's own error toast,
|
||||||
* instead use {@link #printInfo(LogMessage, Exception)}
|
* instead use {@link #printInfo(LogMessage, Exception)}
|
||||||
*
|
*
|
||||||
* @param message log message
|
* @param message log message
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package app.revanced.extension.shared;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public enum ResourceType {
|
|
||||||
ANIM("anim"),
|
|
||||||
ANIMATOR("animator"),
|
|
||||||
ARRAY("array"),
|
|
||||||
ATTR("attr"),
|
|
||||||
BOOL("bool"),
|
|
||||||
COLOR("color"),
|
|
||||||
DIMEN("dimen"),
|
|
||||||
DRAWABLE("drawable"),
|
|
||||||
FONT("font"),
|
|
||||||
FRACTION("fraction"),
|
|
||||||
ID("id"),
|
|
||||||
INTEGER("integer"),
|
|
||||||
INTERPOLATOR("interpolator"),
|
|
||||||
LAYOUT("layout"),
|
|
||||||
MENU("menu"),
|
|
||||||
MIPMAP("mipmap"),
|
|
||||||
NAVIGATION("navigation"),
|
|
||||||
PLURALS("plurals"),
|
|
||||||
RAW("raw"),
|
|
||||||
STRING("string"),
|
|
||||||
STYLE("style"),
|
|
||||||
STYLEABLE("styleable"),
|
|
||||||
TRANSITION("transition"),
|
|
||||||
VALUES("values"),
|
|
||||||
XML("xml");
|
|
||||||
|
|
||||||
private static final Map<String, ResourceType> VALUE_MAP;
|
|
||||||
|
|
||||||
static {
|
|
||||||
ResourceType[] values = values();
|
|
||||||
VALUE_MAP = new HashMap<>(2 * values.length);
|
|
||||||
|
|
||||||
for (ResourceType type : values) {
|
|
||||||
VALUE_MAP.put(type.value, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String value;
|
|
||||||
|
|
||||||
public static ResourceType fromValue(String value) {
|
|
||||||
ResourceType type = VALUE_MAP.get(value);
|
|
||||||
if (type == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown resource type: " + value);
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceType(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -70,7 +70,7 @@ public class StringRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a StringRef object that'll not change its value
|
* Creates a StringRef object that'll not change it's value
|
||||||
*
|
*
|
||||||
* @param value value which toString() method returns when invoked on returned object
|
* @param value value which toString() method returns when invoked on returned object
|
||||||
* @return Unique StringRef instance, its value will never change
|
* @return Unique StringRef instance, its value will never change
|
||||||
|
|
@ -102,7 +102,7 @@ public class StringRef {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
if (resources == null || packageName == null) {
|
if (resources == null || packageName == null) {
|
||||||
var context = Utils.getContext();
|
Context context = Utils.getContext();
|
||||||
resources = context.getResources();
|
resources = context.getResources();
|
||||||
packageName = context.getPackageName();
|
packageName = context.getPackageName();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,18 +106,14 @@ public abstract class TrieSearch<T> {
|
||||||
* Elements not contained can collide with elements the array does contain,
|
* Elements not contained can collide with elements the array does contain,
|
||||||
* so must compare the nodes character value.
|
* so must compare the nodes character value.
|
||||||
*
|
*
|
||||||
/*
|
* Alternatively this array could be a sorted and densely packed array,
|
||||||
* Alternatively, this could be implemented as a sorted, densely packed array
|
* and lookup is done using binary search.
|
||||||
* with lookups performed via binary search.
|
* That would save a small amount of memory because there's no null children entries,
|
||||||
* This approach would save a small amount of memory by eliminating null
|
* but would give a worst case search of O(nlog(m)) where n is the number of
|
||||||
* child entries. However, it would result in a worst-case lookup time of
|
* characters in the searched text and m is the maximum size of the sorted character arrays.
|
||||||
* O(n log m), where:
|
* Using a hash table array always gives O(n) search time.
|
||||||
* - n is the number of characters in the input text, and
|
* The memory usage here is very small (all Litho filters use ~10KB of memory),
|
||||||
* - m is the maximum size of the sorted character arrays.
|
* so the more performant hash implementation is chosen.
|
||||||
* In contrast, using a hash-based array guarantees O(n) lookup time.
|
|
||||||
* Given that the total memory usage is already very small (all Litho filters
|
|
||||||
* together use approximately 10KB), the hash-based implementation is preferred
|
|
||||||
* for its superior performance.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private TrieNode<T>[] children;
|
private TrieNode<T>[] children;
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,8 @@ import java.text.Collator;
|
||||||
import java.text.Normalizer;
|
import java.text.Normalizer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
@ -61,6 +59,7 @@ import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
||||||
import app.revanced.extension.shared.ui.Dim;
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
|
@ -77,8 +76,6 @@ public class Utils {
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Boolean isDarkModeEnabled;
|
private static Boolean isDarkModeEnabled;
|
||||||
|
|
||||||
private static boolean appIsUsingBoldIcons;
|
|
||||||
|
|
||||||
// Cached Collator instance with its locale.
|
// Cached Collator instance with its locale.
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Locale cachedCollatorLocale;
|
private static Locale cachedCollatorLocale;
|
||||||
|
|
@ -134,7 +131,6 @@ public class Utils {
|
||||||
return versionName;
|
return versionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static String getApplicationName() {
|
public static String getApplicationName() {
|
||||||
if (applicationLabel == null) {
|
if (applicationLabel == null) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -152,12 +148,12 @@ public class Utils {
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its layout height and width to 1dp.
|
* Hide a view by setting its layout height and width to 1dp.
|
||||||
*
|
*
|
||||||
* @param setting The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
*/
|
*/
|
||||||
public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) {
|
public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) {
|
||||||
if (hideViewBy0dpUnderCondition(setting.get(), view)) {
|
if (hideViewBy0dpUnderCondition(condition.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +165,7 @@ public class Utils {
|
||||||
*/
|
*/
|
||||||
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
|
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
hideViewBy0dp(view);
|
hideViewByLayoutParams(view);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,33 +173,19 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its layout params to 0x0
|
* Hide a view by setting its visibility to GONE.
|
||||||
* @param view The view to hide.
|
|
||||||
*/
|
|
||||||
public static void hideViewBy0dp(View view) {
|
|
||||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
|
||||||
if (params == null)
|
|
||||||
params = new ViewGroup.LayoutParams(0, 0);
|
|
||||||
|
|
||||||
params.width = 0;
|
|
||||||
params.height = 0;
|
|
||||||
view.setLayoutParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide a view by setting its visibility as GONE.
|
|
||||||
*
|
*
|
||||||
* @param setting The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
*/
|
*/
|
||||||
public static void hideViewUnderCondition(BooleanSetting setting, View view) {
|
public static void hideViewUnderCondition(BooleanSetting condition, View view) {
|
||||||
if (hideViewUnderCondition(setting.get(), view)) {
|
if (hideViewUnderCondition(condition.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its visibility as GONE.
|
* Hide a view by setting its visibility to GONE.
|
||||||
*
|
*
|
||||||
* @param condition The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
|
|
@ -217,14 +199,14 @@ public class Utils {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) {
|
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) {
|
||||||
if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) {
|
if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) {
|
public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) {
|
||||||
if (condition) {
|
if (setting) {
|
||||||
ViewParent parent = view.getParent();
|
ViewParent parent = view.getParent();
|
||||||
if (parent instanceof ViewGroup parentGroup) {
|
if (parent instanceof ViewGroup parentGroup) {
|
||||||
parentGroup.removeView(view);
|
parentGroup.removeView(view);
|
||||||
|
|
@ -273,7 +255,7 @@ public class Utils {
|
||||||
// Could do a thread sleep, but that will trigger an exception if the thread is interrupted.
|
// Could do a thread sleep, but that will trigger an exception if the thread is interrupted.
|
||||||
meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random()));
|
meaninglessValue += Long.numberOfLeadingZeros((long) Math.exp(Math.random()));
|
||||||
}
|
}
|
||||||
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time-wasting work,
|
// Return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work,
|
||||||
// leaving an empty loop that hammers on the System.currentTimeMillis native call.
|
// leaving an empty loop that hammers on the System.currentTimeMillis native call.
|
||||||
return meaninglessValue;
|
return meaninglessValue;
|
||||||
}
|
}
|
||||||
|
|
@ -283,14 +265,12 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int indexOfFirstFound(String value, String... targets) {
|
public static int indexOfFirstFound(String value, String... targets) {
|
||||||
if (isNotEmpty(value)) {
|
|
||||||
for (String string : targets) {
|
for (String string : targets) {
|
||||||
if (!string.isEmpty()) {
|
if (!string.isEmpty()) {
|
||||||
final int indexOf = value.indexOf(string);
|
final int indexOf = value.indexOf(string);
|
||||||
if (indexOf >= 0) return indexOf;
|
if (indexOf >= 0) return indexOf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,13 +278,12 @@ public class Utils {
|
||||||
* @return zero, if the resource is not found.
|
* @return zero, if the resource is not found.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("DiscouragedApi")
|
@SuppressLint("DiscouragedApi")
|
||||||
public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
|
public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) {
|
||||||
return context.getResources().getIdentifier(resourceIdentifierName,
|
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName());
|
||||||
type == null ? null : type.value, context.getPackageName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
|
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) {
|
||||||
final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName);
|
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type);
|
||||||
if (resourceId == 0) {
|
if (resourceId == 0) {
|
||||||
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
|
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
|
||||||
+ " type: " + type);
|
+ " type: " + type);
|
||||||
|
|
@ -314,18 +293,22 @@ public class Utils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return zero, if the resource is not found.
|
* @return zero, if the resource is not found.
|
||||||
* @see #getResourceIdentifierOrThrow(ResourceType, String)
|
* @see #getResourceIdentifierOrThrow(String, String)
|
||||||
*/
|
*/
|
||||||
public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) {
|
public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) {
|
||||||
return getResourceIdentifier(getContext(), type, resourceIdentifierName);
|
return getResourceIdentifier(getContext(), resourceIdentifierName, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return zero, if the resource is not found.
|
* @return The resource identifier, or throws an exception if not found.
|
||||||
* @see #getResourceIdentifier(ResourceType, String)
|
|
||||||
*/
|
*/
|
||||||
public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) {
|
public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) {
|
||||||
return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName);
|
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type);
|
||||||
|
if (resourceId == 0) {
|
||||||
|
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
|
||||||
|
+ " type: " + type);
|
||||||
|
}
|
||||||
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getResourceString(int id) throws Resources.NotFoundException {
|
public static String getResourceString(int id) throws Resources.NotFoundException {
|
||||||
|
|
@ -333,29 +316,29 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(ResourceType.INTEGER, resourceIdentifierName));
|
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName));
|
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
//noinspection deprecation
|
//noinspection deprecation
|
||||||
return getContext().getResources().getColor(getResourceIdentifierOrThrow(ResourceType.COLOR, resourceIdentifierName));
|
return getContext().getResources().getColor(getResourceIdentifierOrThrow(resourceIdentifierName, "color"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
|
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
|
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName));
|
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MatchFilter<T> {
|
public interface MatchFilter<T> {
|
||||||
|
|
@ -366,7 +349,7 @@ public class Utils {
|
||||||
* Includes sub children.
|
* Includes sub children.
|
||||||
*/
|
*/
|
||||||
public static <R extends View> R getChildViewByResourceName(View view, String str) {
|
public static <R extends View> R getChildViewByResourceName(View view, String str) {
|
||||||
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(ResourceType.ID, str));
|
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(str, "id"));
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (R) child;
|
return (R) child;
|
||||||
}
|
}
|
||||||
|
|
@ -460,11 +443,6 @@ public class Utils {
|
||||||
clipboard.setPrimaryClip(clip);
|
clipboard.setPrimaryClip(clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isNotEmpty(@Nullable String str) {
|
|
||||||
return str != null && !str.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static boolean isTablet() {
|
public static boolean isTablet() {
|
||||||
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
||||||
}
|
}
|
||||||
|
|
@ -473,7 +451,7 @@ public class Utils {
|
||||||
private static Boolean isRightToLeftTextLayout;
|
private static Boolean isRightToLeftTextLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc.).
|
* @return If the device language uses right to left text layout (Hebrew, Arabic, etc).
|
||||||
* If this should match any ReVanced language override then instead use
|
* If this should match any ReVanced language override then instead use
|
||||||
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
|
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
|
||||||
* This is the default locale of the device, which may differ if
|
* This is the default locale of the device, which may differ if
|
||||||
|
|
@ -487,7 +465,7 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc.).
|
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc).
|
||||||
*/
|
*/
|
||||||
public static boolean isRightToLeftLocale(Locale locale) {
|
public static boolean isRightToLeftLocale(Locale locale) {
|
||||||
String displayLanguage = locale.getDisplayLanguage();
|
String displayLanguage = locale.getDisplayLanguage();
|
||||||
|
|
@ -504,7 +482,6 @@ public class Utils {
|
||||||
return getTextDirectionString(isRightToLeftLocale());
|
return getTextDirectionString(isRightToLeftLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static String getTextDirectionString(Locale locale) {
|
public static String getTextDirectionString(Locale locale) {
|
||||||
return getTextDirectionString(isRightToLeftLocale(locale));
|
return getTextDirectionString(isRightToLeftLocale(locale));
|
||||||
}
|
}
|
||||||
|
|
@ -517,7 +494,7 @@ public class Utils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the text contains at least 1 number character,
|
* @return if the text contains at least 1 number character,
|
||||||
* including any Unicode numbers such as Arabic.
|
* including any unicode numbers such as Arabic.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
public static boolean containsNumber(CharSequence text) {
|
public static boolean containsNumber(CharSequence text) {
|
||||||
|
|
@ -829,21 +806,6 @@ public class Utils {
|
||||||
window.setBackgroundDrawable(null); // Remove default dialog background
|
window.setBackgroundDrawable(null); // Remove default dialog background
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return If the unpatched app is currently using bold icons.
|
|
||||||
*/
|
|
||||||
public static boolean appIsUsingBoldIcons() {
|
|
||||||
return appIsUsingBoldIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls if ReVanced bold icons are shown in various places.
|
|
||||||
* @param boldIcons If the app is currently using bold icons.
|
|
||||||
*/
|
|
||||||
public static void setAppIsUsingBoldIcons(boolean boldIcons) {
|
|
||||||
appIsUsingBoldIcons = boldIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the theme light color used by the app.
|
* Sets the theme light color used by the app.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1149,7 +1111,7 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses {@link #adjustColorBrightness(int, float)} depending on if light or dark mode is active.
|
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
|
||||||
*/
|
*/
|
||||||
@ColorInt
|
@ColorInt
|
||||||
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
|
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
|
||||||
|
|
@ -1205,18 +1167,4 @@ public class Utils {
|
||||||
public static float clamp(float value, float lower, float upper) {
|
public static float clamp(float value, float lower, float upper) {
|
||||||
return Math.max(lower, Math.min(value, upper));
|
return Math.max(lower, Math.min(value, upper));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param maxSize The maximum number of elements to keep in the map.
|
|
||||||
* @return A {@link LinkedHashMap} that automatically evicts the oldest entry
|
|
||||||
* when the size exceeds {@code maxSize}.
|
|
||||||
*/
|
|
||||||
public static <T, V> Map<T, V> createSizeRestrictedMap(int maxSize) {
|
|
||||||
return new LinkedHashMap<>(2 * maxSize) {
|
|
||||||
@Override
|
|
||||||
protected boolean removeEldestEntry(Entry eldest) {
|
|
||||||
return size() > maxSize;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
|
@ -19,17 +19,14 @@ import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
abstract class Check {
|
abstract class Check {
|
||||||
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
|
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
|
||||||
|
|
||||||
|
|
@ -78,6 +75,7 @@ abstract class Check {
|
||||||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE);
|
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
static void issueWarning(Activity activity, Collection<Check> failedChecks) {
|
static void issueWarning(Activity activity, Collection<Check> failedChecks) {
|
||||||
final var reasons = new StringBuilder();
|
final var reasons = new StringBuilder();
|
||||||
|
|
||||||
|
|
@ -130,7 +128,7 @@ abstract class Check {
|
||||||
// Add icon to the dialog.
|
// Add icon to the dialog.
|
||||||
ImageView iconView = new ImageView(activity);
|
ImageView iconView = new ImageView(activity);
|
||||||
iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
|
iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
|
||||||
ResourceType.DRAWABLE, "revanced_ic_dialog_alert"));
|
"revanced_ic_dialog_alert", "drawable"));
|
||||||
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
|
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
|
||||||
iconView.setPadding(0, 0, 0, 0);
|
iconView.setPadding(0, 0, 0, 0);
|
||||||
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,8 @@ import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
|
@ -31,7 +27,6 @@ import static app.revanced.extension.shared.checks.PatchInfo.Build.*;
|
||||||
* <br>
|
* <br>
|
||||||
* Various indicators help to detect if the app was patched by the user.
|
* Various indicators help to detect if the app was patched by the user.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class CheckEnvironmentPatch {
|
public final class CheckEnvironmentPatch {
|
||||||
private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning();
|
private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning();
|
||||||
|
|
@ -44,7 +39,6 @@ public final class CheckEnvironmentPatch {
|
||||||
ADB((String) null),
|
ADB((String) null),
|
||||||
ROOT_MOUNT_ON_APP_STORE("com.android.vending"),
|
ROOT_MOUNT_ON_APP_STORE("com.android.vending"),
|
||||||
MANAGER("app.revanced.manager.flutter",
|
MANAGER("app.revanced.manager.flutter",
|
||||||
"app.revanced.manager.flutter.debug",
|
|
||||||
"app.revanced.manager",
|
"app.revanced.manager",
|
||||||
"app.revanced.manager.debug");
|
"app.revanced.manager.debug");
|
||||||
|
|
||||||
|
|
@ -124,7 +118,7 @@ public final class CheckEnvironmentPatch {
|
||||||
* If the build properties are different, the app was likely downloaded pre-patched or patched on another device.
|
* If the build properties are different, the app was likely downloaded pre-patched or patched on another device.
|
||||||
*/
|
*/
|
||||||
private static class CheckWasPatchedOnSameDevice extends Check {
|
private static class CheckWasPatchedOnSameDevice extends Check {
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint({"NewApi", "HardwareIds"})
|
||||||
@Override
|
@Override
|
||||||
protected Boolean check() {
|
protected Boolean check() {
|
||||||
if (PATCH_BOARD.isEmpty()) {
|
if (PATCH_BOARD.isEmpty()) {
|
||||||
|
|
@ -294,8 +288,8 @@ public final class CheckEnvironmentPatch {
|
||||||
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
||||||
Boolean timeCheckPassed = nearPatchTime.check();
|
Boolean timeCheckPassed = nearPatchTime.check();
|
||||||
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||||
// Allow installing recently patched APKs,
|
// Allow installing recently patched apks,
|
||||||
// even if the installation source is not Manager or ADB.
|
// even if the install source is not Manager or ADB.
|
||||||
Check.disableForever();
|
Check.disableForever();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
|
||||||
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
|
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
|
||||||
protected static BaseFixRedgifsApiPatch INSTANCE;
|
protected static BaseFixRedgifsApiPatch INSTANCE;
|
||||||
public abstract String getDefaultUserAgent();
|
public abstract String getDefaultUserAgent();
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,12 @@ import android.content.pm.PackageManager;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import app.revanced.extension.shared.GmsCoreSupport;
|
import app.revanced.extension.shared.GmsCoreSupport;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
|
|
@ -55,18 +52,9 @@ public class CustomBrandingPatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private static final int notificationSmallIcon;
|
||||||
private static Integer notificationSmallIcon;
|
|
||||||
|
|
||||||
private static int getNotificationSmallIcon() {
|
|
||||||
// Cannot use static initialization block otherwise cyclic references exist
|
|
||||||
// between Settings initialization and this class.
|
|
||||||
if (notificationSmallIcon == null) {
|
|
||||||
if (GmsCoreSupport.isPackageNameOriginal()) {
|
|
||||||
Logger.printDebug(() -> "App is root mounted. Not overriding small notification icon");
|
|
||||||
return notificationSmallIcon = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
static {
|
||||||
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
|
||||||
if (branding == BrandingTheme.ORIGINAL) {
|
if (branding == BrandingTheme.ORIGINAL) {
|
||||||
notificationSmallIcon = 0;
|
notificationSmallIcon = 0;
|
||||||
|
|
@ -77,14 +65,12 @@ public class CustomBrandingPatch {
|
||||||
iconName += "_custom";
|
iconName += "_custom";
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable");
|
||||||
if (notificationSmallIcon == 0) {
|
if (notificationSmallIcon == 0) {
|
||||||
Logger.printException(() -> "Could not load notification small icon");
|
Logger.printException(() -> "Could not load notification small icon");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notificationSmallIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
|
|
@ -102,9 +88,8 @@ public class CustomBrandingPatch {
|
||||||
*/
|
*/
|
||||||
public static void setNotificationIcon(Notification.Builder builder) {
|
public static void setNotificationIcon(Notification.Builder builder) {
|
||||||
try {
|
try {
|
||||||
final int smallIcon = getNotificationSmallIcon();
|
if (notificationSmallIcon != 0) {
|
||||||
if (smallIcon != 0) {
|
builder.setSmallIcon(notificationSmallIcon)
|
||||||
builder.setSmallIcon(smallIcon)
|
|
||||||
.setColor(Color.TRANSPARENT); // Remove YT red tint.
|
.setColor(Color.TRANSPARENT); // Remove YT red tint.
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|
@ -114,45 +99,12 @@ public class CustomBrandingPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
*
|
||||||
* The total number of app name aliases, including dummy aliases.
|
* The total number of app name aliases, including dummy aliases.
|
||||||
*/
|
*/
|
||||||
private static int numberOfPresetAppNames() {
|
private static int numberOfPresetAppNames() {
|
||||||
// Modified during patching, but requires a default if custom branding is excluded.
|
// Modified during patching.
|
||||||
return 1;
|
throw new IllegalStateException();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
* <p>
|
|
||||||
* If a custom icon was provided during patching.
|
|
||||||
*/
|
|
||||||
private static boolean userProvidedCustomIcon() {
|
|
||||||
// Modified during patching, but requires a default if custom branding is excluded.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
* <p>
|
|
||||||
* If a custom name was provided during patching.
|
|
||||||
*/
|
|
||||||
private static boolean userProvidedCustomName() {
|
|
||||||
// Modified during patching, but requires a default if custom branding is excluded..
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getDefaultAppNameIndex() {
|
|
||||||
return userProvidedCustomName()
|
|
||||||
? numberOfPresetAppNames()
|
|
||||||
: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BrandingTheme getDefaultIconStyle() {
|
|
||||||
return userProvidedCustomIcon()
|
|
||||||
? BrandingTheme.CUSTOM
|
|
||||||
: BrandingTheme.ROUNDED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,12 @@ public final class EnableDebuggingPatch {
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
||||||
if (LOG_FEATURE_FLAGS && value) {
|
if (LOG_FEATURE_FLAGS && value) {
|
||||||
Long flagObj = flag;
|
if (DISABLED_FEATURE_FLAGS.contains(flag)) {
|
||||||
if (DISABLED_FEATURE_FLAGS.contains(flagObj)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (featureFlags.putIfAbsent(flagObj, TRUE) == null) {
|
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
|
||||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,8 +59,6 @@ public final class EnableDebuggingPatch {
|
||||||
*/
|
*/
|
||||||
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
||||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
|
||||||
|
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
// Align the log outputs to make post processing easier.
|
// Align the log outputs to make post processing easier.
|
||||||
Logger.printDebug(() -> " double feature is enabled: " + flag
|
Logger.printDebug(() -> " double feature is enabled: " + flag
|
||||||
|
|
@ -77,8 +74,6 @@ public final class EnableDebuggingPatch {
|
||||||
*/
|
*/
|
||||||
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
||||||
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
if (DISABLED_FEATURE_FLAGS.contains(flag)) return defaultValue;
|
|
||||||
|
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> " long feature is enabled: " + flag
|
Logger.printDebug(() -> " long feature is enabled: " + flag
|
||||||
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ public final class SanitizeSharingLinksPatch {
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitize(String url) {
|
public static String sanitize(String url) {
|
||||||
if (BaseSettings.SANITIZE_SHARING_LINKS.get()) {
|
if (BaseSettings.SANITIZE_SHARED_LINKS.get()) {
|
||||||
url = sanitizer.sanitizeURLString(url);
|
url = sanitizer.sanitizeUrlString(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
|
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package app.revanced.extension.shared.patches.litho;
|
package app.revanced.extension.shared.patches.components;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
|
|
@ -13,11 +13,11 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||||
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows custom filtering using a path and optionally a proto buffer string.
|
* Allows custom filtering using a path and optionally a proto buffer string.
|
||||||
|
|
@ -25,7 +25,7 @@ import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class CustomFilter extends Filter {
|
public final class CustomFilter extends Filter {
|
||||||
|
|
||||||
private static void showInvalidSyntaxToast(String expression) {
|
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
||||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,12 +37,7 @@ public final class CustomFilter extends Filter {
|
||||||
public static final String SYNTAX_STARTS_WITH = "^";
|
public static final String SYNTAX_STARTS_WITH = "^";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional character that separates the path from an accessibility string pattern.
|
* Optional character that separates the path from a proto buffer string pattern.
|
||||||
*/
|
|
||||||
public static final String SYNTAX_ACCESSIBILITY_SYMBOL = "#";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional character that separates the path/accessibility from a proto buffer string pattern.
|
|
||||||
*/
|
*/
|
||||||
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
||||||
|
|
||||||
|
|
@ -57,21 +52,15 @@ public final class CustomFilter extends Filter {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map key is the full path including optional special characters (^, #, $),
|
// Map key is the path including optional special characters (^ and/or $)
|
||||||
// and any accessibility pattern, but does not contain any buffer patterns.
|
|
||||||
Map<String, CustomFilterGroup> result = new HashMap<>();
|
Map<String, CustomFilterGroup> result = new HashMap<>();
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile(
|
Pattern pattern = Pattern.compile(
|
||||||
"(" // Map key group.
|
"(" // map key group
|
||||||
// Optional starts with.
|
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)" // optional starts with
|
||||||
+ "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)"
|
+ "([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*)" // path
|
||||||
// Path string.
|
+ "(\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E?)" // optional buffer symbol
|
||||||
+ "([^\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + SYNTAX_BUFFER_SYMBOL + "\\E]*)"
|
+ ")" // end map key group
|
||||||
// Optional accessibility string.
|
+ "(.*)"); // optional buffer string
|
||||||
+ "(?:\\Q" + SYNTAX_ACCESSIBILITY_SYMBOL + "\\E([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*))?"
|
|
||||||
// Optional buffer string.
|
|
||||||
+ "(?:\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E(.*))?"
|
|
||||||
+ ")"); // end map key group
|
|
||||||
|
|
||||||
for (String expression : rawCustomFilterText.split("\n")) {
|
for (String expression : rawCustomFilterText.split("\n")) {
|
||||||
if (expression.isBlank()) continue;
|
if (expression.isBlank()) continue;
|
||||||
|
|
@ -85,12 +74,10 @@ public final class CustomFilter extends Filter {
|
||||||
final String mapKey = matcher.group(1);
|
final String mapKey = matcher.group(1);
|
||||||
final boolean pathStartsWith = !matcher.group(2).isEmpty();
|
final boolean pathStartsWith = !matcher.group(2).isEmpty();
|
||||||
final String path = matcher.group(3);
|
final String path = matcher.group(3);
|
||||||
final String accessibility = matcher.group(4); // null if not present
|
final boolean hasBufferSymbol = !matcher.group(4).isEmpty();
|
||||||
final String buffer = matcher.group(5); // null if not present
|
final String bufferString = matcher.group(5);
|
||||||
|
|
||||||
if (path.isBlank()
|
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
||||||
|| (accessibility != null && accessibility.isEmpty())
|
|
||||||
|| (buffer != null && buffer.isEmpty())) {
|
|
||||||
showInvalidSyntaxToast(expression);
|
showInvalidSyntaxToast(expression);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -103,13 +90,8 @@ public final class CustomFilter extends Filter {
|
||||||
group = new CustomFilterGroup(pathStartsWith, path);
|
group = new CustomFilterGroup(pathStartsWith, path);
|
||||||
result.put(mapKey, group);
|
result.put(mapKey, group);
|
||||||
}
|
}
|
||||||
|
if (hasBufferSymbol) {
|
||||||
if (accessibility != null) {
|
group.addBufferString(bufferString);
|
||||||
group.addAccessibilityString(accessibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer != null) {
|
|
||||||
group.addBufferString(buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,22 +99,14 @@ public final class CustomFilter extends Filter {
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean startsWith;
|
final boolean startsWith;
|
||||||
StringTrieSearch accessibilitySearch;
|
|
||||||
ByteTrieSearch bufferSearch;
|
ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
CustomFilterGroup(boolean startsWith, String path) {
|
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
||||||
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||||
this.startsWith = startsWith;
|
this.startsWith = startsWith;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAccessibilityString(String accessibilityString) {
|
void addBufferString(@NonNull String bufferString) {
|
||||||
if (accessibilitySearch == null) {
|
|
||||||
accessibilitySearch = new StringTrieSearch();
|
|
||||||
}
|
|
||||||
accessibilitySearch.addPattern(accessibilityString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addBufferString(String bufferString) {
|
|
||||||
if (bufferSearch == null) {
|
if (bufferSearch == null) {
|
||||||
bufferSearch = new ByteTrieSearch();
|
bufferSearch = new ByteTrieSearch();
|
||||||
}
|
}
|
||||||
|
|
@ -144,11 +118,6 @@ public final class CustomFilter extends Filter {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append("CustomFilterGroup{");
|
builder.append("CustomFilterGroup{");
|
||||||
if (accessibilitySearch != null) {
|
|
||||||
builder.append(", accessibility=");
|
|
||||||
builder.append(accessibilitySearch.getPatterns());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append("path=");
|
builder.append("path=");
|
||||||
if (startsWith) builder.append(SYNTAX_STARTS_WITH);
|
if (startsWith) builder.append(SYNTAX_STARTS_WITH);
|
||||||
builder.append(filters[0]);
|
builder.append(filters[0]);
|
||||||
|
|
@ -178,26 +147,18 @@ public final class CustomFilter extends Filter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// All callbacks are custom filter groups.
|
// All callbacks are custom filter groups.
|
||||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||||
|
|
||||||
// Check path start requirement.
|
|
||||||
if (custom.startsWith && contentIndex != 0) {
|
if (custom.startsWith && contentIndex != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check accessibility string if specified.
|
if (custom.bufferSearch == null) {
|
||||||
if (custom.accessibilitySearch != null && !custom.accessibilitySearch.matches(accessibility)) {
|
return true; // No buffer filter, only path filtering.
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check buffer if specified.
|
return custom.bufferSearch.matches(buffer);
|
||||||
if (custom.bufferSearch != null && !custom.bufferSearch.matches(buffer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true; // All custom filter conditions passed.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
package app.revanced.extension.shared.patches.litho;
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters litho based components.
|
* Filters litho based components.
|
||||||
*
|
*
|
||||||
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
|
||||||
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
* and {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||||
*
|
*
|
||||||
* To filter {@link FilterContentType#PROTOBUFFER} or {@link FilterContentType#ACCESSIBILITY}, first add a callback to
|
* To filter {@link FilterContentType#PROTOBUFFER}, first add a callback to
|
||||||
* either an identifier or a path.
|
* either an identifier or a path.
|
||||||
* Then inside {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
||||||
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||||
*
|
*
|
||||||
|
|
@ -26,7 +26,6 @@ public abstract class Filter {
|
||||||
public enum FilterContentType {
|
public enum FilterContentType {
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
PATH,
|
PATH,
|
||||||
ACCESSIBILITY,
|
|
||||||
PROTOBUFFER
|
PROTOBUFFER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,15 +33,15 @@ public abstract class Filter {
|
||||||
* Identifier callbacks. Do not add to this instance,
|
* Identifier callbacks. Do not add to this instance,
|
||||||
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
|
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
|
||||||
*/
|
*/
|
||||||
public final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
|
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
|
||||||
/**
|
/**
|
||||||
* Path callbacks. Do not add to this instance,
|
* Path callbacks. Do not add to this instance,
|
||||||
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
|
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
|
||||||
*/
|
*/
|
||||||
public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* if any of the groups are found.
|
* if any of the groups are found.
|
||||||
*/
|
*/
|
||||||
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
|
||||||
|
|
@ -50,7 +49,7 @@ public abstract class Filter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds callbacks to {@link #isFiltered(String, String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
* if any of the groups are found.
|
* if any of the groups are found.
|
||||||
*/
|
*/
|
||||||
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
protected final void addPathCallbacks(StringFilterGroup... groups) {
|
||||||
|
|
@ -64,15 +63,12 @@ public abstract class Filter {
|
||||||
* <p>
|
* <p>
|
||||||
* Method is called off the main thread.
|
* Method is called off the main thread.
|
||||||
*
|
*
|
||||||
* @param identifier Litho identifier.
|
|
||||||
* @param accessibility Accessibility string, or an empty string if not present for the component.
|
|
||||||
* @param buffer Protocol buffer.
|
|
||||||
* @param matchedGroup The actual filter that matched.
|
* @param matchedGroup The actual filter that matched.
|
||||||
* @param contentType The type of content matched.
|
* @param contentType The type of content matched.
|
||||||
* @param contentIndex Matched index of the identifier or path.
|
* @param contentIndex Matched index of the identifier or path.
|
||||||
* @return True if the litho component should be filtered out.
|
* @return True if the litho component should be filtered out.
|
||||||
*/
|
*/
|
||||||
public boolean isFiltered(String identifier, String accessibility, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public abstract class FilterGroup<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final BooleanSetting setting;
|
protected final BooleanSetting setting;
|
||||||
public final T[] filters;
|
protected final T[] filters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new filter group.
|
* Initialize a new filter group.
|
||||||
|
|
@ -122,7 +122,7 @@ public abstract class FilterGroup<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you have more than 1 filter patterns, then all instances of
|
* If you have more than 1 filter patterns, then all instances of
|
||||||
* this class should be filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||||
* which uses a prefix tree to give better performance.
|
* which uses a prefix tree to give better performance.
|
||||||
*/
|
*/
|
||||||
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||||
|
|
@ -149,7 +149,7 @@ public abstract class FilterGroup<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] createFailurePattern(byte[] pattern) {
|
private static int[] createFailurePattern(byte[] pattern) {
|
||||||
// Computes the failure function using a bootstrapping process,
|
// Computes the failure function using a boot-strapping process,
|
||||||
// where the pattern is matched against itself.
|
// where the pattern is matched against itself.
|
||||||
final int patternLength = pattern.length;
|
final int patternLength = pattern.length;
|
||||||
final int[] failure = new int[patternLength];
|
final int[] failure = new int[patternLength];
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
package app.revanced.extension.shared.patches.litho;
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
import app.revanced.extension.shared.TrieSearch;
|
import app.revanced.extension.shared.TrieSearch;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||||
|
|
||||||
private final List<T> filterGroups = new ArrayList<>();
|
private final List<T> filterGroups = new ArrayList<>();
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LithoFilterPatch {
|
public final class LithoFilterPatch {
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,14 +21,11 @@ public final class LithoFilterPatch {
|
||||||
private static final class LithoFilterParameters {
|
private static final class LithoFilterParameters {
|
||||||
final String identifier;
|
final String identifier;
|
||||||
final String path;
|
final String path;
|
||||||
final String accessibility;
|
|
||||||
final byte[] buffer;
|
final byte[] buffer;
|
||||||
|
|
||||||
LithoFilterParameters(String lithoIdentifier, String lithoPath,
|
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
|
||||||
String accessibility, byte[] buffer) {
|
|
||||||
this.identifier = lithoIdentifier;
|
this.identifier = lithoIdentifier;
|
||||||
this.path = lithoPath;
|
this.path = lithoPath;
|
||||||
this.accessibility = accessibility;
|
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,14 +36,9 @@ public final class LithoFilterPatch {
|
||||||
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
||||||
builder.append( "ID: ");
|
builder.append( "ID: ");
|
||||||
builder.append(identifier);
|
builder.append(identifier);
|
||||||
if (!accessibility.isEmpty()) {
|
|
||||||
// AccessibilityId and AccessibilityText are pieces of BufferStrings.
|
|
||||||
builder.append(" Accessibility: ");
|
|
||||||
builder.append(accessibility);
|
|
||||||
}
|
|
||||||
builder.append(" Path: ");
|
builder.append(" Path: ");
|
||||||
builder.append(path);
|
builder.append(path);
|
||||||
if (YouTubeAndMusicSettings.DEBUG_PROTOCOLBUFFER.get()) {
|
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||||
builder.append(" BufferStrings: ");
|
builder.append(" BufferStrings: ");
|
||||||
findAsciiStrings(builder, buffer);
|
findAsciiStrings(builder, buffer);
|
||||||
}
|
}
|
||||||
|
|
@ -86,16 +75,6 @@ public final class LithoFilterPatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder for actual filters.
|
|
||||||
*/
|
|
||||||
private static final class DummyFilter extends Filter {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Filter[] filters = new Filter[]{
|
|
||||||
new DummyFilter() // Replaced during patching, do not touch.
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Litho layout fixed thread pool size override.
|
* Litho layout fixed thread pool size override.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -113,57 +92,26 @@ public final class LithoFilterPatch {
|
||||||
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
|
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For YouTube 20.22+, this is set to true by a patch,
|
* Placeholder for actual filters.
|
||||||
* because it cannot use the thread buffer due to the buffer frequently not being correct,
|
|
||||||
* especially for components that are recreated such as dragging off-screen then back on screen.
|
|
||||||
* Instead, parse the identifier found near the start of the buffer and use that to
|
|
||||||
* identify the correct buffer to use when filtering.
|
|
||||||
* <p>
|
|
||||||
* <b>This is set during patching, do not change manually.</b>
|
|
||||||
*/
|
*/
|
||||||
private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = false;
|
private static final class DummyFilter extends Filter { }
|
||||||
|
|
||||||
/**
|
private static final Filter[] filters = new Filter[] {
|
||||||
* Turns on additional logging, used for development purposes only.
|
new DummyFilter() // Replaced patching, do not touch.
|
||||||
*/
|
};
|
||||||
public static final boolean DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String suffix for components.
|
|
||||||
* Can be any of: ".eml", ".eml-fe", ".e-b", ".eml-js", "e-js-b"
|
|
||||||
*/
|
|
||||||
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = ".e".getBytes(StandardCharsets.US_ASCII);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used as placeholder for litho id/path filters that do not use a buffer
|
|
||||||
*/
|
|
||||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Because litho filtering is multithreaded and the buffer is passed in from a different injection point,
|
|
||||||
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
|
||||||
* Used for 20.21 and lower.
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier to protocol buffer mapping. Only used for 20.22+.
|
|
||||||
* Thread local is needed because filtering is multithreaded and each thread can load
|
|
||||||
* a different component with the same identifier.
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global shared buffer. Used only if the buffer is not found in the ThreadLocal.
|
|
||||||
*/
|
|
||||||
private static final Map<String, byte[]> identifierToBufferGlobal
|
|
||||||
= Collections.synchronizedMap(createIdentifierToBufferMap());
|
|
||||||
|
|
||||||
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
||||||
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||||
|
|
||||||
static {
|
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
|
||||||
|
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
|
||||||
|
|
||||||
|
static {
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
filterUsingCallbacks(identifierSearchTree, filter,
|
filterUsingCallbacks(identifierSearchTree, filter,
|
||||||
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
||||||
|
|
@ -195,13 +143,16 @@ public final class LithoFilterPatch {
|
||||||
|
|
||||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||||
parameters.accessibility, parameters.path, parameters.buffer,
|
parameters.path, parameters.buffer, group, type, matchedStartIndex);
|
||||||
group, type, matchedStartIndex);
|
|
||||||
|
|
||||||
if (isFiltered && BaseSettings.DEBUG.get()) {
|
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||||
Logger.printDebug(() -> type == Filter.FilterContentType.IDENTIFIER
|
if (type == Filter.FilterContentType.IDENTIFIER) {
|
||||||
? filterSimpleName + " filtered identifier: " + parameters.identifier
|
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||||
: filterSimpleName + " filtered path: " + parameters.path);
|
+ " identifier: " + parameters.identifier);
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||||
|
+ " path: " + parameters.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isFiltered;
|
return isFiltered;
|
||||||
|
|
@ -211,119 +162,16 @@ public final class LithoFilterPatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, byte[]> createIdentifierToBufferMap() {
|
|
||||||
// It's unclear how many items should be cached. This is a guess.
|
|
||||||
return Utils.createSizeRestrictedMap(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function that differs from {@link Character#isDigit(char)}
|
|
||||||
* as this only matches ascii and not Unicode numbers.
|
|
||||||
*/
|
|
||||||
private static boolean isAsciiNumber(byte character) {
|
|
||||||
return '0' <= character && character <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAsciiLowerCaseLetter(byte character) {
|
|
||||||
return 'a' <= character && character <= 'z';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
* Targets 20.22+
|
* Targets 20.22+
|
||||||
*/
|
*/
|
||||||
public static void setProtoBuffer(byte[] buffer) {
|
public static void setProtoBuffer(byte[] buffer) {
|
||||||
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
||||||
StringBuilder builder = new StringBuilder();
|
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
||||||
LithoFilterParameters.findAsciiStrings(builder, buffer);
|
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
||||||
Logger.printDebug(() -> "New buffer: " + builder);
|
// or when the calling thread eventually dies.
|
||||||
}
|
bufferThreadLocal.set(buffer);
|
||||||
|
|
||||||
// The identifier always seems to start very close to the buffer start.
|
|
||||||
// Highest identifier start index ever observed is 50, with most around 30 to 40.
|
|
||||||
// The buffer can be very large with up to 200kb has been observed,
|
|
||||||
// so the search is restricted to only the start.
|
|
||||||
final int maxBufferStartIndex = 500; // 10x expected upper bound.
|
|
||||||
|
|
||||||
// Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of
|
|
||||||
// unique characters, but it seems to be slower since the extra overhead of checking the
|
|
||||||
// bad character array negates any performance gain of skipping a few extra subsearches.
|
|
||||||
int emlIndex = -1;
|
|
||||||
final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length;
|
|
||||||
final int lastBufferIndexToCheckFrom = Math.min(maxBufferStartIndex, buffer.length - emlStringLength);
|
|
||||||
for (int i = 0; i < lastBufferIndexToCheckFrom; i++) {
|
|
||||||
boolean match = true;
|
|
||||||
for (int j = 0; j < emlStringLength; j++) {
|
|
||||||
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
|
|
||||||
match = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match) {
|
|
||||||
emlIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emlIndex < 0) {
|
|
||||||
// Buffer is not used for creating a new litho component.
|
|
||||||
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
|
||||||
Logger.printDebug(() -> "Could not find eml index");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startIndex = emlIndex - 1;
|
|
||||||
while (startIndex > 0) {
|
|
||||||
final byte character = buffer[startIndex];
|
|
||||||
int startIndexFinal = startIndex;
|
|
||||||
if (isAsciiLowerCaseLetter(character) || isAsciiNumber(character) || character == '_') {
|
|
||||||
// Valid character for the first path element.
|
|
||||||
startIndex--;
|
|
||||||
} else {
|
|
||||||
startIndex++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip away any numbers on the start of the identifier, which can
|
|
||||||
// be from random data in the buffer before the identifier starts.
|
|
||||||
while (true) {
|
|
||||||
final byte character = buffer[startIndex];
|
|
||||||
if (isAsciiNumber(character)) {
|
|
||||||
startIndex++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the pipe character after the identifier.
|
|
||||||
int endIndex = -1;
|
|
||||||
for (int i = emlIndex, length = buffer.length; i < length; i++) {
|
|
||||||
if (buffer[i] == '|') {
|
|
||||||
endIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (endIndex < 0) {
|
|
||||||
if (BaseSettings.DEBUG.get()) {
|
|
||||||
Logger.printException(() -> "Debug: Could not find buffer identifier");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String identifier = new String(buffer, startIndex, endIndex - startIndex, StandardCharsets.US_ASCII);
|
|
||||||
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
|
||||||
Logger.printDebug(() -> "Found buffer for identifier: " + identifier);
|
|
||||||
}
|
|
||||||
identifierToBufferGlobal.put(identifier, buffer);
|
|
||||||
|
|
||||||
Map<String, byte[]> map = identifierToBufferThread.get();
|
|
||||||
if (map == null) {
|
|
||||||
map = createIdentifierToBufferMap();
|
|
||||||
identifierToBufferThread.set(map);
|
|
||||||
}
|
|
||||||
map.put(identifier, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -331,81 +179,46 @@ public final class LithoFilterPatch {
|
||||||
* Targets 20.21 and lower.
|
* Targets 20.21 and lower.
|
||||||
*/
|
*/
|
||||||
public static void setProtoBuffer(@Nullable ByteBuffer buffer) {
|
public static void setProtoBuffer(@Nullable ByteBuffer buffer) {
|
||||||
|
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
||||||
|
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
||||||
|
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
||||||
|
// or when the calling thread eventually dies.
|
||||||
if (buffer == null || !buffer.hasArray()) {
|
if (buffer == null || !buffer.hasArray()) {
|
||||||
// It appears the buffer can be cleared out just before the call to #filter()
|
// It appears the buffer can be cleared out just before the call to #filter()
|
||||||
// Ignore this null value and retain the last buffer that was set.
|
// Ignore this null value and retain the last buffer that was set.
|
||||||
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
|
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
|
||||||
} else {
|
} else {
|
||||||
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
setProtoBuffer(buffer.array());
|
||||||
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
|
||||||
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
|
||||||
// or when the calling thread eventually dies.
|
|
||||||
bufferThreadLocal.set(buffer.array());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isFiltered(String identifier, @Nullable String accessibilityId,
|
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
|
||||||
@Nullable String accessibilityText, StringBuilder pathBuilder) {
|
|
||||||
try {
|
try {
|
||||||
if (identifier.isEmpty() || pathBuilder.length() == 0) {
|
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] buffer = null;
|
byte[] buffer = bufferThreadLocal.get();
|
||||||
if (EXTRACT_IDENTIFIER_FROM_BUFFER) {
|
|
||||||
final int pipeIndex = identifier.indexOf('|');
|
|
||||||
if (pipeIndex >= 0) {
|
|
||||||
// If the identifier contains no pipe, then it's not an ".eml" identifier
|
|
||||||
// and the buffer is not uniquely identified. Typically, this only happens
|
|
||||||
// for subcomponents where buffer filtering is not used.
|
|
||||||
String identifierKey = identifier.substring(0, pipeIndex);
|
|
||||||
|
|
||||||
var map = identifierToBufferThread.get();
|
|
||||||
if (map != null) {
|
|
||||||
buffer = map.get(identifierKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer == null) {
|
|
||||||
// Buffer for thread local not found. Use the last buffer found from any thread.
|
|
||||||
buffer = identifierToBufferGlobal.get(identifierKey);
|
|
||||||
|
|
||||||
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER && buffer == null) {
|
|
||||||
// No buffer is found for some components, such as
|
|
||||||
// shorts_lockup_cell.eml on channel profiles.
|
|
||||||
// For now, just ignore this and filter without a buffer.
|
|
||||||
if (BaseSettings.DEBUG.get()) {
|
|
||||||
Logger.printException(() -> "Debug: Could not find buffer for identifier: " + identifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buffer = bufferThreadLocal.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Potentially the buffer may have been null or never set up until now.
|
// Potentially the buffer may have been null or never set up until now.
|
||||||
// Use an empty buffer so the litho id/path filters that do not use a buffer still work.
|
// Use an empty buffer so the litho id/path filters still work correctly.
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
buffer = EMPTY_BYTE_ARRAY;
|
buffer = EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
String path = pathBuilder.toString();
|
LithoFilterParameters parameter = new LithoFilterParameters(
|
||||||
|
lithoIdentifier, pathBuilder.toString(), buffer);
|
||||||
String accessibility = "";
|
|
||||||
if (accessibilityId != null && !accessibilityId.isBlank()) {
|
|
||||||
accessibility = accessibilityId;
|
|
||||||
}
|
|
||||||
if (accessibilityText != null && !accessibilityText.isBlank()) {
|
|
||||||
accessibility = accessibilityId + '|' + accessibilityText;
|
|
||||||
}
|
|
||||||
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, accessibility, buffer);
|
|
||||||
Logger.printDebug(() -> "Searching " + parameter);
|
Logger.printDebug(() -> "Searching " + parameter);
|
||||||
|
|
||||||
return identifierSearchTree.matches(identifier, parameter)
|
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
|
||||||
|| pathSearchTree.matches(path, parameter);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathSearchTree.matches(parameter.path, parameter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "isFiltered failure", ex);
|
Logger.printException(() -> "isFiltered failure", ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,23 +24,23 @@ public class LinkSanitizer {
|
||||||
: List.of(parametersToRemove);
|
: List.of(parametersToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sanitizeURLString(String url) {
|
public String sanitizeUrlString(String url) {
|
||||||
try {
|
try {
|
||||||
return sanitizeURI(Uri.parse(url)).toString();
|
return sanitizeUri(Uri.parse(url)).toString();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "sanitizeURLString failure: " + url, ex);
|
Logger.printException(() -> "sanitizeUrlString failure: " + url, ex);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri sanitizeURI(Uri uri) {
|
public Uri sanitizeUri(Uri uri) {
|
||||||
try {
|
try {
|
||||||
String scheme = uri.getScheme();
|
String scheme = uri.getScheme();
|
||||||
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
||||||
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
||||||
// Checking !uri.isHierarchical() works for all cases, except if the
|
// Checking !uri.isHierarchical() works for all cases, except if the
|
||||||
// video title starts with / and then it's hierarchical but still an invalid URI.
|
// video title starts with / and then it's hierarchical but still an invalid URI.
|
||||||
Logger.printDebug(() -> "Ignoring URI: " + uri);
|
Logger.printDebug(() -> "Ignoring uri: " + uri);
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,12 +56,12 @@ public class LinkSanitizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri sanitizedURL = builder.build();
|
Uri sanitizedUrl = builder.build();
|
||||||
Logger.printInfo(() -> "Sanitized URL: " + uri + " to: " + sanitizedURL);
|
Logger.printInfo(() -> "Sanitized url: " + uri + " to: " + sanitizedUrl);
|
||||||
|
|
||||||
return sanitizedURL;
|
return sanitizedUrl;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "sanitizeURI failure: " + uri, ex);
|
Logger.printException(() -> "sanitizeUri failure: " + uri, ex);
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ public class Requester {
|
||||||
public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException {
|
public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException {
|
||||||
String url = apiUrl + route.getCompiledRoute();
|
String url = apiUrl + route.getCompiledRoute();
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
// This request sends data via URL query parameters. No request body is included.
|
// Request data is in the URL parameters and no body is sent.
|
||||||
// If a request body is added, the caller must set the appropriate Content-Length header.
|
// The calling code must set a length if using a request body.
|
||||||
connection.setFixedLengthStreamingMode(0);
|
connection.setFixedLengthStreamingMode(0);
|
||||||
connection.setRequestMethod(route.getMethod().name());
|
connection.setRequestMethod(route.getMethod().name());
|
||||||
String agentString = System.getProperty("http.agent")
|
String agentString = System.getProperty("http.agent")
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@ import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
|
||||||
import app.revanced.extension.shared.ui.Dim;
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
|
|
@ -25,18 +21,17 @@ import app.revanced.extension.shared.ui.Dim;
|
||||||
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
|
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
|
||||||
* Provides common logic for initializing the activity and setting up the toolbar.
|
* Provides common logic for initializing the activity and setting up the toolbar.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings({"deprecation", "NewApi"})
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public abstract class BaseActivityHook extends Activity {
|
public abstract class BaseActivityHook extends Activity {
|
||||||
|
|
||||||
private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
|
private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
|
||||||
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_settings_fragments");
|
getResourceIdentifierOrThrow("revanced_settings_fragments", "id");
|
||||||
private static final int ID_REVANCED_TOOLBAR_PARENT =
|
private static final int ID_REVANCED_TOOLBAR_PARENT =
|
||||||
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent");
|
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id");
|
||||||
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
|
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
|
||||||
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_settings_with_toolbar");
|
getResourceIdentifierOrThrow("revanced_settings_with_toolbar", "layout");
|
||||||
private static final int STRING_REVANCED_SETTINGS_TITLE =
|
private static final int STRING_REVANCED_SETTINGS_TITLE =
|
||||||
getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title");
|
getResourceIdentifierOrThrow("revanced_settings_title", "string");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout parameters for the toolbar, extracted from the dummy toolbar.
|
* Layout parameters for the toolbar, extracted from the dummy toolbar.
|
||||||
|
|
@ -100,15 +95,15 @@ public abstract class BaseActivityHook extends Activity {
|
||||||
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
|
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
|
||||||
// Replace dummy placeholder toolbar.
|
// Replace dummy placeholder toolbar.
|
||||||
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
||||||
ViewGroup toolbarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
ViewGroup toolBarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
|
||||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolbarParent, "revanced_toolbar");
|
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
|
||||||
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||||
toolbarParent.removeView(dummyToolbar);
|
toolBarParent.removeView(dummyToolbar);
|
||||||
|
|
||||||
// Sets appropriate system navigation bar color for the activity.
|
// Sets appropriate system navigation bar color for the activity.
|
||||||
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
|
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
|
||||||
|
|
||||||
Toolbar toolbar = new Toolbar(toolbarParent.getContext());
|
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||||
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
||||||
toolbar.setNavigationIcon(getNavigationIcon());
|
toolbar.setNavigationIcon(getNavigationIcon());
|
||||||
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
|
||||||
|
|
@ -125,14 +120,7 @@ public abstract class BaseActivityHook extends Activity {
|
||||||
|
|
||||||
onPostToolbarSetup(activity, toolbar, fragment);
|
onPostToolbarSetup(activity, toolbar, fragment);
|
||||||
|
|
||||||
toolbarParent.addView(toolbar, 0);
|
toolBarParent.addView(toolbar, 0);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the resource ID for the content view layout.
|
|
||||||
*/
|
|
||||||
protected int getContentViewResourceId() {
|
|
||||||
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -140,6 +128,11 @@ public abstract class BaseActivityHook extends Activity {
|
||||||
*/
|
*/
|
||||||
protected abstract void customizeActivityTheme(Activity activity);
|
protected abstract void customizeActivityTheme(Activity activity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resource ID for the content view layout.
|
||||||
|
*/
|
||||||
|
protected abstract int getContentViewResourceId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the background color for the toolbar.
|
* Returns the background color for the toolbar.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,6 @@ import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
|
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.patches.CustomBrandingPatch;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings shared across multiple apps.
|
* Settings shared across multiple apps.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -27,44 +24,24 @@ public class BaseSettings {
|
||||||
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
|
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
|
||||||
*/
|
*/
|
||||||
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
|
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
|
||||||
/**
|
|
||||||
* Do not use this setting directly. Instead use {@link app.revanced.extension.shared.Utils#appIsUsingBoldIcons()}
|
|
||||||
*/
|
|
||||||
public static final BooleanSetting SETTINGS_DISABLE_BOLD_ICONS = new BooleanSetting("revanced_settings_disable_bold_icons", FALSE, true);
|
|
||||||
|
|
||||||
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
|
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
|
||||||
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
|
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
|
||||||
|
|
||||||
/**
|
|
||||||
* The first time the app was launched with no previous app data (either a clean install, or after wiping app data).
|
|
||||||
*/
|
|
||||||
public static final LongSetting FIRST_TIME_APP_LAUNCHED = new LongSetting("revanced_last_time_app_was_launched", -1L, false, false);
|
|
||||||
|
|
||||||
public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Settings shared by YouTube and YouTube Music.
|
// Settings shared by YouTube and YouTube Music.
|
||||||
//
|
//
|
||||||
|
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_video_streams_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||||
|
|
||||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||||
|
|
||||||
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", CustomBrandingPatch.getDefaultIconStyle(), true);
|
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
|
||||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", CustomBrandingPatch.getDefaultAppNameIndex(), true);
|
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
|
||||||
|
|
||||||
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
|
||||||
|
|
||||||
static {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
|
|
||||||
if (FIRST_TIME_APP_LAUNCHED.get() < 0) {
|
|
||||||
Logger.printInfo(() -> "First launch of installation with no prior app data");
|
|
||||||
FIRST_TIME_APP_LAUNCHED.save(now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class BooleanSetting extends Setting<Boolean> {
|
||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
*
|
*
|
||||||
* This intentionally is a static method to deter
|
* This intentionally is a static method to deter
|
||||||
* accidental usage when {@link #save(Boolean)} was intended.
|
* accidental usage when {@link #save(Boolean)} was intnded.
|
||||||
*/
|
*/
|
||||||
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||||
setting.value = Objects.requireNonNull(newValue);
|
setting.value = Objects.requireNonNull(newValue);
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,60 @@ public abstract class Setting<T> {
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
||||||
|
*/
|
||||||
|
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
|
||||||
|
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
if (!oldSetting.isSetToDefault()) {
|
||||||
|
Logger.printInfo(() -> "Migrating old setting value: " + oldSetting + " into replacement setting: " + newSetting);
|
||||||
|
newSetting.save(oldSetting.value);
|
||||||
|
oldSetting.resetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate an old Setting value previously stored in a different SharedPreference.
|
||||||
|
* <p>
|
||||||
|
* This method will be deleted in the future.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes", "NewApi"})
|
||||||
|
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
|
||||||
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
|
return; // Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
Object newValue = setting.get();
|
||||||
|
final Object migratedValue;
|
||||||
|
if (setting instanceof BooleanSetting) {
|
||||||
|
migratedValue = oldPrefs.getBoolean(settingKey, (Boolean) newValue);
|
||||||
|
} else if (setting instanceof IntegerSetting) {
|
||||||
|
migratedValue = oldPrefs.getIntegerString(settingKey, (Integer) newValue);
|
||||||
|
} else if (setting instanceof LongSetting) {
|
||||||
|
migratedValue = oldPrefs.getLongString(settingKey, (Long) newValue);
|
||||||
|
} else if (setting instanceof FloatSetting) {
|
||||||
|
migratedValue = oldPrefs.getFloatString(settingKey, (Float) newValue);
|
||||||
|
} else if (setting instanceof StringSetting) {
|
||||||
|
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
||||||
|
} else {
|
||||||
|
Logger.printException(() -> "Unknown setting: " + setting);
|
||||||
|
// Remove otherwise it'll show a toast on every launch.
|
||||||
|
oldPrefs.preferences.edit().remove(settingKey).apply();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
||||||
|
if (migratedValue.equals(newValue)) {
|
||||||
|
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
||||||
|
return; // Old value is already equal to the new setting value.
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
||||||
|
//noinspection unchecked
|
||||||
|
setting.save(migratedValue);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets, but does _not_ persistently save the value.
|
* Sets, but does _not_ persistently save the value.
|
||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
|
|
@ -365,7 +419,7 @@ public abstract class Setting<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the currently set value is the same as {@link #defaultValue}.
|
* @return if the currently set value is the same as {@link #defaultValue}
|
||||||
*/
|
*/
|
||||||
public boolean isSetToDefault() {
|
public boolean isSetToDefault() {
|
||||||
return value.equals(defaultValue);
|
return value.equals(defaultValue);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ public class YouTubeAndMusicSettings extends BaseSettings {
|
||||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
public static final BooleanSetting DEBUG_PROTOCOLBUFFER = new BooleanSetting("revanced_debug_protocolbuffer", FALSE, false,
|
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
|
||||||
"revanced_debug_protocolbuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import androidx.annotation.Nullable;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
|
|
@ -104,16 +103,10 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
* so all app specific {@link Setting} instances are loaded before this method returns.
|
* so all app specific {@link Setting} instances are loaded before this method returns.
|
||||||
*/
|
*/
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
String preferenceResourceName;
|
String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get()
|
||||||
if (BaseSettings.SHOW_MENU_ICONS.get()) {
|
? "revanced_prefs_icons"
|
||||||
preferenceResourceName = Utils.appIsUsingBoldIcons()
|
: "revanced_prefs";
|
||||||
? "revanced_prefs_icons_bold"
|
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
|
||||||
: "revanced_prefs_icons";
|
|
||||||
} else {
|
|
||||||
preferenceResourceName = "revanced_prefs";
|
|
||||||
}
|
|
||||||
|
|
||||||
final var identifier = Utils.getResourceIdentifier(ResourceType.XML, preferenceResourceName);
|
|
||||||
if (identifier == 0) return;
|
if (identifier == 0) return;
|
||||||
addPreferencesFromResource(identifier);
|
addPreferencesFromResource(identifier);
|
||||||
|
|
||||||
|
|
@ -208,7 +201,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||||
boolean syncSettingValue,
|
boolean syncSettingValue,
|
||||||
boolean applySettingToPreference) {
|
boolean applySettingToPreference) {
|
||||||
// Alternatively this could iterate through all Settings and check for any matching Preferences,
|
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||||
// the Preferences.
|
// the Preferences.
|
||||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import java.util.Locale;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
|
@ -82,13 +81,13 @@ public class ColorPickerPreference extends EditTextPreference {
|
||||||
private boolean opacitySliderEnabled = false;
|
private boolean opacitySliderEnabled = false;
|
||||||
|
|
||||||
public static final int ID_REVANCED_COLOR_PICKER_VIEW =
|
public static final int ID_REVANCED_COLOR_PICKER_VIEW =
|
||||||
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_color_picker_view");
|
getResourceIdentifierOrThrow("revanced_color_picker_view", "id");
|
||||||
public static final int ID_PREFERENCE_COLOR_DOT =
|
public static final int ID_PREFERENCE_COLOR_DOT =
|
||||||
getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot");
|
getResourceIdentifierOrThrow("preference_color_dot", "id");
|
||||||
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
|
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
|
||||||
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_dot_widget");
|
getResourceIdentifierOrThrow("revanced_color_dot_widget", "layout");
|
||||||
public static final int LAYOUT_REVANCED_COLOR_PICKER =
|
public static final int LAYOUT_REVANCED_COLOR_PICKER =
|
||||||
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker");
|
getResourceIdentifierOrThrow("revanced_color_picker", "layout");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes non valid hex characters, converts to all uppercase,
|
* Removes non valid hex characters, converts to all uppercase,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
|
|
||||||
|
|
@ -31,18 +30,14 @@ import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class CustomDialogListPreference extends ListPreference {
|
public class CustomDialogListPreference extends ListPreference {
|
||||||
|
|
||||||
public static final int ID_REVANCED_CHECK_ICON = getResourceIdentifierOrThrow(
|
public static final int ID_REVANCED_CHECK_ICON =
|
||||||
ResourceType.ID, "revanced_check_icon");
|
getResourceIdentifierOrThrow("revanced_check_icon", "id");
|
||||||
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = getResourceIdentifierOrThrow(
|
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER =
|
||||||
ResourceType.ID, "revanced_check_icon_placeholder");
|
getResourceIdentifierOrThrow("revanced_check_icon_placeholder", "id");
|
||||||
public static final int ID_REVANCED_ITEM_TEXT = getResourceIdentifierOrThrow(
|
public static final int ID_REVANCED_ITEM_TEXT =
|
||||||
ResourceType.ID, "revanced_item_text");
|
getResourceIdentifierOrThrow("revanced_item_text", "id");
|
||||||
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = getResourceIdentifierOrThrow(
|
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED =
|
||||||
ResourceType.LAYOUT, "revanced_custom_list_item_checked");
|
getResourceIdentifierOrThrow("revanced_custom_list_item_checked", "layout");
|
||||||
public static final int DRAWABLE_CHECKMARK = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark");
|
|
||||||
public static final int DRAWABLE_CHECKMARK_BOLD = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark_bold");
|
|
||||||
|
|
||||||
private String staticSummary = null;
|
private String staticSummary = null;
|
||||||
private CharSequence[] highlightedEntriesForDialog = null;
|
private CharSequence[] highlightedEntriesForDialog = null;
|
||||||
|
|
@ -130,13 +125,9 @@ public class CustomDialogListPreference extends ListPreference {
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
view = inflater.inflate(layoutResourceId, parent, false);
|
view = inflater.inflate(layoutResourceId, parent, false);
|
||||||
holder = new SubViewDataContainer();
|
holder = new SubViewDataContainer();
|
||||||
|
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
|
||||||
holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER);
|
holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER);
|
||||||
holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT);
|
holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT);
|
||||||
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
|
|
||||||
holder.checkIcon.setImageResource(Utils.appIsUsingBoldIcons()
|
|
||||||
? DRAWABLE_CHECKMARK_BOLD
|
|
||||||
: DRAWABLE_CHECKMARK
|
|
||||||
);
|
|
||||||
view.setTag(holder);
|
view.setTag(holder);
|
||||||
} else {
|
} else {
|
||||||
holder = (SubViewDataContainer) view.getTag();
|
holder = (SubViewDataContainer) view.getTag();
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.patches.EnableDebuggingPatch;
|
import app.revanced.extension.shared.patches.EnableDebuggingPatch;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
@ -53,26 +52,25 @@ import app.revanced.extension.shared.ui.Dim;
|
||||||
public class FeatureFlagsManagerPreference extends Preference {
|
public class FeatureFlagsManagerPreference extends Preference {
|
||||||
|
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
|
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all");
|
getResourceIdentifierOrThrow("revanced_settings_select_all", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
|
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all");
|
getResourceIdentifierOrThrow("revanced_settings_deselect_all", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
|
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all");
|
getResourceIdentifierOrThrow("revanced_settings_copy_all", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one");
|
getResourceIdentifierOrThrow("revanced_settings_arrow_right_one", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double");
|
getResourceIdentifierOrThrow("revanced_settings_arrow_right_double", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one");
|
getResourceIdentifierOrThrow("revanced_settings_arrow_left_one", "drawable");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
|
||||||
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_double");
|
getResourceIdentifierOrThrow("revanced_settings_arrow_left_double", "drawable");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags to hide from the UI.
|
* Flags to hide from the UI.
|
||||||
*/
|
*/
|
||||||
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
|
||||||
45386834L, // 'You' tab settings icon.
|
45386834L // 'You' tab settings icon.
|
||||||
45532100L // Cairo flag. Turning this off with all other flags causes the settings menu to be a mix of old/new.
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,10 +129,9 @@ public class FeatureFlagsManagerPreference extends Preference {
|
||||||
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
disabledFlags.removeAll(FLAGS_TO_IGNORE);
|
||||||
|
|
||||||
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
|
||||||
// It's impossible to reach the settings menu without reaching at least one flag.
|
// String does not need to be localized because it's basically impossible
|
||||||
// So if theres no flags, then that means the user has just enabled debugging
|
// to reach the settings menu without encountering at least 1 flag.
|
||||||
// but has not restarted the app yet.
|
Utils.showToastShort("No feature flags logged yet");
|
||||||
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_no_flags"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,18 +36,18 @@ public class SharedPrefCategory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
||||||
preferences.edit().putString(key, (value == null ? null : value.toString())).commit();
|
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any preference data type that has the specified key.
|
* Removes any preference data type that has the specified key.
|
||||||
*/
|
*/
|
||||||
public void removeKey(@NonNull String key) {
|
public void removeKey(@NonNull String key) {
|
||||||
preferences.edit().remove(Objects.requireNonNull(key)).commit();
|
preferences.edit().remove(Objects.requireNonNull(key)).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveBoolean(@NonNull String key, boolean value) {
|
public void saveBoolean(@NonNull String key, boolean value) {
|
||||||
preferences.edit().putBoolean(key, value).commit();
|
preferences.edit().putBoolean(key, value).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,13 @@ import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||||
import app.revanced.extension.shared.ui.Dim;
|
import app.revanced.extension.shared.ui.Dim;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings({"deprecation", "NewApi"})
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -136,10 +133,8 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
*/
|
*/
|
||||||
@SuppressLint("UseCompatLoadingForDrawables")
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
public static Drawable getBackButtonDrawable() {
|
public static Drawable getBackButtonDrawable() {
|
||||||
final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE,
|
final int backButtonResource = Utils.getResourceIdentifierOrThrow(
|
||||||
Utils.appIsUsingBoldIcons()
|
"revanced_settings_toolbar_arrow_left", "drawable");
|
||||||
? "revanced_settings_toolbar_arrow_left_bold"
|
|
||||||
: "revanced_settings_toolbar_arrow_left");
|
|
||||||
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
|
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
|
||||||
customizeBackButtonDrawable(drawable);
|
customizeBackButtonDrawable(drawable);
|
||||||
return drawable;
|
return drawable;
|
||||||
|
|
|
||||||
|
|
@ -9,36 +9,36 @@ import android.util.AttributeSet;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple preference that opens a URL when clicked.
|
* Simple preference that opens a url when clicked.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class URLLinkPreference extends Preference {
|
public class UrlLinkPreference extends Preference {
|
||||||
|
|
||||||
protected String externalURL;
|
protected String externalUrl;
|
||||||
|
|
||||||
{
|
{
|
||||||
setOnPreferenceClickListener(pref -> {
|
setOnPreferenceClickListener(pref -> {
|
||||||
if (externalURL == null) {
|
if (externalUrl == null) {
|
||||||
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||||
i.setData(Uri.parse(externalURL));
|
i.setData(Uri.parse(externalUrl));
|
||||||
pref.getContext().startActivity(i);
|
pref.getContext().startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
public URLLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
}
|
}
|
||||||
public URLLinkPreference(Context context, AttributeSet attrs) {
|
public UrlLinkPreference(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
public URLLinkPreference(Context context) {
|
public UrlLinkPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,11 +16,10 @@ import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for search result items, defining common fields and behavior.
|
* Abstract base class for search result items, defining common fields and behavior.
|
||||||
|
|
@ -50,7 +49,7 @@ public abstract class BaseSearchResultItem {
|
||||||
|
|
||||||
private static int getResourceIdentifier(String name) {
|
private static int getResourceIdentifier(String name) {
|
||||||
// Placeholder for actual resource identifier retrieval.
|
// Placeholder for actual resource identifier retrieval.
|
||||||
return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name);
|
return Utils.getResourceIdentifierOrThrow(name, "layout");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +166,7 @@ public abstract class BaseSearchResultItem {
|
||||||
if (pref instanceof SwitchPreference) return ViewType.SWITCH;
|
if (pref instanceof SwitchPreference) return ViewType.SWITCH;
|
||||||
if (pref instanceof ListPreference) return ViewType.LIST;
|
if (pref instanceof ListPreference) return ViewType.LIST;
|
||||||
if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER;
|
if (pref instanceof ColorPickerPreference) return ViewType.COLOR_PICKER;
|
||||||
if (pref instanceof URLLinkPreference) return ViewType.URL_LINK;
|
if (pref instanceof UrlLinkPreference) return ViewType.URL_LINK;
|
||||||
if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS;
|
if ("no_results_placeholder".equals(pref.getKey())) return ViewType.NO_RESULTS;
|
||||||
return ViewType.REGULAR;
|
return ViewType.REGULAR;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package app.revanced.extension.shared.settings.search;
|
package app.revanced.extension.shared.settings.search;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
|
||||||
|
import static app.revanced.extension.shared.settings.search.BaseSearchViewController.DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
|
||||||
|
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ArgbEvaluator;
|
import android.animation.ArgbEvaluator;
|
||||||
|
|
@ -32,11 +33,10 @@ import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.URLLinkPreference;
|
import app.revanced.extension.shared.settings.preference.UrlLinkPreference;
|
||||||
import app.revanced.extension.shared.ui.ColorDot;
|
import app.revanced.extension.shared.ui.ColorDot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,15 +54,15 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||||
protected static final int PAUSE_BETWEEN_BLINKS = 100;
|
protected static final int PAUSE_BETWEEN_BLINKS = 100;
|
||||||
|
|
||||||
protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "preference_title");
|
"preference_title", "id");
|
||||||
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "preference_summary");
|
"preference_summary", "id");
|
||||||
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "preference_path");
|
"preference_path", "id");
|
||||||
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "preference_switch");
|
"preference_switch", "id");
|
||||||
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "preference_color_dot");
|
"preference_color_dot", "id");
|
||||||
|
|
||||||
protected static class RegularViewHolder {
|
protected static class RegularViewHolder {
|
||||||
TextView titleView;
|
TextView titleView;
|
||||||
|
|
@ -275,7 +275,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||||
holder.titleView.setText(item.highlightedTitle);
|
holder.titleView.setText(item.highlightedTitle);
|
||||||
holder.summaryView.setText(item.highlightedSummary);
|
holder.summaryView.setText(item.highlightedSummary);
|
||||||
holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE);
|
holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE);
|
||||||
holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon());
|
holder.iconView.setImageResource(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -436,7 +436,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes string for comparison (removes extra characters, spaces etc.).
|
* Normalizes string for comparison (removes extra characters, spaces etc).
|
||||||
*/
|
*/
|
||||||
protected String normalizeString(String input) {
|
protected String normalizeString(String input) {
|
||||||
if (TextUtils.isEmpty(input)) return "";
|
if (TextUtils.isEmpty(input)) return "";
|
||||||
|
|
@ -609,8 +609,8 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
|
||||||
boolean hasNavigationCapability(Preference preference) {
|
boolean hasNavigationCapability(Preference preference) {
|
||||||
// PreferenceScreen always allows navigation.
|
// PreferenceScreen always allows navigation.
|
||||||
if (preference instanceof PreferenceScreen) return true;
|
if (preference instanceof PreferenceScreen) return true;
|
||||||
// URLLinkPreference does not navigate to a new screen, it opens an external URL.
|
// UrlLinkPreference does not navigate to a new screen, it opens an external URL.
|
||||||
if (preference instanceof URLLinkPreference) return false;
|
if (preference instanceof UrlLinkPreference) return false;
|
||||||
// Other group types that might have their own screens.
|
// Other group types that might have their own screens.
|
||||||
if (preference instanceof PreferenceGroup) {
|
if (preference instanceof PreferenceGroup) {
|
||||||
// Check if it has its own fragment or intent.
|
// Check if it has its own fragment or intent.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import android.preference.PreferenceGroup;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
@ -38,7 +37,6 @@ import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.AppLanguage;
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
@ -72,29 +70,14 @@ public abstract class BaseSearchViewController {
|
||||||
|
|
||||||
protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed.
|
protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed.
|
||||||
|
|
||||||
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow(
|
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow("revanced_search_view", "id");
|
||||||
ResourceType.ID, "revanced_search_view");
|
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow("revanced_search_view_container", "id");
|
||||||
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow(
|
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow("action_search", "id");
|
||||||
ResourceType.ID, "revanced_search_view_container");
|
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow("revanced_settings_fragments", "id");
|
||||||
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow(
|
public static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON =
|
||||||
ResourceType.ID, "action_search");
|
getResourceIdentifierOrThrow("revanced_settings_search_icon", "drawable");
|
||||||
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow(
|
protected static final int MENU_REVANCED_SEARCH_MENU =
|
||||||
ResourceType.ID, "revanced_settings_fragments");
|
getResourceIdentifierOrThrow("revanced_search_menu", "menu");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_search_icon");
|
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_search_icon_bold");
|
|
||||||
protected static final int MENU_REVANCED_SEARCH_MENU = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.MENU, "revanced_search_menu");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The search icon, either bold or not bold, depending on the ReVanced UI setting.
|
|
||||||
*/
|
|
||||||
public static int getSearchIcon() {
|
|
||||||
return Utils.appIsUsingBoldIcons()
|
|
||||||
? DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD
|
|
||||||
: DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new BaseSearchViewController instance.
|
* Constructs a new BaseSearchViewController instance.
|
||||||
|
|
@ -129,7 +112,7 @@ public abstract class BaseSearchViewController {
|
||||||
// Retrieve SearchView and container from XML.
|
// Retrieve SearchView and container from XML.
|
||||||
searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW);
|
searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW);
|
||||||
EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow(
|
EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow(
|
||||||
null, "android:id/search_src_text"));
|
"android:id/search_src_text", null));
|
||||||
// Disable fullscreen keyboard mode.
|
// Disable fullscreen keyboard mode.
|
||||||
searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||||
|
|
||||||
|
|
@ -265,10 +248,6 @@ public abstract class BaseSearchViewController {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set bold icon if needed.
|
|
||||||
MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH);
|
|
||||||
search.setIcon(getSearchIcon());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -545,7 +524,7 @@ public abstract class BaseSearchViewController {
|
||||||
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
|
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
|
||||||
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
|
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
|
||||||
noResultsPreference.setSelectable(false);
|
noResultsPreference.setSelectable(false);
|
||||||
noResultsPreference.setIcon(getSearchIcon());
|
noResultsPreference.setIcon(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
|
||||||
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
|
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.shared.settings.preference.BulletPointPreference;
|
import app.revanced.extension.shared.settings.preference.BulletPointPreference;
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
|
|
||||||
|
|
@ -39,35 +37,25 @@ public class SearchHistoryManager {
|
||||||
private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored.
|
private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored.
|
||||||
|
|
||||||
private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow(
|
private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "clear_history_button");
|
"clear_history_button", "id");
|
||||||
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
|
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "history_text");
|
"history_text", "id");
|
||||||
private static final int ID_HISTORY_ICON = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.ID, "history_icon");
|
|
||||||
private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow(
|
private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "delete_icon");
|
"delete_icon", "id");
|
||||||
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
|
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "empty_history_title");
|
"empty_history_title", "id");
|
||||||
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
|
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "empty_history_summary");
|
"empty_history_summary", "id");
|
||||||
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "search_history_header");
|
"search_history_header", "id");
|
||||||
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "revanced_settings_search_tips_summary");
|
"revanced_settings_search_tips_summary", "id");
|
||||||
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
|
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
|
||||||
ResourceType.LAYOUT, "revanced_preference_search_history_screen");
|
"revanced_preference_search_history_screen", "layout");
|
||||||
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
|
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
|
||||||
ResourceType.LAYOUT, "revanced_preference_search_history_item");
|
"revanced_preference_search_history_item", "layout");
|
||||||
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
|
||||||
ResourceType.ID, "search_history_list");
|
"search_history_list", "id");
|
||||||
private static final int ID_SEARCH_REMOVE_ICON = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_search_remove");
|
|
||||||
private static final int ID_SEARCH_REMOVE_ICON_BOLD = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_search_remove_bold");
|
|
||||||
private static final int ID_SEARCH_ARROW_TIME_ICON = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_arrow_time");
|
|
||||||
private static final int ID_SEARCH_ARROW_TIME_ICON_BOLD = getResourceIdentifierOrThrow(
|
|
||||||
ResourceType.DRAWABLE, "revanced_settings_arrow_time_bold");
|
|
||||||
|
|
||||||
private final Deque<String> searchHistory;
|
private final Deque<String> searchHistory;
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
|
|
@ -109,8 +97,7 @@ public class SearchHistoryManager {
|
||||||
|
|
||||||
// Inflate search history layout.
|
// Inflate search history layout.
|
||||||
LayoutInflater inflater = LayoutInflater.from(activity);
|
LayoutInflater inflater = LayoutInflater.from(activity);
|
||||||
View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN,
|
View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN, searchHistoryContainer, false);
|
||||||
searchHistoryContainer, false);
|
|
||||||
searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams(
|
searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT));
|
FrameLayout.LayoutParams.MATCH_PARENT));
|
||||||
|
|
@ -333,29 +320,17 @@ public class SearchHistoryManager {
|
||||||
public void notifyDataSetChanged() {
|
public void notifyDataSetChanged() {
|
||||||
container.removeAllViews();
|
container.removeAllViews();
|
||||||
for (String query : history) {
|
for (String query : history) {
|
||||||
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM,
|
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM, container, false);
|
||||||
container, false);
|
|
||||||
|
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
|
||||||
|
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
|
||||||
|
|
||||||
|
historyText.setText(query);
|
||||||
|
|
||||||
// Set click listener for main item (select query).
|
// Set click listener for main item (select query).
|
||||||
view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query));
|
view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query));
|
||||||
|
|
||||||
// Set history icon.
|
|
||||||
ImageView historyIcon = view.findViewById(ID_HISTORY_ICON);
|
|
||||||
historyIcon.setImageResource(Utils.appIsUsingBoldIcons()
|
|
||||||
? ID_SEARCH_ARROW_TIME_ICON_BOLD
|
|
||||||
: ID_SEARCH_ARROW_TIME_ICON
|
|
||||||
);
|
|
||||||
|
|
||||||
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
|
|
||||||
historyText.setText(query);
|
|
||||||
|
|
||||||
// Set click listener for delete icon.
|
// Set click listener for delete icon.
|
||||||
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
|
|
||||||
|
|
||||||
deleteIcon.setImageResource(Utils.appIsUsingBoldIcons()
|
|
||||||
? ID_SEARCH_REMOVE_ICON_BOLD
|
|
||||||
: ID_SEARCH_REMOVE_ICON
|
|
||||||
);
|
|
||||||
|
|
||||||
deleteIcon.setOnClickListener(v -> createAndShowDialog(
|
deleteIcon.setOnClickListener(v -> createAndShowDialog(
|
||||||
query,
|
query,
|
||||||
str("revanced_settings_search_remove_message"),
|
str("revanced_settings_search_remove_message"),
|
||||||
|
|
|
||||||
|
|
@ -9,34 +9,9 @@ import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantLocale")
|
@SuppressWarnings("ConstantLocale")
|
||||||
public enum ClientType {
|
public enum ClientType {
|
||||||
/**
|
|
||||||
* Video not playable: Paid, Movie, Private, Age-restricted.
|
|
||||||
* Uses non-adaptive bitrate.
|
|
||||||
* AV1 codec available.
|
|
||||||
*/
|
|
||||||
ANDROID_REEL(
|
|
||||||
3,
|
|
||||||
"ANDROID",
|
|
||||||
"com.google.android.youtube",
|
|
||||||
Build.MANUFACTURER,
|
|
||||||
Build.MODEL,
|
|
||||||
"Android",
|
|
||||||
Build.VERSION.RELEASE,
|
|
||||||
String.valueOf(Build.VERSION.SDK_INT),
|
|
||||||
Build.ID,
|
|
||||||
"20.44.38",
|
|
||||||
// This client has been used by most open-source YouTube stream extraction tools since 2024, including NewPipe Extractor, SmartTube, and Grayjay.
|
|
||||||
// This client can log in, but if an access token is used in the request, GVS can more easily identify the request as coming from ReVanced.
|
|
||||||
// This means that the GVS server can strengthen its validation of the ANDROID_REEL client.
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
"Android Reel"
|
|
||||||
),
|
|
||||||
/**
|
/**
|
||||||
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
|
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
|
||||||
* This client can only be used when logged out.
|
* This client can only be used when logged out.
|
||||||
|
|
@ -53,10 +28,10 @@ public enum ClientType {
|
||||||
// Android 12.1
|
// Android 12.1
|
||||||
"32",
|
"32",
|
||||||
"SQ3A.220605.009.A1",
|
"SQ3A.220605.009.A1",
|
||||||
|
"132.0.6808.3",
|
||||||
"1.61.48",
|
"1.61.48",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
|
||||||
"Android VR 1.61"
|
"Android VR 1.61"
|
||||||
),
|
),
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,12 +48,39 @@ public enum ClientType {
|
||||||
ANDROID_VR_1_61_48.osVersion,
|
ANDROID_VR_1_61_48.osVersion,
|
||||||
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
|
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
|
||||||
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
|
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
|
||||||
|
"107.0.5284.2",
|
||||||
"1.43.32",
|
"1.43.32",
|
||||||
ANDROID_VR_1_61_48.useAuth,
|
ANDROID_VR_1_61_48.useAuth,
|
||||||
ANDROID_VR_1_61_48.supportsMultiAudioTracks,
|
ANDROID_VR_1_61_48.supportsMultiAudioTracks,
|
||||||
ANDROID_VR_1_61_48.usePlayerEndpoint,
|
|
||||||
"Android VR 1.43"
|
"Android VR 1.43"
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* Video not playable: Paid / Movie / Private / Age-restricted.
|
||||||
|
* Note: The 'Authorization' key must be excluded from the header.
|
||||||
|
*
|
||||||
|
* According to TeamNewPipe in 2022, if the 'androidSdkVersion' field is missing,
|
||||||
|
* the GVS did not return a valid response:
|
||||||
|
* [NewPipe#8713 (comment)](https://github.com/TeamNewPipe/NewPipe/issues/8713#issuecomment-1207443550).
|
||||||
|
*
|
||||||
|
* According to the latest commit in yt-dlp, the GVS returns a valid response
|
||||||
|
* even if the 'androidSdkVersion' field is missing:
|
||||||
|
* [yt-dlp#14693](https://github.com/yt-dlp/yt-dlp/pull/14693).
|
||||||
|
*
|
||||||
|
* For some reason, PoToken is not required.
|
||||||
|
*/
|
||||||
|
ANDROID_NO_SDK(
|
||||||
|
3,
|
||||||
|
"ANDROID",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
Build.VERSION.RELEASE,
|
||||||
|
"20.05.46",
|
||||||
|
"com.google.android.youtube/20.05.46 (Linux; U; Android " + Build.VERSION.RELEASE + ") gzip",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"Android No SDK"
|
||||||
|
),
|
||||||
/**
|
/**
|
||||||
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
||||||
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
|
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
|
||||||
|
|
@ -93,10 +95,10 @@ public enum ClientType {
|
||||||
"15",
|
"15",
|
||||||
"35",
|
"35",
|
||||||
"AP3A.241005.015.A2",
|
"AP3A.241005.015.A2",
|
||||||
|
"132.0.6779.0",
|
||||||
"23.47.101",
|
"23.47.101",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
|
||||||
"Android Studio"
|
"Android Studio"
|
||||||
),
|
),
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,8 +114,32 @@ public enum ClientType {
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
|
||||||
"visionOS"
|
"visionOS"
|
||||||
|
),
|
||||||
|
/**
|
||||||
|
* The device machine id for the iPad 6th Gen (iPad7,6).
|
||||||
|
* AV1 hardware decoding is not supported.
|
||||||
|
* See [this GitHub Gist](https://gist.github.com/adamawolf/3048717) for more information.
|
||||||
|
*
|
||||||
|
* Based on Google's actions to date, PoToken may not be required on devices with very low specs.
|
||||||
|
* For example, suppose the User-Agent for a PlayStation 3 (with 256MB of RAM) is used.
|
||||||
|
* Accessing 'Web' (https://www.youtube.com) will redirect to 'TV' (https://www.youtube.com/tv).
|
||||||
|
* 'TV' target devices with very low specs, such as embedded devices, game consoles, and blu-ray players, so PoToken is not required.
|
||||||
|
*
|
||||||
|
* For this reason, the device machine id for the iPad 6th Gen (with 2GB of RAM),
|
||||||
|
* the lowest spec device capable of running iPadOS 17, was used.
|
||||||
|
*/
|
||||||
|
IPADOS(5,
|
||||||
|
"IOS",
|
||||||
|
"Apple",
|
||||||
|
"iPad7,6",
|
||||||
|
"iPadOS",
|
||||||
|
"17.7.10.21H450",
|
||||||
|
"19.22.3",
|
||||||
|
"com.google.ios.youtube/19.22.3 (iPad7,6; U; CPU iPadOS 17_7_10 like Mac OS X; " + Locale.getDefault() + ")",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"iPadOS"
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -169,6 +195,13 @@ public enum ClientType {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String buildId;
|
private final String buildId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cronet release version, as found in decompiled client apk.
|
||||||
|
* Field is null if not applicable.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private final String cronetVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App version.
|
* App version.
|
||||||
*/
|
*/
|
||||||
|
|
@ -184,11 +217,6 @@ public enum ClientType {
|
||||||
*/
|
*/
|
||||||
public final boolean supportsMultiAudioTracks;
|
public final boolean supportsMultiAudioTracks;
|
||||||
|
|
||||||
/**
|
|
||||||
* If the client should use the player endpoint for stream extraction.
|
|
||||||
*/
|
|
||||||
public final boolean usePlayerEndpoint;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Friendly name displayed in stats for nerds.
|
* Friendly name displayed in stats for nerds.
|
||||||
*/
|
*/
|
||||||
|
|
@ -206,10 +234,10 @@ public enum ClientType {
|
||||||
String osVersion,
|
String osVersion,
|
||||||
@NonNull String androidSdkVersion,
|
@NonNull String androidSdkVersion,
|
||||||
@NonNull String buildId,
|
@NonNull String buildId,
|
||||||
|
@NonNull String cronetVersion,
|
||||||
String clientVersion,
|
String clientVersion,
|
||||||
boolean useAuth,
|
boolean useAuth,
|
||||||
boolean supportsMultiAudioTracks,
|
boolean supportsMultiAudioTracks,
|
||||||
boolean usePlayerEndpoint,
|
|
||||||
String friendlyName) {
|
String friendlyName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
|
|
@ -220,20 +248,21 @@ public enum ClientType {
|
||||||
this.osVersion = osVersion;
|
this.osVersion = osVersion;
|
||||||
this.androidSdkVersion = androidSdkVersion;
|
this.androidSdkVersion = androidSdkVersion;
|
||||||
this.buildId = buildId;
|
this.buildId = buildId;
|
||||||
|
this.cronetVersion = cronetVersion;
|
||||||
this.clientVersion = clientVersion;
|
this.clientVersion = clientVersion;
|
||||||
this.useAuth = useAuth;
|
this.useAuth = useAuth;
|
||||||
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
||||||
this.usePlayerEndpoint = usePlayerEndpoint;
|
|
||||||
this.friendlyName = friendlyName;
|
this.friendlyName = friendlyName;
|
||||||
|
|
||||||
Locale defaultLocale = Locale.getDefault();
|
Locale defaultLocale = Locale.getDefault();
|
||||||
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s)",
|
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
|
||||||
packageName,
|
packageName,
|
||||||
clientVersion,
|
clientVersion,
|
||||||
osVersion,
|
osVersion,
|
||||||
defaultLocale,
|
defaultLocale,
|
||||||
deviceModel,
|
deviceModel,
|
||||||
buildId
|
Objects.requireNonNull(buildId),
|
||||||
|
Objects.requireNonNull(cronetVersion)
|
||||||
);
|
);
|
||||||
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +278,6 @@ public enum ClientType {
|
||||||
String userAgent,
|
String userAgent,
|
||||||
boolean useAuth,
|
boolean useAuth,
|
||||||
boolean supportsMultiAudioTracks,
|
boolean supportsMultiAudioTracks,
|
||||||
boolean usePlayerEndpoint,
|
|
||||||
String friendlyName) {
|
String friendlyName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
|
|
@ -261,10 +289,10 @@ public enum ClientType {
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.useAuth = useAuth;
|
this.useAuth = useAuth;
|
||||||
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
||||||
this.usePlayerEndpoint = usePlayerEndpoint;
|
|
||||||
this.friendlyName = friendlyName;
|
this.friendlyName = friendlyName;
|
||||||
this.packageName = null;
|
this.packageName = null;
|
||||||
this.androidSdkVersion = null;
|
this.androidSdkVersion = null;
|
||||||
this.buildId = null;
|
this.buildId = null;
|
||||||
|
this.cronetVersion = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -38,7 +39,7 @@ public class SpoofVideoStreamsPatch {
|
||||||
@Nullable
|
@Nullable
|
||||||
private static volatile AppLanguage languageOverride;
|
private static volatile AppLanguage languageOverride;
|
||||||
|
|
||||||
private static volatile ClientType preferredClient = ClientType.ANDROID_REEL;
|
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_43_32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If this patch was included during patching.
|
* @return If this patch was included during patching.
|
||||||
|
|
@ -249,7 +250,7 @@ public class SpoofVideoStreamsPatch {
|
||||||
* Called after {@link #fetchStreams(String, Map)}.
|
* Called after {@link #fetchStreams(String, Map)}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static byte[] getStreamingData(String videoId) {
|
public static ByteBuffer getStreamingData(String videoId) {
|
||||||
if (SPOOF_STREAMING_DATA) {
|
if (SPOOF_STREAMING_DATA) {
|
||||||
try {
|
try {
|
||||||
StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId);
|
StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId);
|
||||||
|
|
|
||||||
|
|
@ -15,20 +15,13 @@ import app.revanced.extension.shared.spoof.ClientType;
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
|
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
static final Route.CompiledRoute GET_PLAYER_STREAMING_DATA = new Route(
|
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
||||||
Route.Method.POST,
|
Route.Method.POST,
|
||||||
"player" +
|
"player" +
|
||||||
"?fields=streamingData" +
|
"?fields=streamingData" +
|
||||||
"&alt=proto"
|
"&alt=proto"
|
||||||
).compile();
|
).compile();
|
||||||
|
|
||||||
static final Route.CompiledRoute GET_REEL_STREAMING_DATA = new Route(
|
|
||||||
Route.Method.POST,
|
|
||||||
"reel/reel_item_watch" +
|
|
||||||
"?fields=playerResponse.playabilityStatus,playerResponse.streamingData" +
|
|
||||||
"&alt=proto"
|
|
||||||
).compile();
|
|
||||||
|
|
||||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,7 +47,6 @@ final class PlayerRoutes {
|
||||||
Locale streamLocale = language.getLocale();
|
Locale streamLocale = language.getLocale();
|
||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
|
|
||||||
client.put("deviceMake", clientType.deviceMake);
|
client.put("deviceMake", clientType.deviceMake);
|
||||||
client.put("deviceModel", clientType.deviceModel);
|
client.put("deviceModel", clientType.deviceModel);
|
||||||
client.put("clientName", clientType.clientName);
|
client.put("clientName", clientType.clientName);
|
||||||
|
|
@ -69,19 +61,9 @@ final class PlayerRoutes {
|
||||||
context.put("client", client);
|
context.put("client", client);
|
||||||
|
|
||||||
innerTubeBody.put("context", context);
|
innerTubeBody.put("context", context);
|
||||||
|
|
||||||
if (clientType.usePlayerEndpoint) {
|
|
||||||
innerTubeBody.put("contentCheckOk", true);
|
innerTubeBody.put("contentCheckOk", true);
|
||||||
innerTubeBody.put("racyCheckOk", true);
|
innerTubeBody.put("racyCheckOk", true);
|
||||||
innerTubeBody.put("videoId", videoId);
|
innerTubeBody.put("videoId", videoId);
|
||||||
} else {
|
|
||||||
JSONObject playerRequest = new JSONObject();
|
|
||||||
playerRequest.put("contentCheckOk", true);
|
|
||||||
playerRequest.put("racyCheckOk", true);
|
|
||||||
playerRequest.put("videoId", videoId);
|
|
||||||
innerTubeBody.put("playerRequest", playerRequest);
|
|
||||||
innerTubeBody.put("disablePlayerResponse", false);
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
package app.revanced.extension.shared.spoof.requests;
|
package app.revanced.extension.shared.spoof.requests;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
|
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
|
||||||
import static app.revanced.extension.shared.Utils.isNotEmpty;
|
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
|
||||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_PLAYER_STREAMING_DATA;
|
|
||||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_REEL_STREAMING_DATA;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -26,11 +28,6 @@ import java.util.concurrent.TimeoutException;
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass;
|
|
||||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.PlayerResponse;
|
|
||||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.StreamingData;
|
|
||||||
import app.revanced.extension.shared.innertube.ReelItemWatchResponseOuterClass.ReelItemWatchResponse;
|
|
||||||
import app.revanced.extension.shared.requests.Route;
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
|
|
@ -86,6 +83,8 @@ public class StreamingDataRequest {
|
||||||
*/
|
*/
|
||||||
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
||||||
|
|
||||||
|
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||||
|
new LinkedHashMap<>(100) {
|
||||||
/**
|
/**
|
||||||
* Cache limit must be greater than the maximum number of videos open at once,
|
* Cache limit must be greater than the maximum number of videos open at once,
|
||||||
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
|
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
|
||||||
|
|
@ -93,8 +92,13 @@ public class StreamingDataRequest {
|
||||||
* is somehow still referenced. Each stream is a small array of Strings
|
* is somehow still referenced. Each stream is a small array of Strings
|
||||||
* so memory usage is not a concern.
|
* so memory usage is not a concern.
|
||||||
*/
|
*/
|
||||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
private static final int CACHE_LIMIT = 50;
|
||||||
Utils.createSizeRestrictedMap(50));
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Entry eldest) {
|
||||||
|
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strings found in the response if the video is a livestream.
|
* Strings found in the response if the video is a livestream.
|
||||||
|
|
@ -115,7 +119,7 @@ public class StreamingDataRequest {
|
||||||
|
|
||||||
private final String videoId;
|
private final String videoId;
|
||||||
|
|
||||||
private final Future<byte[]> future;
|
private final Future<ByteBuffer> future;
|
||||||
|
|
||||||
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
||||||
Objects.requireNonNull(playerHeaders);
|
Objects.requireNonNull(playerHeaders);
|
||||||
|
|
@ -138,12 +142,6 @@ public class StreamingDataRequest {
|
||||||
Logger.printInfo(() -> toastMessage, ex);
|
Logger.printInfo(() -> toastMessage, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleDebugToast(String toastMessage, ClientType clientType) {
|
|
||||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
|
||||||
Utils.showToastShort(String.format(toastMessage, clientType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static HttpURLConnection send(ClientType clientType,
|
private static HttpURLConnection send(ClientType clientType,
|
||||||
String videoId,
|
String videoId,
|
||||||
|
|
@ -156,10 +154,7 @@ public class StreamingDataRequest {
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Route.CompiledRoute route = clientType.usePlayerEndpoint ?
|
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType);
|
||||||
GET_PLAYER_STREAMING_DATA : GET_REEL_STREAMING_DATA;
|
|
||||||
|
|
||||||
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(route, clientType);
|
|
||||||
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||||
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
|
|
@ -216,7 +211,7 @@ public class StreamingDataRequest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] fetch(String videoId, Map<String, String> playerHeaders) {
|
private static ByteBuffer fetch(String videoId, Map<String, String> playerHeaders) {
|
||||||
final boolean debugEnabled = BaseSettings.DEBUG.get();
|
final boolean debugEnabled = BaseSettings.DEBUG.get();
|
||||||
|
|
||||||
// Retry with different client if empty response body is received.
|
// Retry with different client if empty response body is received.
|
||||||
|
|
@ -227,11 +222,33 @@ public class StreamingDataRequest {
|
||||||
|
|
||||||
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
byte[] playerResponseBuffer = buildPlayerResponseBuffer(clientType, connection);
|
try {
|
||||||
if (playerResponseBuffer != null) {
|
// gzip encoding doesn't response with content length (-1),
|
||||||
|
// but empty response body does.
|
||||||
|
if (connection.getContentLength() == 0) {
|
||||||
|
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||||
|
Utils.showToastShort("Debug: Ignoring empty spoof stream client " + clientType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
|
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||||
|
baos.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
if (clientType == ClientType.ANDROID_CREATOR && liveStreamBufferSearch.matches(buffer)) {
|
||||||
|
Logger.printDebug(() -> "Skipping Android Studio as video is a livestream: " + videoId);
|
||||||
|
} else {
|
||||||
lastSpoofedClientType = clientType;
|
lastSpoofedClientType = clientType;
|
||||||
|
|
||||||
return playerResponseBuffer;
|
return ByteBuffer.wrap(baos.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Fetch failed while processing response data", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,61 +258,12 @@ public class StreamingDataRequest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static byte[] buildPlayerResponseBuffer(ClientType clientType,
|
|
||||||
HttpURLConnection connection) {
|
|
||||||
// gzip encoding doesn't response with content length (-1),
|
|
||||||
// but empty response body does.
|
|
||||||
if (connection.getContentLength() == 0) {
|
|
||||||
handleDebugToast("Debug: Ignoring empty spoof stream client (%s)", clientType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream inputStream = connection.getInputStream()) {
|
|
||||||
PlayerResponse playerResponse = clientType.usePlayerEndpoint
|
|
||||||
? PlayerResponse.parseFrom(inputStream)
|
|
||||||
: ReelItemWatchResponse.parseFrom(inputStream).getPlayerResponse();
|
|
||||||
|
|
||||||
var playabilityStatus = playerResponse.getPlayabilityStatus();
|
|
||||||
if (playabilityStatus.getStatus() != PlayerResponseOuterClass.Status.OK) {
|
|
||||||
handleDebugToast("Debug: Ignoring unplayable video (%s)", clientType);
|
|
||||||
String reason = playabilityStatus.getReason();
|
|
||||||
if (isNotEmpty(reason)) {
|
|
||||||
Logger.printDebug(() -> String.format("Debug: Ignoring unplayable video (%s), reason: %s", clientType, reason));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerResponse.Builder responseBuilder = playerResponse.toBuilder();
|
|
||||||
if (!playerResponse.hasStreamingData()) {
|
|
||||||
handleDebugToast("Debug: Ignoring empty streaming data (%s)", clientType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android Studio only supports the HLS protocol for live streams.
|
|
||||||
// HLS protocol can theoretically be played with ExoPlayer,
|
|
||||||
// but the related code has not yet been implemented.
|
|
||||||
// If DASH protocol is not available, the client will be skipped.
|
|
||||||
StreamingData streamingData = playerResponse.getStreamingData();
|
|
||||||
if (streamingData.getAdaptiveFormatsCount() == 0) {
|
|
||||||
handleDebugToast("Debug: Ignoring empty adaptiveFormat (%s)", clientType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseBuilder.build().toByteArray();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.printException(() -> "Failed to write player response to buffer array", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean fetchCompleted() {
|
public boolean fetchCompleted() {
|
||||||
return future.isDone();
|
return future.isDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public byte[] getStream() {
|
public ByteBuffer getStream() {
|
||||||
try {
|
try {
|
||||||
return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS);
|
return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||||
} catch (TimeoutException ex) {
|
} catch (TimeoutException ex) {
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
kotlin("jvm")
|
|
||||||
alias(libs.plugins.protobuf)
|
|
||||||
alias(libs.plugins.shadow)
|
|
||||||
}
|
|
||||||
|
|
||||||
val shade: Configuration by configurations.creating {
|
|
||||||
configurations.getByName("compileClasspath").extendsFrom(this)
|
|
||||||
configurations.getByName("runtimeClasspath").extendsFrom(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.annotation)
|
|
||||||
compileOnly(libs.okhttp)
|
|
||||||
shade(libs.protobuf.javalite)
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
// Make sure generated proto sources are compiled and end up in the shaded jar
|
|
||||||
main {
|
|
||||||
java.srcDir("$buildDir/generated/source/proto/main/java")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protobuf {
|
|
||||||
protoc {
|
|
||||||
artifact = libs.protobuf.protoc.get().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
generateProtoTasks {
|
|
||||||
all().forEach { task ->
|
|
||||||
task.builtins {
|
|
||||||
named("java") {
|
|
||||||
option("lite")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val shadowJar = tasks.named<ShadowJar>("shadowJar") {
|
|
||||||
configurations = listOf(shade)
|
|
||||||
relocate("com.google.protobuf", "app.revanced.com.google.protobuf")
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations.named("runtimeElements") {
|
|
||||||
isCanBeConsumed = true
|
|
||||||
isCanBeResolved = false
|
|
||||||
|
|
||||||
outgoing.artifacts.clear()
|
|
||||||
outgoing.artifact(shadowJar)
|
|
||||||
}!!.let { artifacts { add(it.name, shadowJar) } }
|
|
||||||
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package app.revanced.extension.shared.innertube;
|
|
||||||
|
|
||||||
option optimize_for = LITE_RUNTIME;
|
|
||||||
option java_package = "app.revanced.extension.shared.innertube";
|
|
||||||
|
|
||||||
message PlayerResponse {
|
|
||||||
oneof data {
|
|
||||||
PlayabilityStatus playability_status = 2;
|
|
||||||
StreamingData streaming_data = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayabilityStatus {
|
|
||||||
Status status = 1;
|
|
||||||
string reason = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Status {
|
|
||||||
OK = 0;
|
|
||||||
ERROR = 1;
|
|
||||||
UNPLAYABLE = 2;
|
|
||||||
LOGIN_REQUIRED = 3;
|
|
||||||
CONTENT_CHECK_REQUIRED = 4;
|
|
||||||
AGE_CHECK_REQUIRED = 5;
|
|
||||||
LIVE_STREAM_OFFLINE = 6;
|
|
||||||
FULLSCREEN_ONLY = 7;
|
|
||||||
GL_PLAYBACK_REQUIRED = 8;
|
|
||||||
AGE_VERIFICATION_REQUIRED = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StreamingData {
|
|
||||||
repeated Format formats = 2;
|
|
||||||
repeated Format adaptiveFormats = 3;
|
|
||||||
string serverAbrStreamingUrl = 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Format {
|
|
||||||
string url = 2;
|
|
||||||
string signatureCipher = 48;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
import "app/revanced/extension/shared/innertube/player_response.proto";
|
|
||||||
|
|
||||||
package app.revanced.extension.shared.innertube;
|
|
||||||
|
|
||||||
option optimize_for = LITE_RUNTIME;
|
|
||||||
option java_package = "app.revanced.extension.shared.innertube";
|
|
||||||
|
|
||||||
message ReelItemWatchResponse {
|
|
||||||
oneof data {
|
|
||||||
PlayerResponse player_response = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,39 @@
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.protobuf)
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:spotify:stub"))
|
compileOnly(project(":extensions:spotify:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
|
||||||
|
implementation(libs.nanohttpd)
|
||||||
|
implementation(libs.protobuf.javalite)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = libs.protobuf.protoc.get().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
generateProtoTasks {
|
||||||
|
all().forEach { task ->
|
||||||
|
task.builtins {
|
||||||
|
create("java") {
|
||||||
|
option("lite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package app.revanced.extension.spotify.layout.hide.createbutton;
|
package app.revanced.extension.spotify.layout.hide.createbutton;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
|
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
|
||||||
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
|
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
|
||||||
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
|
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
|
||||||
|
|
@ -17,7 +16,7 @@ public final class HideCreateButtonPatch {
|
||||||
* The main approach used is matching the resource id for the Create button title.
|
* The main approach used is matching the resource id for the Create button title.
|
||||||
*/
|
*/
|
||||||
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
|
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
|
||||||
new ResourceIdComponentFilter(ResourceType.STRING, "navigationbar_musicappitems_create_title"),
|
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
|
||||||
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
|
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
|
||||||
// and thus getting the resource identifier for the Create button title always return 0.
|
// and thus getting the resource identifier for the Create button title always return 0.
|
||||||
// FIXME: Remove this once the above issue is no longer relevant.
|
// FIXME: Remove this once the above issue is no longer relevant.
|
||||||
|
|
@ -29,7 +28,7 @@ public final class HideCreateButtonPatch {
|
||||||
* Used in older versions of the app.
|
* Used in older versions of the app.
|
||||||
*/
|
*/
|
||||||
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
|
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
|
||||||
new ResourceIdComponentFilter(ResourceType.STRING, "bottom_navigation_bar_create_tab_title");
|
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.*;
|
||||||
|
|
||||||
|
class ClientTokenService {
|
||||||
|
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
|
||||||
|
private static final String IOS_USER_AGENT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String clientVersion = getClientVersion();
|
||||||
|
int commitHashIndex = clientVersion.lastIndexOf(".");
|
||||||
|
String version = clientVersion.substring(
|
||||||
|
clientVersion.indexOf("-") + 1,
|
||||||
|
clientVersion.lastIndexOf(".", commitHashIndex - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
|
||||||
|
ConnectivitySdkData.newBuilder()
|
||||||
|
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
|
||||||
|
.setIos(NativeIOSData.newBuilder()
|
||||||
|
.setHwMachine(getHardwareMachine())
|
||||||
|
.setSystemVersion(getSystemVersion())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
|
||||||
|
ClientDataRequest.newBuilder()
|
||||||
|
.setClientVersion(getClientVersion())
|
||||||
|
.setClientId(IOS_CLIENT_ID);
|
||||||
|
|
||||||
|
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
|
||||||
|
ClientTokenRequest.newBuilder()
|
||||||
|
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
|
||||||
|
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
|
||||||
|
|
||||||
|
return IOS_CLIENT_TOKEN_REQUEST
|
||||||
|
.setClientData(IOS_CLIENT_DATA_REQUEST
|
||||||
|
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
|
||||||
|
.setDeviceId(deviceId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
|
||||||
|
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
|
||||||
|
Logger.printInfo(() -> "Requesting iOS client token");
|
||||||
|
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
|
||||||
|
request = newIOSClientTokenRequest(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTokenResponse response;
|
||||||
|
try {
|
||||||
|
response = requestClientToken(request);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to handle request", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
|
||||||
|
urlConnection.setRequestMethod("POST");
|
||||||
|
urlConnection.setDoOutput(true);
|
||||||
|
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
|
||||||
|
|
||||||
|
byte[] requestArray = request.toByteArray();
|
||||||
|
urlConnection.setFixedLengthStreamingMode(requestArray.length);
|
||||||
|
urlConnection.getOutputStream().write(requestArray);
|
||||||
|
|
||||||
|
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||||
|
return ClientTokenResponse.parseFrom(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
|
||||||
|
ClientTokenRequest request;
|
||||||
|
try {
|
||||||
|
request = ClientTokenRequest.parseFrom(inputStream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to parse request from input stream", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
|
||||||
|
|
||||||
|
ClientTokenResponse response = getClientTokenResponse(request);
|
||||||
|
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
|
||||||
|
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getClientVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getSystemVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getHardwareMachine() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
|
||||||
|
import com.google.protobuf.MessageLite;
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
|
||||||
|
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
||||||
|
|
||||||
|
class RequestListener extends NanoHTTPD {
|
||||||
|
RequestListener(int port) {
|
||||||
|
super(port);
|
||||||
|
|
||||||
|
try {
|
||||||
|
start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Response serve(@NonNull IHTTPSession session) {
|
||||||
|
String uri = session.getUri();
|
||||||
|
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
|
||||||
|
|
||||||
|
Logger.printInfo(() -> "Serving request for URI: " + uri);
|
||||||
|
|
||||||
|
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
|
||||||
|
if (response != null) return newResponse(Response.Status.OK, response);
|
||||||
|
|
||||||
|
Logger.printException(() -> "Failed to serve client token request");
|
||||||
|
return INTERNAL_ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
|
||||||
|
return new FilterInputStream(inputStream) {
|
||||||
|
private long remaining = contentLength;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
int result = super.read();
|
||||||
|
if (result != -1) remaining--;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
len = (int) Math.min(len, remaining);
|
||||||
|
int result = super.read(b, off, len);
|
||||||
|
if (result != -1) remaining -= result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream getInputStream(@NonNull IHTTPSession session) {
|
||||||
|
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
|
||||||
|
return newLimitedInputStream(session.getInputStream(), requestContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.Status status) {
|
||||||
|
return newResponse(status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
||||||
|
if (messageLite == null) {
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] messageBytes = messageLite.toByteArray();
|
||||||
|
InputStream stream = new ByteArrayInputStream(messageBytes);
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SpoofClientPatch {
|
||||||
|
private static RequestListener listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Launch requests listener server.
|
||||||
|
*/
|
||||||
|
public synchronized static void launchListener(int port) {
|
||||||
|
if (listener != null) {
|
||||||
|
Logger.printInfo(() -> "Listener already running on port " + port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger.printInfo(() -> "Launching listener on port " + port);
|
||||||
|
listener = new RequestListener(port);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "launchListener failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,6 @@ public final class SanitizeSharingLinksPatch {
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String sanitizeSharingLink(String url) {
|
public static String sanitizeSharingLink(String url) {
|
||||||
return sanitizer.sanitizeURLString(url);
|
return sanitizer.sanitizeUrlString(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package app.revanced.extension.spotify.shared;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
public final class ComponentFilters {
|
public final class ComponentFilters {
|
||||||
|
|
@ -20,26 +19,21 @@ public final class ComponentFilters {
|
||||||
public static final class ResourceIdComponentFilter implements ComponentFilter {
|
public static final class ResourceIdComponentFilter implements ComponentFilter {
|
||||||
|
|
||||||
public final String resourceName;
|
public final String resourceName;
|
||||||
public final ResourceType resourceType;
|
public final String resourceType;
|
||||||
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
||||||
// 0 is returned when a resource has not been found.
|
// 0 is returned when a resource has not been found.
|
||||||
private int resourceId = -1;
|
private int resourceId = -1;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String stringfiedResourceId;
|
private String stringfiedResourceId;
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
||||||
this(ResourceType.valueOf(resourceType), resourceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceIdComponentFilter(ResourceType resourceType, String resourceName) {
|
|
||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
this.resourceType = resourceType;
|
this.resourceType = resourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getResourceId() {
|
public int getResourceId() {
|
||||||
if (resourceId == -1) {
|
if (resourceId == -1) {
|
||||||
resourceId = Utils.getResourceIdentifier(resourceType, resourceName);
|
resourceId = Utils.getResourceIdentifier(resourceName, resourceType);
|
||||||
}
|
}
|
||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package spotify.clienttoken.data.v0;
|
||||||
|
|
||||||
|
option optimize_for = LITE_RUNTIME;
|
||||||
|
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
|
||||||
|
|
||||||
|
message ClientTokenRequest {
|
||||||
|
ClientTokenRequestType request_type = 1;
|
||||||
|
|
||||||
|
oneof request {
|
||||||
|
ClientDataRequest client_data = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenRequestType {
|
||||||
|
REQUEST_UNKNOWN = 0;
|
||||||
|
REQUEST_CLIENT_DATA_REQUEST = 1;
|
||||||
|
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientDataRequest {
|
||||||
|
string client_version = 1;
|
||||||
|
string client_id = 2;
|
||||||
|
|
||||||
|
oneof data {
|
||||||
|
ConnectivitySdkData connectivity_sdk_data = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectivitySdkData {
|
||||||
|
PlatformSpecificData platform_specific_data = 1;
|
||||||
|
string device_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlatformSpecificData {
|
||||||
|
oneof data {
|
||||||
|
NativeIOSData ios = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message NativeIOSData {
|
||||||
|
int32 user_interface_idiom = 1;
|
||||||
|
bool target_iphone_simulator = 2;
|
||||||
|
string hw_machine = 3;
|
||||||
|
string system_version = 4;
|
||||||
|
string simulator_model_identifier = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientTokenResponse {
|
||||||
|
ClientTokenResponseType response_type = 1;
|
||||||
|
|
||||||
|
oneof response {
|
||||||
|
GrantedTokenResponse granted_token = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenResponseType {
|
||||||
|
RESPONSE_UNKNOWN = 0;
|
||||||
|
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
|
||||||
|
RESPONSE_CHALLENGES_RESPONSE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GrantedTokenResponse {
|
||||||
|
string token = 1;
|
||||||
|
int32 expires_after_seconds = 2;
|
||||||
|
int32 refresh_after_seconds = 3;
|
||||||
|
repeated TokenDomain domains = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TokenDomain {
|
||||||
|
string domain = 1;
|
||||||
|
}
|
||||||
|
|
@ -7,11 +7,11 @@ android {
|
||||||
compileSdk = 34
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,3 @@ dependencies {
|
||||||
compileOnly(project(":extensions:strava:stub"))
|
compileOnly(project(":extensions:strava:stub"))
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 26
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
package app.revanced.extension.strava;
|
package app.revanced.extension.strava;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import app.revanced.extension.shared.ResourceType;
|
|
||||||
import com.strava.mediamodels.data.MediaType;
|
import com.strava.mediamodels.data.MediaType;
|
||||||
import com.strava.photos.data.Media;
|
import com.strava.photos.data.Media;
|
||||||
|
|
||||||
|
|
@ -26,6 +27,7 @@ import java.util.stream.Stream;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
public final class AddMediaDownloadPatch {
|
public final class AddMediaDownloadPatch {
|
||||||
public static final int ACTION_DOWNLOAD = -1;
|
public static final int ACTION_DOWNLOAD = -1;
|
||||||
public static final int ACTION_OPEN_LINK = -2;
|
public static final int ACTION_OPEN_LINK = -2;
|
||||||
|
|
@ -82,7 +84,7 @@ public final class AddMediaDownloadPatch {
|
||||||
} finally {
|
} finally {
|
||||||
values.clear();
|
values.clear();
|
||||||
values.put(MediaStore.Images.Media.IS_PENDING, 0);
|
values.put(MediaStore.Images.Media.IS_PENDING, 0);
|
||||||
resolver.update(row, values, null, null);
|
resolver.update(row, values, null);
|
||||||
}
|
}
|
||||||
showInfoToast("yis_2024_local_save_image_success", "✔️");
|
showInfoToast("yis_2024_local_save_image_success", "✔️");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -148,7 +150,7 @@ public final class AddMediaDownloadPatch {
|
||||||
} finally {
|
} finally {
|
||||||
values.clear();
|
values.clear();
|
||||||
values.put(MediaStore.Video.Media.IS_PENDING, 0);
|
values.put(MediaStore.Video.Media.IS_PENDING, 0);
|
||||||
resolver.update(row, values, null, null);
|
resolver.update(row, values, null);
|
||||||
}
|
}
|
||||||
showInfoToast("yis_2024_local_save_video_success", "✔️");
|
showInfoToast("yis_2024_local_save_video_success", "✔️");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -164,7 +166,7 @@ public final class AddMediaDownloadPatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getString(String name, String fallback) {
|
private static String getString(String name, String fallback) {
|
||||||
int id = Utils.getResourceIdentifier(ResourceType.STRING, name);
|
int id = Utils.getResourceIdentifier(name, "string");
|
||||||
return id != 0
|
return id != 0
|
||||||
? Utils.getResourceString(id)
|
? Utils.getResourceString(id)
|
||||||
: fallback;
|
: fallback;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package app.revanced.extension.strava;
|
package app.revanced.extension.strava;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import com.strava.modularframework.data.Destination;
|
import com.strava.modularframework.data.Destination;
|
||||||
import com.strava.modularframework.data.GenericLayoutModule;
|
import com.strava.modularframework.data.GenericLayoutModule;
|
||||||
import com.strava.modularframework.data.GenericModuleField;
|
import com.strava.modularframework.data.GenericModuleField;
|
||||||
|
|
@ -19,6 +21,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
public class HideDistractionsPatch {
|
public class HideDistractionsPatch {
|
||||||
public static boolean upselling;
|
public static boolean upselling;
|
||||||
public static boolean promo;
|
public static boolean promo;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ android {
|
||||||
compileSdk = 34
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 26
|
minSdk = 21
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,3 @@ dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
compileOnly(libs.okhttp)
|
compileOnly(libs.okhttp)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 23
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,9 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 22
|
minSdk = 22
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,22 @@ import app.revanced.extension.tiktok.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class RememberClearDisplayPatch {
|
public class RememberClearDisplayPatch {
|
||||||
|
|
||||||
|
private static Boolean cachedState = null;
|
||||||
|
|
||||||
public static boolean getClearDisplayState() {
|
public static boolean getClearDisplayState() {
|
||||||
return Settings.CLEAR_DISPLAY.get();
|
if (cachedState == null) {
|
||||||
|
cachedState = Settings.CLEAR_DISPLAY.get();
|
||||||
}
|
}
|
||||||
|
return cachedState;
|
||||||
|
}
|
||||||
|
|
||||||
public static void rememberClearDisplayState(boolean newState) {
|
public static void rememberClearDisplayState(boolean newState) {
|
||||||
|
if (cachedState != null && cachedState == newState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedState = newState;
|
||||||
Settings.CLEAR_DISPLAY.save(newState);
|
Settings.CLEAR_DISPLAY.save(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,43 @@
|
||||||
package app.revanced.extension.tiktok.download;
|
package app.revanced.extension.tiktok.download;
|
||||||
|
|
||||||
import app.revanced.extension.tiktok.settings.Settings;
|
import com.ss.android.ugc.aweme.feed.model.Video;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class DownloadsPatch {
|
public class DownloadsPatch {
|
||||||
|
|
||||||
public static String getDownloadPath() {
|
public static String getDownloadPath() {
|
||||||
return Settings.DOWNLOAD_PATH.get();
|
return "Pictures/Tiktok";
|
||||||
|
//return Settings.DOWNLOAD_PATH.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldRemoveWatermark() {
|
public static boolean shouldRemoveWatermark() {
|
||||||
return Settings.DOWNLOAD_WATERMARK.get();
|
return true;
|
||||||
|
//return Settings.DOWNLOAD_WATERMARK.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void patchVideoObject(Video video) {
|
||||||
|
if (video == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean isMissingCleanUrl = false;
|
||||||
|
|
||||||
|
// non-watermark url is removed by tiktok for some videos (licensing/user restrictions)
|
||||||
|
if (video.downloadNoWatermarkAddr == null) {
|
||||||
|
isMissingCleanUrl = true;
|
||||||
|
} else if (video.downloadNoWatermarkAddr.getUrlList() == null || video.downloadNoWatermarkAddr.getUrlList().isEmpty()) {
|
||||||
|
isMissingCleanUrl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite field with the play address if empty
|
||||||
|
if (isMissingCleanUrl) {
|
||||||
|
if (video.h264PlayAddr != null && video.h264PlayAddr.getUrlList() != null && !video.h264PlayAddr.getUrlList().isEmpty()) {
|
||||||
|
video.downloadNoWatermarkAddr = video.h264PlayAddr;
|
||||||
|
} else if (video.playAddr != null) {
|
||||||
|
// fallback
|
||||||
|
video.downloadNoWatermarkAddr = video.playAddr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,11 +6,35 @@ import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
public class AdsFilter implements IFilter {
|
public class AdsFilter implements IFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean getEnabled() {
|
public boolean getEnabled() {
|
||||||
return Settings.REMOVE_ADS.get();
|
return true;
|
||||||
|
// return Settings.REMOVE_ADS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getFiltered(Aweme item) {
|
public boolean getFiltered(Aweme item) {
|
||||||
return item.isAd() || item.isWithPromotionalMusic();
|
if (item == null) return false;
|
||||||
|
|
||||||
|
// TikTok's Internal Commercial Types
|
||||||
|
// Verified in AwemeExtKt: 1, 29, 30, 32, 33, 201 are commercial
|
||||||
|
int type = item.getAwemeType();
|
||||||
|
if (type == 1 || type == 29 || type == 30 || type == 32 || type == 33 || type == 201) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ad Flags (Hard and Soft/Sponsored)
|
||||||
|
if (item.isAd || item.isSoftAd || item.awemeRawAd != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Music Marketing
|
||||||
|
if (item.isWithPromotionalMusic()) return true;
|
||||||
|
|
||||||
|
if (item.mCommerceVideoAuthInfo != null) {
|
||||||
|
// PseudoAds (Spark Ads) and Branded Content
|
||||||
|
return item.mCommerceVideoAuthInfo.isBrandedContent() ||
|
||||||
|
item.mCommerceVideoAuthInfo.isPseudoAd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package app.revanced.extension.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class BloatFilter implements IFilter {
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
if (item == null) return false;
|
||||||
|
|
||||||
|
// Full screen promos
|
||||||
|
if (item.isReferralFakeAweme || item.isRecBigCardFakeAweme) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System cards (non video interrupts)
|
||||||
|
if (item.awemeType == 104 || item.awemeType == 105) return true;
|
||||||
|
|
||||||
|
// Accounts to follow recs and overlays
|
||||||
|
if (item.recommendCardType != 0) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,31 +8,41 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class FeedItemsFilter {
|
public final class FeedItemsFilter {
|
||||||
private static final List<IFilter> FILTERS = List.of(
|
|
||||||
|
private static final IFilter[] FILTERS = new IFilter[] {
|
||||||
new AdsFilter(),
|
new AdsFilter(),
|
||||||
new LiveFilter(),
|
new LiveFilter(),
|
||||||
|
new ShopFilter(),
|
||||||
new StoryFilter(),
|
new StoryFilter(),
|
||||||
new ImageVideoFilter(),
|
new ImageVideoFilter(),
|
||||||
new ViewCountFilter(),
|
new BloatFilter()
|
||||||
new LikeCountFilter(),
|
};
|
||||||
new ShopFilter()
|
|
||||||
);
|
|
||||||
|
|
||||||
public static void filter(FeedItemList feedItemList) {
|
public static void filter(FeedItemList feedItemList) {
|
||||||
|
if (feedItemList == null || feedItemList.items == null) return;
|
||||||
filterFeedList(feedItemList.items, item -> item);
|
filterFeedList(feedItemList.items, item -> item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void filter(FollowFeedList followFeedList) {
|
public static void filter(FollowFeedList followFeedList) {
|
||||||
|
if (followFeedList == null || followFeedList.mItems == null) return;
|
||||||
filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
|
filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> void filterFeedList(List<T> list, AwemeExtractor<T> extractor) {
|
private static <T> void filterFeedList(
|
||||||
// Could be simplified with removeIf() but requires Android 7.0+ while TikTok supports 4.0+.
|
List<T> list,
|
||||||
|
AwemeExtractor<T> extractor
|
||||||
|
) {
|
||||||
|
if (list == null) return;
|
||||||
|
|
||||||
Iterator<T> iterator = list.iterator();
|
Iterator<T> iterator = list.iterator();
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
T container = iterator.next();
|
T container = iterator.next();
|
||||||
Aweme item = extractor.extract(container);
|
Aweme item = extractor.extract(container);
|
||||||
if (item != null && shouldFilter(item)) {
|
|
||||||
|
if (item == null) continue;
|
||||||
|
|
||||||
|
if (shouldFilter(item)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,17 @@ public class ImageVideoFilter implements IFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getFiltered(Aweme item) {
|
public boolean getFiltered(Aweme item) {
|
||||||
return item.isImage() || item.isPhotoMode();
|
if (item == null) return false;
|
||||||
|
|
||||||
|
int type = item.getAwemeType();
|
||||||
|
|
||||||
|
// 2 = Standard Image, 150 = Photo Mode, 160 = Text Mode
|
||||||
|
if (type == 2 || type == 150 || type == 160) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback checks
|
||||||
|
var imageInfos = item.getImageInfos();
|
||||||
|
return imageInfos != null && !imageInfos.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ public final class LikeCountFilter implements IFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getFiltered(Aweme item) {
|
public boolean getFiltered(Aweme item) {
|
||||||
AwemeStatistics statistics = item.getStatistics();
|
AwemeStatistics statistics = item.statistics;
|
||||||
|
|
||||||
|
if (statistics == null) statistics = item.getStatistics();
|
||||||
|
|
||||||
if (statistics == null) return false;
|
if (statistics == null) return false;
|
||||||
|
|
||||||
long likeCount = statistics.getDiggCount();
|
long likeCount = statistics.getDiggCount();
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue