mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-30 22:21:02 +00:00
Compare commits
326 Commits
v5.47.0-de
...
feat/moder
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48b3a2d18c | ||
|
|
d39c53f677 | ||
|
|
2ea26b02ea | ||
|
|
acac6e960c | ||
|
|
e928128a94 | ||
|
|
7d891afd2e | ||
|
|
545fc62b79 | ||
|
|
03c7e353a7 | ||
|
|
43f919d9ad | ||
|
|
ac5b65fcb5 | ||
|
|
2c0e81ee17 | ||
|
|
f681a6ffd4 | ||
|
|
cd23ee4b6c | ||
|
|
bfdc103a93 | ||
|
|
59f9e4328f | ||
|
|
bd6e544007 | ||
|
|
b6975fe015 | ||
|
|
7302d29383 | ||
|
|
bae293e3dd | ||
|
|
1026a8fbd7 | ||
|
|
c82691380c | ||
|
|
dbdd2a67ff | ||
|
|
80c34b9d74 | ||
|
|
debf297d9e | ||
|
|
dc9e68ba57 | ||
|
|
3a3fe5ed9b | ||
|
|
56eff2a625 | ||
|
|
230ab2fa59 | ||
|
|
d6a437c7b1 | ||
|
|
99bfecbc97 | ||
|
|
cab788d4ad | ||
|
|
2fac6ccb32 | ||
|
|
6da8a69f22 | ||
|
|
6afaf53370 | ||
|
|
466043132a | ||
|
|
e192cbae52 | ||
|
|
e79b4246a5 | ||
|
|
8c72146bb9 | ||
|
|
c1a8045ebb | ||
|
|
63a73d0eab | ||
|
|
d0ea3d8d31 | ||
|
|
ef3272bf90 | ||
|
|
4950ac412f | ||
|
|
ebea32fe87 | ||
|
|
66b0852f8f | ||
|
|
421cb2899e | ||
|
|
97e74157fa | ||
|
|
5edd9dccae | ||
|
|
9c18e1e649 | ||
|
|
71ce8230a9 | ||
|
|
156441d3cf | ||
|
|
634f47ef84 | ||
|
|
eeb133325e | ||
|
|
69aa683901 | ||
|
|
29699557aa | ||
|
|
e8d58ca9af | ||
|
|
5b5c50254d | ||
|
|
ef052c0d8f | ||
|
|
d9fa580222 | ||
|
|
182224c79d | ||
|
|
83c0127ebb | ||
|
|
3762f1de08 | ||
|
|
18c0b04f0c | ||
|
|
4c4ba1c78c | ||
|
|
7cef24a5e9 | ||
|
|
8725a49ba3 | ||
|
|
8b6360e34f | ||
|
|
a10c51f160 | ||
|
|
eecc44b956 | ||
|
|
44320b34f6 | ||
|
|
62aa4ed555 | ||
|
|
e7d2d7d62b | ||
|
|
3401467a6d | ||
|
|
5a15476a9f | ||
|
|
50f95543f1 | ||
|
|
87247590de | ||
|
|
41e2590584 | ||
|
|
778d13ce8b | ||
|
|
19f146c01d | ||
|
|
8c0c3b44e5 | ||
|
|
811a7855f1 | ||
|
|
bce0a745db | ||
|
|
f4a04e7917 | ||
|
|
be005c533a | ||
|
|
70328c5a65 | ||
|
|
1ee89fe0e6 | ||
|
|
530f81b436 | ||
|
|
198cb93ef2 | ||
|
|
ad783451ed | ||
|
|
165f4b45e0 | ||
|
|
fcce0a6948 | ||
|
|
357051105c | ||
|
|
2e6b7c2fe4 | ||
|
|
dd5b9ac58d | ||
|
|
5477e8edbe | ||
|
|
f4f43660da | ||
|
|
60861a8506 | ||
|
|
decd7cb23c | ||
|
|
8dfcbd09cc | ||
|
|
94cc175b55 | ||
|
|
b6c1ec7d18 | ||
|
|
cdec438fd4 | ||
|
|
555b6fe3ca | ||
|
|
7787268bed | ||
|
|
24cb9d987b | ||
|
|
68a57901d8 | ||
|
|
e2bc428b29 | ||
|
|
968dfde7a8 | ||
|
|
8b833268bb | ||
|
|
eadc76b161 | ||
|
|
4540e7a484 | ||
|
|
da08df7d9e | ||
|
|
f8e912f937 | ||
|
|
ae0240c69b | ||
|
|
7a41f5b285 | ||
|
|
12b819d20e | ||
|
|
61225c825c | ||
|
|
c7ea78824a | ||
|
|
6a4c03220e | ||
|
|
c17bfcb452 | ||
|
|
1cecadf54c | ||
|
|
e7d596e4d8 | ||
|
|
d5c56c4889 | ||
|
|
8e94a17c28 | ||
|
|
b8cacb507d | ||
|
|
6dfe4440bb | ||
|
|
043cae5db6 | ||
|
|
b174421e2b | ||
|
|
b6d1f1c62a | ||
|
|
52ae070369 | ||
|
|
8fbcf002e4 | ||
|
|
678645723a | ||
|
|
004b5908db | ||
|
|
32dfe48ad7 | ||
|
|
a103eb5b7a | ||
|
|
69a71fbd3a | ||
|
|
b6cc108fba | ||
|
|
f4af27dfec | ||
|
|
4cc315952d | ||
|
|
6312fe8d60 | ||
|
|
3d754575a4 | ||
|
|
cceb7a7c43 | ||
|
|
a3f7609fe3 | ||
|
|
d25dcfe49a | ||
|
|
1cc2cb9cb2 | ||
|
|
f5cbb31724 | ||
|
|
b42ae27ce6 | ||
|
|
43ab29d03d | ||
|
|
789f0a5628 | ||
|
|
da836b667c | ||
|
|
44e7dbcf4d | ||
|
|
30076b7a9b | ||
|
|
bbd8ae0e24 | ||
|
|
641a23b35a | ||
|
|
195c239000 | ||
|
|
c47beae213 | ||
|
|
cebcfab86a | ||
|
|
71c6cb569e | ||
|
|
6bb6281149 | ||
|
|
16bd96e2bb | ||
|
|
8f3f4c95bb | ||
|
|
da02d68587 | ||
|
|
a429824bb7 | ||
|
|
e0f33468e6 | ||
|
|
315931cbf8 | ||
|
|
dc69f2433e | ||
|
|
73e43b2a49 | ||
|
|
918f04793f | ||
|
|
f1a9537f01 | ||
|
|
1f4f252c81 | ||
|
|
2b560f5fe9 | ||
|
|
f8bd1239cc | ||
|
|
c825ebda37 | ||
|
|
255c00b183 | ||
|
|
0ea3491227 | ||
|
|
5d437b08dd | ||
|
|
018d176914 | ||
|
|
9a77beea8a | ||
|
|
ded8370207 | ||
|
|
4d1104fc32 | ||
|
|
34830ba63b | ||
|
|
7a6894d809 | ||
|
|
144e6e2694 | ||
|
|
847ee189a9 | ||
|
|
dc813fe617 | ||
|
|
02831a6069 | ||
|
|
5228fd4b58 | ||
|
|
6bd7dca75b | ||
|
|
22ed7bfbb3 | ||
|
|
a7c220a4ae | ||
|
|
d8ca4ee931 | ||
|
|
a5d197b977 | ||
|
|
a0ec4c07f7 | ||
|
|
0928dcd00d | ||
|
|
bbd8932b2e | ||
|
|
300b12f948 | ||
|
|
ac583d40d0 | ||
|
|
e1e03e1b69 | ||
|
|
8c603802f7 | ||
|
|
c400188c38 | ||
|
|
0af0ee92c4 | ||
|
|
95c72ad300 | ||
|
|
2f3ecab0e1 | ||
|
|
a5d39c3bbe | ||
|
|
07c4dd3a55 | ||
|
|
67c6c345ea | ||
|
|
ed514d9755 | ||
|
|
0e994f5bfe | ||
|
|
8cc69fe38b | ||
|
|
41b31dd56c | ||
|
|
9671c7499d | ||
|
|
d62d17fdeb | ||
|
|
c6eaba9af6 | ||
|
|
fff29544b9 | ||
|
|
9495cf49ef | ||
|
|
15675b5164 | ||
|
|
654d091e65 | ||
|
|
98371be33c | ||
|
|
2f0de15e67 | ||
|
|
db5b79ddbb | ||
|
|
a1a80ebc57 | ||
|
|
5ccfb3cb9f | ||
|
|
2687b3006b | ||
|
|
29a86fb8ec | ||
|
|
81a429af74 | ||
|
|
3406033732 | ||
|
|
afbcf3d90f | ||
|
|
b7c995930a | ||
|
|
675a2c4209 | ||
|
|
0855290097 | ||
|
|
49c925e95f | ||
|
|
7499f3d19b | ||
|
|
3d55083dbc | ||
|
|
829bfa76d1 | ||
|
|
88352d8774 | ||
|
|
03ce5711de | ||
|
|
fc70f852f9 | ||
|
|
955f7c9341 | ||
|
|
93160722c0 | ||
|
|
78689fde83 | ||
|
|
260afefaab | ||
|
|
b7be52dec6 | ||
|
|
594317e573 | ||
|
|
c95170ecbd | ||
|
|
ecda492866 | ||
|
|
4a73671262 | ||
|
|
6d72b4a3fb | ||
|
|
f00c0e0d89 | ||
|
|
772620c8ce | ||
|
|
9984e586b4 | ||
|
|
9a2f23291d | ||
|
|
7ef70f7823 | ||
|
|
a1fd6b13d5 | ||
|
|
f30ece9287 | ||
|
|
f2356a8be2 | ||
|
|
47f1a5f9c9 | ||
|
|
fc988fa078 | ||
|
|
c37527f182 | ||
|
|
64334b4f79 | ||
|
|
0389073600 | ||
|
|
5449357f7f | ||
|
|
39da47e6ee | ||
|
|
1356a7e5b2 | ||
|
|
b5cda51048 | ||
|
|
a1ad5fea20 | ||
|
|
c98c73b0bc | ||
|
|
ed87bc7b7a | ||
|
|
1901e965e8 | ||
|
|
aae71e6a19 | ||
|
|
a1c9170cc9 | ||
|
|
88b077a9b7 | ||
|
|
563f586eed | ||
|
|
c6becb4044 | ||
|
|
2d207fccbc | ||
|
|
967ef47c2d | ||
|
|
240e953160 | ||
|
|
0f03a071e9 | ||
|
|
e52b33981c | ||
|
|
4d9de1a81a | ||
|
|
636bded69c | ||
|
|
2d49d76e82 | ||
|
|
934947a257 | ||
|
|
8e05bb3a80 | ||
|
|
f88ad4e4a7 | ||
|
|
f252fb24b6 | ||
|
|
2f9081eb6c | ||
|
|
a82f49aa08 | ||
|
|
823530f707 | ||
|
|
7eb78d4f2b | ||
|
|
4dec67385e | ||
|
|
edaad1a7b7 | ||
|
|
d3df24977a | ||
|
|
7b02a31e3f | ||
|
|
41c8fbc10d | ||
|
|
59e1321e62 | ||
|
|
252f57f430 | ||
|
|
d6593e2acd | ||
|
|
c4e6e62e71 | ||
|
|
ca736094e4 | ||
|
|
7e010d38cc | ||
|
|
a4d24ad192 | ||
|
|
45d42a1405 | ||
|
|
ecf5752100 | ||
|
|
4096b34003 | ||
|
|
23b200ce68 | ||
|
|
56876f336b | ||
|
|
c7a71f44df | ||
|
|
a25d769f69 | ||
|
|
c2a099d1f4 | ||
|
|
d906046a52 | ||
|
|
765957f2c9 | ||
|
|
8e64416f14 | ||
|
|
6a5b204f8e | ||
|
|
b99789b1cd | ||
|
|
bb671766f6 | ||
|
|
97ce498368 | ||
|
|
dcaa6feda0 | ||
|
|
e8d56c85cc | ||
|
|
04401899f4 | ||
|
|
e52a9509e2 | ||
|
|
c6d84744ca | ||
|
|
d3fae2a3e7 | ||
|
|
6ddf0583a4 | ||
|
|
77864f41f4 | ||
|
|
a352a05db6 | ||
|
|
724e6d61b2 |
3
.github/workflows/build_pull_request.yml
vendored
3
.github/workflows/build_pull_request.yml
vendored
@@ -25,7 +25,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
|
||||||
|
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
|
||||||
|
|||||||
2
.github/workflows/pull_strings.yml
vendored
2
.github/workflows/pull_strings.yml
vendored
@@ -2,7 +2,7 @@ name: Pull strings
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 */12 * * *"
|
- cron: "0 0 * * 0"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
7
.github/workflows/push_strings.yml
vendored
7
.github/workflows/push_strings.yml
vendored
@@ -16,10 +16,11 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Preprocess strings
|
- name: Process strings
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
run: ./gradlew clean preprocessCrowdinStrings
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew processStringsForCrowdin
|
||||||
|
|
||||||
- name: Push strings
|
- name: Push strings
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
|
|||||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -31,7 +31,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew :patches:buildAndroid clean
|
run: ./gradlew :patches:buildAndroid clean
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
@@ -55,6 +56,8 @@ jobs:
|
|||||||
id: release
|
id: release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Attest
|
- name: Attest
|
||||||
if: steps.release.outputs.new_release_published == 'true'
|
if: steps.release.outputs.new_release_published == 'true'
|
||||||
|
|||||||
361
CHANGELOG.md
361
CHANGELOG.md
@@ -1,3 +1,364 @@
|
|||||||
|
# [5.50.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.3...v5.50.0-dev.4) (2026-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.50.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.2...v5.50.0-dev.3) (2026-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb))
|
||||||
|
|
||||||
|
# [5.50.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.1...v5.50.0-dev.2) (2026-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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
|
||||||
|
|
||||||
|
* **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 `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.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **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 `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.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](https://github.com/ReVanced/revanced-patches/commit/89645dcc2e13603b8f2fedb5e16231cb396e5965))
|
||||||
|
|
||||||
|
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9))
|
||||||
|
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
|
||||||
|
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
|
||||||
|
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
|
||||||
|
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
|
||||||
|
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
|
||||||
|
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
|
||||||
|
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
|
||||||
|
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
|
||||||
|
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e))
|
||||||
|
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48))
|
||||||
|
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307))
|
||||||
|
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
|
||||||
|
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
|
||||||
|
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
|
||||||
|
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f))
|
||||||
|
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44))
|
||||||
|
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
|
||||||
|
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
|
||||||
|
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
|
||||||
|
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
|
||||||
|
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8))
|
||||||
|
|
||||||
|
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
|
||||||
|
|
||||||
|
# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48))
|
||||||
|
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f))
|
||||||
|
|
||||||
|
# [5.48.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.10...v5.48.0-dev.11) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e))
|
||||||
|
|
||||||
|
# [5.48.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.9...v5.48.0-dev.10) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9))
|
||||||
|
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307))
|
||||||
|
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44))
|
||||||
|
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8))
|
||||||
|
|
||||||
|
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
|
||||||
|
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
|
||||||
|
|
||||||
|
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
|
||||||
|
|
||||||
|
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
|
||||||
|
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
|
||||||
|
|
||||||
|
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
|
||||||
|
|
||||||
|
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
|
||||||
|
|
||||||
|
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
|
||||||
|
|
||||||
|
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
|
||||||
|
|
||||||
|
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
|
||||||
|
|
||||||
|
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
|
||||||
|
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
|
||||||
|
|
||||||
|
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76))
|
||||||
|
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
|
||||||
|
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
|
||||||
|
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6))
|
||||||
|
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
|
||||||
|
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
|
||||||
|
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83))
|
||||||
|
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
|
||||||
|
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17))
|
||||||
|
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
|
||||||
|
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a))
|
||||||
|
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
|
||||||
|
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c))
|
||||||
|
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436))
|
||||||
|
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
|
||||||
|
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf))
|
||||||
|
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e))
|
||||||
|
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228))
|
||||||
|
|
||||||
|
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
|
||||||
|
|
||||||
|
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
|
||||||
|
|
||||||
|
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
|
||||||
|
|
||||||
|
# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6))
|
||||||
|
|
||||||
|
# [5.47.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.13...v5.47.0-dev.14) (2025-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146))
|
||||||
|
|
||||||
|
# [5.47.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.12...v5.47.0-dev.13) (2025-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c))
|
||||||
|
|
||||||
|
# [5.47.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.11...v5.47.0-dev.12) (2025-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e))
|
||||||
|
|
||||||
|
# [5.47.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.10...v5.47.0-dev.11) (2025-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83))
|
||||||
|
|
||||||
|
# [5.47.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.9...v5.47.0-dev.10) (2025-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228))
|
||||||
|
|
||||||
|
# [5.47.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.8...v5.47.0-dev.9) (2025-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf))
|
||||||
|
|
||||||
|
# [5.47.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.7...v5.47.0-dev.8) (2025-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a))
|
||||||
|
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436))
|
||||||
|
|
||||||
|
# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
|
||||||
|
|
||||||
|
# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
|
||||||
|
|
||||||
|
# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
|
||||||
|
|
||||||
|
# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
|
||||||
|
|
||||||
|
# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
|
||||||
|
|
||||||
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)
|
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ Thank you for considering contributing to ReVanced Patches. You can find the con
|
|||||||
|
|
||||||
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
|
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
|
||||||
|
|
||||||
## 📜 Licence
|
## 📜 License
|
||||||
|
|
||||||
ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
||||||
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
|
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
|
||||||
Any modifications to ReVanced Patches must also be made available under the GPL,
|
Any modifications to ReVanced Patches must also be made available under the GPL,
|
||||||
along with build & install instructions.
|
along with build & install instructions.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
project_id_env: "CROWDIN_PROJECT_ID"
|
project_id_env: "CROWDIN_PROJECT_ID"
|
||||||
api_token_env: "CROWDIN_PERSONAL_TOKEN"
|
api_token_env: "CROWDIN_PERSONAL_TOKEN"
|
||||||
|
|
||||||
preserve_hierarchy: false
|
preserve_hierarchy: true
|
||||||
files:
|
files:
|
||||||
- source: patches/src/main/resources/addresources/values/strings.xml
|
- source: patches/src/main/resources/addresources/values/strings.xml
|
||||||
|
dest: patches.xml
|
||||||
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
|
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
|
||||||
skip_untranslated_strings: true
|
skip_untranslated_strings: true
|
||||||
|
|||||||
20
extensions/all/misc/disable-play-integrity/build.gradle.kts
Normal file
20
extensions/all/misc/disable-play-integrity/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
aidl = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.google.android.play.core.integrity.protocol;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
|
||||||
|
|
||||||
|
interface IExpressIntegrityService {
|
||||||
|
oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.google.android.play.core.integrity.protocol;
|
||||||
|
|
||||||
|
interface IExpressIntegrityServiceCallback {
|
||||||
|
oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.google.android.play.core.integrity.protocol;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
|
||||||
|
|
||||||
|
interface IIntegrityService {
|
||||||
|
oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.google.android.play.core.integrity.protocol;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
interface IIntegrityServiceCallback {
|
||||||
|
oneway void onResult(in Bundle result) = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package android.ext;
|
||||||
|
/** @hide */
|
||||||
|
// Int values that are assigned to packages in this interface can be retrieved at runtime from
|
||||||
|
// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server).
|
||||||
|
//
|
||||||
|
// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check
|
||||||
|
// or by a check that the APK is stored on an immutable OS partition.
|
||||||
|
public interface PackageId {
|
||||||
|
String PLAY_STORE_NAME = "com.android.vending";
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package android.os;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class BinderWrapper implements IBinder {
|
||||||
|
protected final IBinder base;
|
||||||
|
|
||||||
|
public BinderWrapper(IBinder base) {
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
|
||||||
|
return base.transact(code, data, reply, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IInterface queryLocalInterface(@NonNull String descriptor) {
|
||||||
|
return base.queryLocalInterface(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getInterfaceDescriptor() throws RemoteException {
|
||||||
|
return base.getInterfaceDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean pingBinder() {
|
||||||
|
return base.pingBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBinderAlive() {
|
||||||
|
return base.isBinderAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
|
||||||
|
base.dump(fd, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
|
||||||
|
base.dumpAsync(fd, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException {
|
||||||
|
base.linkToDeath(recipient, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
|
||||||
|
return base.unlinkToDeath(recipient, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package app.grapheneos.gmscompat.lib.playintegrity;
|
||||||
|
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.os.FakeBackgroundHandler;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IIntegrityService;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
|
||||||
|
|
||||||
|
class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
|
||||||
|
|
||||||
|
ClassicPlayIntegrityServiceWrapper(IBinder base) {
|
||||||
|
super(base);
|
||||||
|
requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TokenRequestStub extends IIntegrityService.Stub {
|
||||||
|
public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) {
|
||||||
|
Runnable r = () -> {
|
||||||
|
var result = new Bundle();
|
||||||
|
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE
|
||||||
|
final int API_NOT_AVAILABLE = -1;
|
||||||
|
result.putInt("error", API_NOT_AVAILABLE);
|
||||||
|
try {
|
||||||
|
callback.onResult(result);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e("IIntegrityService.Stub", "", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Binder createTokenRequestStub() {
|
||||||
|
return new TokenRequestStub();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package app.grapheneos.gmscompat.lib.playintegrity;
|
||||||
|
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.BinderWrapper;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
abstract class PlayIntegrityServiceWrapper extends BinderWrapper {
|
||||||
|
final String TAG;
|
||||||
|
protected int requestIntegrityTokenTxnCode;
|
||||||
|
|
||||||
|
public PlayIntegrityServiceWrapper(IBinder base) {
|
||||||
|
super(base);
|
||||||
|
TAG = getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Binder createTokenRequestStub();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
|
||||||
|
if (code == requestIntegrityTokenTxnCode) {
|
||||||
|
if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.transact(code, data, reply, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) {
|
||||||
|
Log.d(TAG, "integrity token request detected");
|
||||||
|
|
||||||
|
try {
|
||||||
|
createTokenRequestStub().transact(code, data, reply, flags);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// this is a local call
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static long getTokenRequestResultDelay() {
|
||||||
|
return 500L;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package app.grapheneos.gmscompat.lib.playintegrity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.ext.PackageId;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class PlayIntegrityUtils {
|
||||||
|
|
||||||
|
public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) {
|
||||||
|
if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) {
|
||||||
|
UnaryOperator<IBinder> binderOverride = null;
|
||||||
|
|
||||||
|
final String CLASSIC_SERVICE =
|
||||||
|
"com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE";
|
||||||
|
final String STANDARD_SERVICE =
|
||||||
|
"com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE";
|
||||||
|
|
||||||
|
String action = service.getAction();
|
||||||
|
if (STANDARD_SERVICE.equals(action)) {
|
||||||
|
binderOverride = StandardPlayIntegrityServiceWrapper::new;
|
||||||
|
} else if (CLASSIC_SERVICE.equals(action)) {
|
||||||
|
binderOverride = ClassicPlayIntegrityServiceWrapper::new;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binderOverride != null) {
|
||||||
|
return new ServiceConnectionWrapper(orig, binderOverride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package app.grapheneos.gmscompat.lib.playintegrity;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.android.internal.os.FakeBackgroundHandler;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IExpressIntegrityService;
|
||||||
|
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
|
||||||
|
|
||||||
|
@SuppressLint("LongLogTag")
|
||||||
|
class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
|
||||||
|
|
||||||
|
StandardPlayIntegrityServiceWrapper(IBinder base) {
|
||||||
|
super(base);
|
||||||
|
requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TokenRequestStub extends IExpressIntegrityService.Stub {
|
||||||
|
public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) {
|
||||||
|
Runnable r = () -> {
|
||||||
|
var result = new Bundle();
|
||||||
|
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE
|
||||||
|
final int API_NOT_AVAILABLE = -1;
|
||||||
|
result.putInt("error", API_NOT_AVAILABLE);
|
||||||
|
try {
|
||||||
|
callback.onRequestExpressIntegrityTokenResult(result);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e("IExpressIntegrityService.Stub", "", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Binder createTokenRequestStub() {
|
||||||
|
return new TokenRequestStub();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package app.grapheneos.gmscompat.lib.util;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class ServiceConnectionWrapper implements ServiceConnection {
|
||||||
|
private final ServiceConnection base;
|
||||||
|
private final UnaryOperator<IBinder> binderOverride;
|
||||||
|
|
||||||
|
public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator<IBinder> binderOverride) {
|
||||||
|
this.base = base;
|
||||||
|
this.binderOverride = binderOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
IBinder override = binderOverride.apply(service);
|
||||||
|
if (override != null) {
|
||||||
|
service = override;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.onServiceConnected(name, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
base.onServiceDisconnected(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindingDied(ComponentName name) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
base.onBindingDied(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNullBinding(ComponentName name) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
base.onNullBinding(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.extension.playintegrity;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils;
|
||||||
|
|
||||||
|
public class DisablePlayIntegrityPatch {
|
||||||
|
public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) {
|
||||||
|
ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn);
|
||||||
|
if (override != null) {
|
||||||
|
conn = override;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.bindService(service, conn, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.android.internal.os;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
public class FakeBackgroundHandler {
|
||||||
|
|
||||||
|
public static Handler getHandler() {
|
||||||
|
return new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import android.widget.Toolbar;
|
|||||||
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;
|
||||||
|
|
||||||
@@ -46,15 +47,7 @@ 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(
|
||||||
"Theme.ReVanced.YouTubeMusic.Settings", "style"));
|
ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings"));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the resource ID for the YouTube Music settings layout.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected int getContentViewResourceId() {
|
|
||||||
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE;
|
|||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.EnumSetting;
|
import app.revanced.extension.shared.settings.EnumSetting;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends YouTubeAndMusicSettings {
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
||||||
|
|||||||
15
extensions/nothingx/build.gradle.kts
Normal file
15
extensions/nothingx/build.gradle.kts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:nothingx:stub"))
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/nothingx/src/main/AndroidManifest.xml
Normal file
1
extensions/nothingx/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,590 @@
|
|||||||
|
package app.revanced.extension.nothingx.patches;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patches to expose the K1 token for Nothing X app to enable pairing with GadgetBridge.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ShowK1TokensPatch {
|
||||||
|
|
||||||
|
private static final String TAG = "ReVanced";
|
||||||
|
private static final String PACKAGE_NAME = "com.nothing.smartcenter";
|
||||||
|
private static final String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
|
||||||
|
private static final String PREFS_NAME = "revanced_nothingx_prefs";
|
||||||
|
private static final String KEY_DONT_SHOW_DIALOG = "dont_show_k1_dialog";
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
private static final int COLOR_BG = 0xFF1E1E1E;
|
||||||
|
private static final int COLOR_CARD = 0xFF2D2D2D;
|
||||||
|
private static final int COLOR_TEXT_PRIMARY = 0xFFFFFFFF;
|
||||||
|
private static final int COLOR_TEXT_SECONDARY = 0xFFB0B0B0;
|
||||||
|
private static final int COLOR_ACCENT = 0xFFFF9500;
|
||||||
|
private static final int COLOR_TOKEN_BG = 0xFF3A3A3A;
|
||||||
|
private static final int COLOR_BUTTON_POSITIVE = 0xFFFF9500;
|
||||||
|
private static final int COLOR_BUTTON_NEGATIVE = 0xFFFF6B6B;
|
||||||
|
|
||||||
|
// Match standalone K1: k1:, K1:, k1>, etc.
|
||||||
|
private static final Pattern K1_STANDALONE_PATTERN = Pattern.compile("(?i)(?:k1\\s*[:>]\\s*)([0-9a-f]{32})");
|
||||||
|
// Match combined r3+k1: format (64 chars = r3(32) + k1(32))
|
||||||
|
private static final Pattern K1_COMBINED_PATTERN = Pattern.compile("(?i)r3\\+k1\\s*:\\s*([0-9a-f]{64})");
|
||||||
|
|
||||||
|
private static volatile boolean k1Logged = false;
|
||||||
|
private static volatile boolean lifecycleCallbacksRegistered = false;
|
||||||
|
private static Context appContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get K1 tokens from database and log files.
|
||||||
|
* Call this after the app initializes.
|
||||||
|
*
|
||||||
|
* @param context Application context
|
||||||
|
*/
|
||||||
|
public static void showK1Tokens(Context context) {
|
||||||
|
if (k1Logged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
appContext = context.getApplicationContext();
|
||||||
|
|
||||||
|
Set<String> allTokens = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
// First try to get from database.
|
||||||
|
String dbToken = getK1TokensFromDatabase();
|
||||||
|
if (dbToken != null) {
|
||||||
|
allTokens.add(dbToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then get from log files.
|
||||||
|
Set<String> logTokens = getK1TokensFromLogFiles();
|
||||||
|
allTokens.addAll(logTokens);
|
||||||
|
|
||||||
|
if (allTokens.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log all found tokens.
|
||||||
|
int index = 1;
|
||||||
|
for (String token : allTokens) {
|
||||||
|
Log.i(TAG, "#" + index++ + ": " + token.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register lifecycle callbacks to show dialog when an Activity is ready.
|
||||||
|
registerLifecycleCallbacks(allTokens);
|
||||||
|
|
||||||
|
k1Logged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register ActivityLifecycleCallbacks to show dialog when first Activity resumes.
|
||||||
|
*
|
||||||
|
* @param tokens Set of K1 tokens to display
|
||||||
|
*/
|
||||||
|
private static void registerLifecycleCallbacks(Set<String> tokens) {
|
||||||
|
if (lifecycleCallbacksRegistered || !(appContext instanceof Application)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application application = (Application) appContext;
|
||||||
|
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStarted(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResumed(Activity activity) {
|
||||||
|
// Check if user chose not to show dialog.
|
||||||
|
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
if (prefs.getBoolean(KEY_DONT_SHOW_DIALOG, false)) {
|
||||||
|
application.unregisterActivityLifecycleCallbacks(this);
|
||||||
|
lifecycleCallbacksRegistered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show dialog on first Activity resume.
|
||||||
|
if (tokens != null && !tokens.isEmpty()) {
|
||||||
|
activity.runOnUiThread(() -> showK1TokensDialog(activity, tokens));
|
||||||
|
// Unregister after showing
|
||||||
|
application.unregisterActivityLifecycleCallbacks(this);
|
||||||
|
lifecycleCallbacksRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityPaused(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityStopped(Activity activity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityDestroyed(Activity activity) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lifecycleCallbacksRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show dialog with K1 tokens.
|
||||||
|
*
|
||||||
|
* @param activity Activity context
|
||||||
|
* @param tokens Set of K1 tokens
|
||||||
|
*/
|
||||||
|
private static void showK1TokensDialog(Activity activity, Set<String> tokens) {
|
||||||
|
try {
|
||||||
|
// Create main container.
|
||||||
|
LinearLayout mainLayout = new LinearLayout(activity);
|
||||||
|
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
mainLayout.setBackgroundColor(COLOR_BG);
|
||||||
|
mainLayout.setPadding(dpToPx(activity, 24), dpToPx(activity, 16),
|
||||||
|
dpToPx(activity, 24), dpToPx(activity, 16));
|
||||||
|
|
||||||
|
// Title.
|
||||||
|
TextView titleView = new TextView(activity);
|
||||||
|
titleView.setText("K1 Token(s) Found");
|
||||||
|
titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||||
|
titleView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
|
titleView.setTextColor(COLOR_TEXT_PRIMARY);
|
||||||
|
titleView.setGravity(Gravity.CENTER);
|
||||||
|
mainLayout.addView(titleView, new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
|
||||||
|
// Subtitle.
|
||||||
|
TextView subtitleView = new TextView(activity);
|
||||||
|
subtitleView.setText(tokens.size() == 1 ? "1 token found • Tap to copy" : tokens.size() + " tokens found • Tap to copy");
|
||||||
|
subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||||
|
subtitleView.setTextColor(COLOR_TEXT_SECONDARY);
|
||||||
|
subtitleView.setGravity(Gravity.CENTER);
|
||||||
|
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
subtitleParams.topMargin = dpToPx(activity, 4);
|
||||||
|
subtitleParams.bottomMargin = dpToPx(activity, 16);
|
||||||
|
mainLayout.addView(subtitleView, subtitleParams);
|
||||||
|
|
||||||
|
// Scrollable content.
|
||||||
|
ScrollView scrollView = new ScrollView(activity);
|
||||||
|
scrollView.setVerticalScrollBarEnabled(false);
|
||||||
|
LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
scrollParams.topMargin = dpToPx(activity, 8);
|
||||||
|
scrollParams.bottomMargin = dpToPx(activity, 16);
|
||||||
|
mainLayout.addView(scrollView, scrollParams);
|
||||||
|
|
||||||
|
LinearLayout tokensContainer = new LinearLayout(activity);
|
||||||
|
tokensContainer.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
scrollView.addView(tokensContainer);
|
||||||
|
|
||||||
|
// Add each token as a card.
|
||||||
|
boolean singleToken = tokens.size() == 1;
|
||||||
|
int index = 1;
|
||||||
|
for (String token : tokens) {
|
||||||
|
LinearLayout tokenCard = createTokenCard(activity, token, index++, singleToken);
|
||||||
|
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
cardParams.bottomMargin = dpToPx(activity, 12);
|
||||||
|
tokensContainer.addView(tokenCard, cardParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info text.
|
||||||
|
TextView infoView = new TextView(activity);
|
||||||
|
infoView.setText(tokens.size() == 1 ? "Tap the token to copy it" : "Tap any token to copy it");
|
||||||
|
infoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
|
||||||
|
infoView.setTextColor(COLOR_TEXT_SECONDARY);
|
||||||
|
infoView.setGravity(Gravity.CENTER);
|
||||||
|
infoView.setAlpha(0.7f);
|
||||||
|
LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
infoParams.topMargin = dpToPx(activity, 8);
|
||||||
|
mainLayout.addView(infoView, infoParams);
|
||||||
|
|
||||||
|
// Button row.
|
||||||
|
LinearLayout buttonRow = new LinearLayout(activity);
|
||||||
|
buttonRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
buttonRow.setGravity(Gravity.END);
|
||||||
|
LinearLayout.LayoutParams buttonRowParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
buttonRowParams.topMargin = dpToPx(activity, 16);
|
||||||
|
mainLayout.addView(buttonRow, buttonRowParams);
|
||||||
|
|
||||||
|
// "Don't show again" button.
|
||||||
|
Button dontShowButton = new Button(activity);
|
||||||
|
dontShowButton.setText("Don't show again");
|
||||||
|
dontShowButton.setTextColor(Color.WHITE);
|
||||||
|
dontShowButton.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
dontShowButton.setAllCaps(false);
|
||||||
|
dontShowButton.setTypeface(Typeface.DEFAULT);
|
||||||
|
dontShowButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||||
|
dontShowButton.setPadding(dpToPx(activity, 16), dpToPx(activity, 8),
|
||||||
|
dpToPx(activity, 16), dpToPx(activity, 8));
|
||||||
|
LinearLayout.LayoutParams dontShowParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
dontShowParams.rightMargin = dpToPx(activity, 8);
|
||||||
|
buttonRow.addView(dontShowButton, dontShowParams);
|
||||||
|
|
||||||
|
// "OK" button.
|
||||||
|
Button okButton = new Button(activity);
|
||||||
|
okButton.setText("OK");
|
||||||
|
okButton.setTextColor(Color.BLACK);
|
||||||
|
okButton.setBackgroundColor(COLOR_BUTTON_POSITIVE);
|
||||||
|
okButton.setAllCaps(false);
|
||||||
|
okButton.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
|
okButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||||
|
okButton.setPadding(dpToPx(activity, 24), dpToPx(activity, 12),
|
||||||
|
dpToPx(activity, 24), dpToPx(activity, 12));
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
okButton.setElevation(dpToPx(activity, 4));
|
||||||
|
}
|
||||||
|
buttonRow.addView(okButton, new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
|
||||||
|
// Build dialog.
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
builder.setView(mainLayout);
|
||||||
|
|
||||||
|
final AlertDialog dialog = builder.create();
|
||||||
|
|
||||||
|
// Style the dialog with dark background.
|
||||||
|
if (dialog.getWindow() != null) {
|
||||||
|
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
// Set button click listeners after dialog is created.
|
||||||
|
dontShowButton.setOnClickListener(v -> {
|
||||||
|
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, true).apply();
|
||||||
|
Toast.makeText(activity, "Dialog disabled. Clear app data to re-enable.",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
okButton.setOnClickListener(v -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Failed to show K1 dialog", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a card view for a single token.
|
||||||
|
*/
|
||||||
|
private static LinearLayout createTokenCard(Activity activity, String token, int index, boolean singleToken) {
|
||||||
|
LinearLayout card = new LinearLayout(activity);
|
||||||
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
card.setBackgroundColor(COLOR_TOKEN_BG);
|
||||||
|
card.setPadding(dpToPx(activity, 16), dpToPx(activity, 12),
|
||||||
|
dpToPx(activity, 16), dpToPx(activity, 12));
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
card.setElevation(dpToPx(activity, 2));
|
||||||
|
}
|
||||||
|
card.setClickable(true);
|
||||||
|
card.setFocusable(true);
|
||||||
|
|
||||||
|
// Token label (only show if multiple tokens).
|
||||||
|
if (!singleToken) {
|
||||||
|
TextView labelView = new TextView(activity);
|
||||||
|
labelView.setText("Token #" + index);
|
||||||
|
labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
|
||||||
|
labelView.setTextColor(COLOR_ACCENT);
|
||||||
|
labelView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
|
card.addView(labelView);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token value.
|
||||||
|
TextView tokenView = new TextView(activity);
|
||||||
|
tokenView.setText(token.toUpperCase());
|
||||||
|
tokenView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||||
|
tokenView.setTextColor(COLOR_TEXT_PRIMARY);
|
||||||
|
tokenView.setTypeface(Typeface.MONOSPACE);
|
||||||
|
tokenView.setLetterSpacing(0.05f);
|
||||||
|
LinearLayout.LayoutParams tokenParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
if (!singleToken) {
|
||||||
|
tokenParams.topMargin = dpToPx(activity, 8);
|
||||||
|
}
|
||||||
|
card.addView(tokenView, tokenParams);
|
||||||
|
|
||||||
|
// Click to copy.
|
||||||
|
card.setOnClickListener(v -> {
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
if (clipboard != null) {
|
||||||
|
clipboard.setText(token.toUpperCase());
|
||||||
|
Toast.makeText(activity, "Token copied!", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert dp to pixels.
|
||||||
|
*/
|
||||||
|
private static int dpToPx(Context context, float dp) {
|
||||||
|
return (int) TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
dp,
|
||||||
|
context.getResources().getDisplayMetrics()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get K1 tokens from log files.
|
||||||
|
* Prioritizes pairing K1 tokens over reconnect tokens.
|
||||||
|
*/
|
||||||
|
private static Set<String> getK1TokensFromLogFiles() {
|
||||||
|
Set<String> pairingTokens = new LinkedHashSet<>();
|
||||||
|
Set<String> reconnectTokens = new LinkedHashSet<>();
|
||||||
|
try {
|
||||||
|
File logDir = new File("/data/data/" + PACKAGE_NAME + "/files/log");
|
||||||
|
if (!logDir.exists() || !logDir.isDirectory()) {
|
||||||
|
return pairingTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] logFiles = logDir.listFiles((dir, name) ->
|
||||||
|
name.endsWith(".log") || name.endsWith(".log.") || name.matches(".*\\.log\\.\\d+"));
|
||||||
|
|
||||||
|
if (logFiles == null || logFiles.length == 0) {
|
||||||
|
return pairingTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File logFile : logFiles) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
// Determine if this is a pairing or reconnect context.
|
||||||
|
boolean isPairingContext = line.toLowerCase().contains("watchbind");
|
||||||
|
boolean isReconnectContext = line.toLowerCase().contains("watchreconnect");
|
||||||
|
|
||||||
|
String k1Token = null;
|
||||||
|
|
||||||
|
// First check for combined r3+k1 format (priority).
|
||||||
|
Matcher combinedMatcher = K1_COMBINED_PATTERN.matcher(line);
|
||||||
|
if (combinedMatcher.find()) {
|
||||||
|
String combined = combinedMatcher.group(1);
|
||||||
|
if (combined.length() == 64) {
|
||||||
|
// Second half is the actual K1
|
||||||
|
k1Token = combined.substring(32).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check for standalone K1 format (only if not found in combined).
|
||||||
|
if (k1Token == null) {
|
||||||
|
Matcher standaloneMatcher = K1_STANDALONE_PATTERN.matcher(line);
|
||||||
|
if (standaloneMatcher.find()) {
|
||||||
|
String token = standaloneMatcher.group(1);
|
||||||
|
if (token != null && token.length() == 32) {
|
||||||
|
k1Token = token.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to appropriate set.
|
||||||
|
if (k1Token != null) {
|
||||||
|
if (isPairingContext && !isReconnectContext) {
|
||||||
|
pairingTokens.add(k1Token);
|
||||||
|
} else {
|
||||||
|
reconnectTokens.add(k1Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Skip unreadable files.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Fail silently.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return pairing tokens first, add reconnect tokens if no pairing tokens found.
|
||||||
|
if (!pairingTokens.isEmpty()) {
|
||||||
|
Log.i(TAG, "Found " + pairingTokens.size() + " pairing K1 token(s)");
|
||||||
|
return pairingTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reconnectTokens.isEmpty()) {
|
||||||
|
Log.i(TAG, "Found " + reconnectTokens.size() + " reconnect K1 token(s) (may not work for initial pairing)");
|
||||||
|
}
|
||||||
|
return reconnectTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get K1 tokens from the database.
|
||||||
|
*/
|
||||||
|
private static String getK1TokensFromDatabase() {
|
||||||
|
try {
|
||||||
|
File dbDir = new File("/data/data/" + PACKAGE_NAME + "/databases");
|
||||||
|
if (!dbDir.exists() || !dbDir.isDirectory()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] dbFiles = dbDir.listFiles((dir, name) ->
|
||||||
|
name.endsWith(".db") && !name.startsWith("google_app_measurement") && !name.contains("firebase"));
|
||||||
|
|
||||||
|
if (dbFiles == null || dbFiles.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File dbFile : dbFiles) {
|
||||||
|
String token = getK1TokensFromDatabase(dbFile);
|
||||||
|
if (token != null) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract K1 tokens from a database file.
|
||||||
|
*/
|
||||||
|
private static String getK1TokensFromDatabase(File dbFile) {
|
||||||
|
SQLiteDatabase db = null;
|
||||||
|
try {
|
||||||
|
db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
// Get all tables.
|
||||||
|
Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> tables = new ArrayList<>();
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
tables.add(cursor.getString(0));
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
// Scan all columns for 32-char hex strings.
|
||||||
|
for (String table : tables) {
|
||||||
|
Cursor schemaCursor = null;
|
||||||
|
try {
|
||||||
|
schemaCursor = db.rawQuery("PRAGMA table_info(" + table + ")", null);
|
||||||
|
List<String> columns = new ArrayList<>();
|
||||||
|
while (schemaCursor.moveToNext()) {
|
||||||
|
columns.add(schemaCursor.getString(1));
|
||||||
|
}
|
||||||
|
schemaCursor.close();
|
||||||
|
|
||||||
|
for (String column : columns) {
|
||||||
|
Cursor dataCursor = null;
|
||||||
|
try {
|
||||||
|
dataCursor = db.query(table, new String[]{column}, null, null, null, null, null);
|
||||||
|
while (dataCursor.moveToNext()) {
|
||||||
|
String value = dataCursor.getString(0);
|
||||||
|
if (value != null && value.length() == 32 && value.matches("[0-9a-fA-F]{32}")) {
|
||||||
|
// Skip obviously fake tokens (MD5 of empty string).
|
||||||
|
if (!value.equalsIgnoreCase(EMPTY_MD5)) {
|
||||||
|
dataCursor.close();
|
||||||
|
db.close();
|
||||||
|
return value.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Skip non-string columns.
|
||||||
|
} finally {
|
||||||
|
if (dataCursor != null) {
|
||||||
|
dataCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Continue to next table.
|
||||||
|
} finally {
|
||||||
|
if (schemaCursor != null && !schemaCursor.isClosed()) {
|
||||||
|
schemaCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (db != null && db.isOpen()) {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the logged flag (useful for testing or re-pairing).
|
||||||
|
*/
|
||||||
|
public static void resetK1Logged() {
|
||||||
|
k1Logged = false;
|
||||||
|
lifecycleCallbacksRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the "don't show again" preference.
|
||||||
|
*/
|
||||||
|
public static void resetDontShowPreference() {
|
||||||
|
if (appContext != null) {
|
||||||
|
SharedPreferences prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, false).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
extensions/nothingx/stub/build.gradle.kts
Normal file
17
extensions/nothingx/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/nothingx/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/nothingx/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,11 @@ import android.view.Window;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -43,8 +47,10 @@ 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;
|
||||||
@@ -76,6 +82,8 @@ 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;
|
||||||
@@ -148,12 +156,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 condition The setting to check for hiding the view.
|
* @param setting 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 condition, View view) {
|
public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) {
|
||||||
if (hideViewBy0dpUnderCondition(condition.get(), view)) {
|
if (hideViewBy0dpUnderCondition(setting.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,22 +173,47 @@ public class Utils {
|
|||||||
*/
|
*/
|
||||||
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
|
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
hideViewByLayoutParams(view);
|
hideViewBy0dp(view);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a view by setting its layout params to 0x0
|
||||||
|
* @param view The view to hide.
|
||||||
|
*/
|
||||||
|
public static void hideViewBy0dp(View view) {
|
||||||
|
if (view instanceof LinearLayout) {
|
||||||
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
|
||||||
|
view.setLayoutParams(layoutParams);
|
||||||
|
} else if (view instanceof FrameLayout) {
|
||||||
|
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
|
||||||
|
view.setLayoutParams(layoutParams2);
|
||||||
|
} else if (view instanceof RelativeLayout) {
|
||||||
|
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
|
||||||
|
view.setLayoutParams(layoutParams3);
|
||||||
|
} else if (view instanceof Toolbar) {
|
||||||
|
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
|
||||||
|
view.setLayoutParams(layoutParams4);
|
||||||
|
} else {
|
||||||
|
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||||
|
params.width = 0;
|
||||||
|
params.height = 0;
|
||||||
|
view.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its visibility to GONE.
|
* Hide a view by setting its visibility to GONE.
|
||||||
*
|
*
|
||||||
* @param condition The setting to check for hiding the view.
|
* @param setting 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 condition, View view) {
|
public static void hideViewUnderCondition(BooleanSetting setting, View view) {
|
||||||
if (hideViewUnderCondition(condition.get(), view)) {
|
if (hideViewUnderCondition(setting.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,14 +232,14 @@ public class Utils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) {
|
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) {
|
||||||
if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) {
|
if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) {
|
||||||
Logger.printDebug(() -> "View hidden by setting: " + condition);
|
Logger.printDebug(() -> "View hidden by setting: " + setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) {
|
public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) {
|
||||||
if (setting) {
|
if (condition) {
|
||||||
ViewParent parent = view.getParent();
|
ViewParent parent = view.getParent();
|
||||||
if (parent instanceof ViewGroup parentGroup) {
|
if (parent instanceof ViewGroup parentGroup) {
|
||||||
parentGroup.removeView(view);
|
parentGroup.removeView(view);
|
||||||
@@ -278,12 +311,13 @@ 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, String resourceIdentifierName, @Nullable String type) {
|
public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
|
||||||
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName());
|
return context.getResources().getIdentifier(resourceIdentifierName,
|
||||||
|
type == null ? null : type.value, context.getPackageName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) {
|
public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
|
||||||
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type);
|
final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName);
|
||||||
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);
|
||||||
@@ -293,48 +327,48 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return zero, if the resource is not found.
|
* @return zero, if the resource is not found.
|
||||||
* @see #getResourceIdentifierOrThrow(String, String)
|
* @see #getResourceIdentifierOrThrow(ResourceType, String)
|
||||||
*/
|
*/
|
||||||
public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) {
|
public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) {
|
||||||
return getResourceIdentifier(getContext(), resourceIdentifierName, type);
|
return getResourceIdentifier(getContext(), type, resourceIdentifierName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The resource identifier, or throws an exception if not found.
|
* @return zero, if the resource is not found.
|
||||||
|
* @see #getResourceIdentifier(ResourceType, String)
|
||||||
*/
|
*/
|
||||||
public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) {
|
public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) {
|
||||||
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type);
|
return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName);
|
||||||
if (resourceId == 0) {
|
}
|
||||||
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
|
|
||||||
+ " type: " + type);
|
public static String getResourceString(int id) throws Resources.NotFoundException {
|
||||||
}
|
return getContext().getResources().getString(id);
|
||||||
return resourceId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer"));
|
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(ResourceType.INTEGER, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim"));
|
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@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(resourceIdentifierName, "color"));
|
return getContext().getResources().getColor(getResourceIdentifierOrThrow(ResourceType.COLOR, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
|
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
|
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
|
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||||
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array"));
|
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MatchFilter<T> {
|
public interface MatchFilter<T> {
|
||||||
@@ -345,7 +379,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(str, "id"));
|
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(ResourceType.ID, str));
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (R) child;
|
return (R) child;
|
||||||
}
|
}
|
||||||
@@ -802,6 +836,21 @@ 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.
|
||||||
*/
|
*/
|
||||||
@@ -1163,4 +1212,18 @@ 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
|
|||||||
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;
|
||||||
@@ -128,7 +129,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(
|
||||||
"revanced_ic_dialog_alert", "drawable"));
|
ResourceType.DRAWABLE, "revanced_ic_dialog_alert"));
|
||||||
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(
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ 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();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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;
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ public class CustomBrandingPatch {
|
|||||||
iconName += "_custom";
|
iconName += "_custom";
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable");
|
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
|
||||||
if (notificationSmallIcon == 0) {
|
if (notificationSmallIcon == 0) {
|
||||||
Logger.printException(() -> "Could not load notification small icon");
|
Logger.printException(() -> "Could not load notification small icon");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.shared.patches.components;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
@@ -15,13 +15,15 @@ import java.util.regex.Pattern;
|
|||||||
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.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class CustomFilter extends Filter {
|
public final class CustomFilter extends Filter {
|
||||||
|
|
||||||
private static void showInvalidSyntaxToast(@NonNull 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));
|
||||||
@@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
||||||
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
|
String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
|
||||||
if (rawCustomFilterText.isBlank()) {
|
if (rawCustomFilterText.isBlank()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
|
|||||||
ByteTrieSearch bufferSearch;
|
ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
||||||
super(Settings.CUSTOM_FILTER, path);
|
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||||
this.startsWith = startsWith;
|
this.startsWith = startsWith;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, 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;
|
||||||
@@ -159,4 +161,4 @@ final class CustomFilter extends Filter {
|
|||||||
|
|
||||||
return custom.bufferSearch.matches(buffer);
|
return custom.bufferSearch.matches(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
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.
|
||||||
*
|
*
|
||||||
@@ -14,11 +17,11 @@ import java.util.List;
|
|||||||
* either an identifier or a path.
|
* either an identifier or a path.
|
||||||
* Then inside {@link #isFiltered(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 ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||||
*
|
*
|
||||||
* All callbacks must be registered before the constructor completes.
|
* All callbacks must be registered before the constructor completes.
|
||||||
*/
|
*/
|
||||||
abstract class Filter {
|
public abstract class Filter {
|
||||||
|
|
||||||
public enum FilterContentType {
|
public enum FilterContentType {
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
@@ -30,12 +33,12 @@ 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...)}.
|
||||||
*/
|
*/
|
||||||
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
|
public 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...)}.
|
||||||
*/
|
*/
|
||||||
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||||
@@ -65,7 +68,7 @@ abstract class Filter {
|
|||||||
* @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.
|
||||||
*/
|
*/
|
||||||
boolean isFiltered(String identifier, 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;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
|
|
||||||
|
public abstract class FilterGroup<T> {
|
||||||
|
public final static class FilterGroupResult {
|
||||||
|
private BooleanSetting setting;
|
||||||
|
private int matchedIndex;
|
||||||
|
private int matchedLength;
|
||||||
|
// In the future it might be useful to include which pattern matched,
|
||||||
|
// but for now that is not needed.
|
||||||
|
|
||||||
|
FilterGroupResult() {
|
||||||
|
this(null, -1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||||
|
setValues(setting, matchedIndex, matchedLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||||
|
this.setting = setting;
|
||||||
|
this.matchedIndex = matchedIndex;
|
||||||
|
this.matchedLength = matchedLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A null value if the group has no setting,
|
||||||
|
* or if no match is returned from {@link FilterGroupList#check(Object)}.
|
||||||
|
*/
|
||||||
|
public BooleanSetting getSetting() {
|
||||||
|
return setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return matchedIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matched index of first pattern that matched, or -1 if nothing matched.
|
||||||
|
*/
|
||||||
|
public int getMatchedIndex() {
|
||||||
|
return matchedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the matched filter pattern.
|
||||||
|
*/
|
||||||
|
public int getMatchedLength() {
|
||||||
|
return matchedLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final BooleanSetting setting;
|
||||||
|
public final T[] filters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new filter group.
|
||||||
|
*
|
||||||
|
* @param setting The associated setting.
|
||||||
|
* @param filters The filters.
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public FilterGroup(final BooleanSetting setting, final T... filters) {
|
||||||
|
this.setting = setting;
|
||||||
|
this.filters = filters;
|
||||||
|
if (filters.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return setting == null || setting.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If {@link FilterGroupList} should include this group when searching.
|
||||||
|
* By default, all filters are included except non enabled settings that require reboot.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
public boolean includeInSearch() {
|
||||||
|
return isEnabled() || !setting.rebootApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract FilterGroupResult check(final T stack);
|
||||||
|
|
||||||
|
|
||||||
|
public static class StringFilterGroup extends FilterGroup<String> {
|
||||||
|
|
||||||
|
public StringFilterGroup(final BooleanSetting setting, final String... filters) {
|
||||||
|
super(setting, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilterGroupResult check(final String string) {
|
||||||
|
int matchedIndex = -1;
|
||||||
|
int matchedLength = 0;
|
||||||
|
if (isEnabled()) {
|
||||||
|
for (String pattern : filters) {
|
||||||
|
if (!string.isEmpty()) {
|
||||||
|
final int indexOf = string.indexOf(pattern);
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
matchedIndex = indexOf;
|
||||||
|
matchedLength = pattern.length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you have more than 1 filter patterns, then all instances of
|
||||||
|
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||||
|
* which uses a prefix tree to give better performance.
|
||||||
|
*/
|
||||||
|
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||||
|
|
||||||
|
private volatile int[][] failurePatterns;
|
||||||
|
|
||||||
|
// Modified implementation from https://stackoverflow.com/a/1507813
|
||||||
|
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
|
||||||
|
// Finds the first occurrence of the pattern in the byte array using
|
||||||
|
// KMP matching algorithm.
|
||||||
|
int patternLength = pattern.length;
|
||||||
|
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
|
||||||
|
while (j > 0 && pattern[j] != data[i]) {
|
||||||
|
j = failure[j - 1];
|
||||||
|
}
|
||||||
|
if (pattern[j] == data[i]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == patternLength) {
|
||||||
|
return i - patternLength + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] createFailurePattern(byte[] pattern) {
|
||||||
|
// Computes the failure function using a boot-strapping process,
|
||||||
|
// where the pattern is matched against itself.
|
||||||
|
final int patternLength = pattern.length;
|
||||||
|
final int[] failure = new int[patternLength];
|
||||||
|
|
||||||
|
for (int i = 1, j = 0; i < patternLength; i++) {
|
||||||
|
while (j > 0 && pattern[j] != pattern[i]) {
|
||||||
|
j = failure[j - 1];
|
||||||
|
}
|
||||||
|
if (pattern[j] == pattern[i]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
failure[i] = j;
|
||||||
|
}
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) {
|
||||||
|
super(setting, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||||
|
*/
|
||||||
|
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
||||||
|
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildFailurePatterns() {
|
||||||
|
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
||||||
|
Logger.printDebug(() -> "Building failure array for: " + this);
|
||||||
|
int[][] failurePatterns = new int[filters.length][];
|
||||||
|
int i = 0;
|
||||||
|
for (byte[] pattern : filters) {
|
||||||
|
failurePatterns[i++] = createFailurePattern(pattern);
|
||||||
|
}
|
||||||
|
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilterGroupResult check(final byte[] bytes) {
|
||||||
|
int matchedLength = 0;
|
||||||
|
int matchedIndex = -1;
|
||||||
|
if (isEnabled()) {
|
||||||
|
int[][] failures = failurePatterns;
|
||||||
|
if (failures == null) {
|
||||||
|
buildFailurePatterns(); // Lazy load.
|
||||||
|
failures = failurePatterns;
|
||||||
|
}
|
||||||
|
for (int i = 0, length = filters.length; i < length; i++) {
|
||||||
|
byte[] filter = filters[i];
|
||||||
|
matchedIndex = indexOf(bytes, filter, failures[i]);
|
||||||
|
if (matchedIndex >= 0) {
|
||||||
|
matchedLength = filter.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
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.StringFilterGroup;
|
||||||
|
|
||||||
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<>();
|
||||||
private final TrieSearch<V> search = createSearchGraph();
|
private final TrieSearch<V> search = createSearchGraph();
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
protected final void addAll(final T... groups) {
|
public final void addAll(final T... groups) {
|
||||||
filterGroups.addAll(Arrays.asList(groups));
|
filterGroups.addAll(Arrays.asList(groups));
|
||||||
|
|
||||||
for (T group : groups) {
|
for (T group : groups) {
|
||||||
@@ -41,18 +42,7 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
return filterGroups.iterator();
|
return filterGroups.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public FilterGroup.FilterGroupResult check(V stack) {
|
||||||
public void forEach(@NonNull Consumer<? super T> action) {
|
|
||||||
filterGroups.forEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Spliterator<T> spliterator() {
|
|
||||||
return filterGroups.spliterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected FilterGroup.FilterGroupResult check(V stack) {
|
|
||||||
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
||||||
search.matches(stack, result);
|
search.matches(stack, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -60,21 +50,21 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract TrieSearch<V> createSearchGraph();
|
protected abstract TrieSearch<V> createSearchGraph();
|
||||||
}
|
|
||||||
|
|
||||||
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||||
protected StringTrieSearch createSearchGraph() {
|
protected StringTrieSearch createSearchGraph() {
|
||||||
return new StringTrieSearch();
|
return new StringTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If searching for a single byte pattern, then it is slightly better to use
|
* If searching for a single byte pattern, then it is slightly better to use
|
||||||
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
||||||
* than a prefix tree to search for only 1 pattern.
|
* than a prefix tree to search for only 1 pattern.
|
||||||
*/
|
*/
|
||||||
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||||
protected ByteTrieSearch createSearchGraph() {
|
protected ByteTrieSearch createSearchGraph() {
|
||||||
return new ByteTrieSearch();
|
return new ByteTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
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.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class LithoFilterPatch {
|
||||||
|
/**
|
||||||
|
* Simple wrapper to pass the litho parameters through the prefix search.
|
||||||
|
*/
|
||||||
|
private static final class LithoFilterParameters {
|
||||||
|
final String identifier;
|
||||||
|
final String path;
|
||||||
|
final byte[] buffer;
|
||||||
|
|
||||||
|
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
|
||||||
|
this.identifier = lithoIdentifier;
|
||||||
|
this.path = lithoPath;
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// Estimate the percentage of the buffer that are Strings.
|
||||||
|
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
|
||||||
|
builder.append( "ID: ");
|
||||||
|
builder.append(identifier);
|
||||||
|
builder.append(" Path: ");
|
||||||
|
builder.append(path);
|
||||||
|
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||||
|
builder.append(" BufferStrings: ");
|
||||||
|
findAsciiStrings(builder, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search through a byte array for all ASCII strings.
|
||||||
|
*/
|
||||||
|
static void findAsciiStrings(StringBuilder builder, byte[] buffer) {
|
||||||
|
// Valid ASCII values (ignore control characters).
|
||||||
|
final int minimumAscii = 32; // 32 = space character
|
||||||
|
final int maximumAscii = 126; // 127 = delete character
|
||||||
|
final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include.
|
||||||
|
String delimitingCharacter = "❙"; // Non ascii character, to allow easier log filtering.
|
||||||
|
|
||||||
|
final int length = buffer.length;
|
||||||
|
int start = 0;
|
||||||
|
int end = 0;
|
||||||
|
while (end < length) {
|
||||||
|
int value = buffer[end];
|
||||||
|
if (value < minimumAscii || value > maximumAscii || end == length - 1) {
|
||||||
|
if (end - start >= minimumAsciiStringLength) {
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
builder.append((char) buffer[i]);
|
||||||
|
}
|
||||||
|
builder.append(delimitingCharacter);
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Litho layout fixed thread pool size override.
|
||||||
|
* <p>
|
||||||
|
* Unpatched YouTube uses a layout fixed thread pool between 1 and 3 threads:
|
||||||
|
* <pre>
|
||||||
|
* 1 thread - > Device has less than 6 cores
|
||||||
|
* 2 threads -> Device has over 6 cores and less than 6GB of memory
|
||||||
|
* 3 threads -> Device has over 6 cores and more than 6GB of memory
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Using more than 1 thread causes layout issues such as the You tab watch/playlist shelf
|
||||||
|
* that is sometimes incorrectly hidden (ReVanced is not hiding it), and seems to
|
||||||
|
* fix a race issue if using the active navigation tab status with litho filtering.
|
||||||
|
*/
|
||||||
|
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for actual filters.
|
||||||
|
*/
|
||||||
|
private static final class DummyFilter extends Filter { }
|
||||||
|
|
||||||
|
private static final Filter[] filters = new Filter[] {
|
||||||
|
new DummyFilter() // Replaced patching, do not touch.
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
||||||
|
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
filterUsingCallbacks(identifierSearchTree, filter,
|
||||||
|
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
|
||||||
|
filterUsingCallbacks(pathSearchTree, filter,
|
||||||
|
filter.pathCallbacks, Filter.FilterContentType.PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Using: "
|
||||||
|
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
|
||||||
|
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), "
|
||||||
|
+ pathSearchTree.numberOfPatterns() + " path filters"
|
||||||
|
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
|
||||||
|
Filter filter, List<StringFilterGroup> groups,
|
||||||
|
Filter.FilterContentType type) {
|
||||||
|
String filterSimpleName = filter.getClass().getSimpleName();
|
||||||
|
|
||||||
|
for (StringFilterGroup group : groups) {
|
||||||
|
if (!group.includeInSearch()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String pattern : group.filters) {
|
||||||
|
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
|
||||||
|
matchedLength, callbackParameter) -> {
|
||||||
|
if (!group.isEnabled()) return false;
|
||||||
|
|
||||||
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
|
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||||
|
parameters.path, parameters.buffer, group, type, matchedStartIndex);
|
||||||
|
|
||||||
|
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||||
|
if (type == Filter.FilterContentType.IDENTIFIER) {
|
||||||
|
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||||
|
+ " identifier: " + parameters.identifier);
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||||
|
+ " path: " + parameters.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isFiltered;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Called off the main thread.
|
||||||
|
* Targets 20.22+
|
||||||
|
*/
|
||||||
|
public static void setProtoBuffer(byte[] 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.
|
||||||
|
bufferThreadLocal.set(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Called off the main thread.
|
||||||
|
* Targets 20.21 and lower.
|
||||||
|
*/
|
||||||
|
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()) {
|
||||||
|
// 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.
|
||||||
|
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
|
||||||
|
} else {
|
||||||
|
setProtoBuffer(buffer.array());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
|
||||||
|
try {
|
||||||
|
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = bufferThreadLocal.get();
|
||||||
|
// Potentially the buffer may have been null or never set up until now.
|
||||||
|
// Use an empty buffer so the litho id/path filters still work correctly.
|
||||||
|
if (buffer == null) {
|
||||||
|
buffer = EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
LithoFilterParameters parameter = new LithoFilterParameters(
|
||||||
|
lithoIdentifier, pathBuilder.toString(), buffer);
|
||||||
|
Logger.printDebug(() -> "Searching " + parameter);
|
||||||
|
|
||||||
|
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathSearchTree.matches(parameter.path, parameter)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "isFiltered failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getExecutorCorePoolSize(int originalCorePoolSize) {
|
||||||
|
if (originalCorePoolSize != LITHO_LAYOUT_THREAD_POOL_SIZE) {
|
||||||
|
Logger.printDebug(() -> "Overriding core thread pool size from: " + originalCorePoolSize
|
||||||
|
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LITHO_LAYOUT_THREAD_POOL_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getExecutorMaxThreads(int originalMaxThreads) {
|
||||||
|
if (originalMaxThreads != LITHO_LAYOUT_THREAD_POOL_SIZE) {
|
||||||
|
Logger.printDebug(() -> "Overriding max thread pool size from: " + originalMaxThreads
|
||||||
|
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LITHO_LAYOUT_THREAD_POOL_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,15 @@ public class LinkSanitizer {
|
|||||||
|
|
||||||
public Uri sanitizeUri(Uri uri) {
|
public Uri sanitizeUri(Uri uri) {
|
||||||
try {
|
try {
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
|
||||||
|
// Opening YouTube share sheet 'other' option passes the video title as a URI.
|
||||||
|
// Checking !uri.isHierarchical() works for all cases, except if the
|
||||||
|
// video title starts with / and then it's hierarchical but still an invalid URI.
|
||||||
|
Logger.printDebug(() -> "Ignoring uri: " + uri);
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
Uri.Builder builder = uri.buildUpon().clearQuery();
|
Uri.Builder builder = uri.buildUpon().clearQuery();
|
||||||
|
|
||||||
if (!parametersToRemove.isEmpty()) {
|
if (!parametersToRemove.isEmpty()) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
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,13 +26,13 @@ import app.revanced.extension.shared.ui.Dim;
|
|||||||
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("revanced_settings_fragments", "id");
|
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_settings_fragments");
|
||||||
private static final int ID_REVANCED_TOOLBAR_PARENT =
|
private static final int ID_REVANCED_TOOLBAR_PARENT =
|
||||||
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id");
|
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent");
|
||||||
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
|
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_with_toolbar", "layout");
|
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_settings_with_toolbar");
|
||||||
private static final int STRING_REVANCED_SETTINGS_TITLE =
|
private static final int STRING_REVANCED_SETTINGS_TITLE =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_title", "string");
|
getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout parameters for the toolbar, extracted from the dummy toolbar.
|
* Layout parameters for the toolbar, extracted from the dummy toolbar.
|
||||||
@@ -123,16 +124,18 @@ public abstract class BaseActivityHook extends Activity {
|
|||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customizes the activity's theme.
|
* Customizes the activity's theme.
|
||||||
*/
|
*/
|
||||||
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,6 +5,8 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings shared across multiple apps.
|
* Settings shared across multiple apps.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -24,10 +26,19 @@ 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);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Settings shared by YouTube and YouTube Music.
|
// Settings shared by YouTube and YouTube Music.
|
||||||
//
|
//
|
||||||
@@ -44,4 +55,13 @@ public class BaseSettings {
|
|||||||
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,23 @@ public abstract class Setting<T> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Availability based on a single parent setting being disabled.
|
||||||
|
*/
|
||||||
|
public static Availability parentNot(BooleanSetting parent) {
|
||||||
|
return new Availability() {
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return !parent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Setting<?>> getParentSettings() {
|
||||||
|
return Collections.singletonList(parent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Availability based on all parents being enabled.
|
* Availability based on all parents being enabled.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.extension.shared.settings;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
|
||||||
|
public class YouTubeAndMusicSettings extends BaseSettings {
|
||||||
|
// Custom filter
|
||||||
|
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||||
|
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||||
|
|
||||||
|
// Miscellaneous
|
||||||
|
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
|
||||||
|
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ 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;
|
||||||
@@ -103,10 +104,16 @@ 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 = BaseSettings.SHOW_MENU_ICONS.get()
|
String preferenceResourceName;
|
||||||
? "revanced_prefs_icons"
|
if (BaseSettings.SHOW_MENU_ICONS.get()) {
|
||||||
: "revanced_prefs";
|
preferenceResourceName = Utils.appIsUsingBoldIcons()
|
||||||
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
|
? "revanced_prefs_icons_bold"
|
||||||
|
: "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);
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ 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;
|
||||||
@@ -81,13 +82,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("revanced_color_picker_view", "id");
|
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_color_picker_view");
|
||||||
public static final int ID_PREFERENCE_COLOR_DOT =
|
public static final int ID_PREFERENCE_COLOR_DOT =
|
||||||
getResourceIdentifierOrThrow("preference_color_dot", "id");
|
getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot");
|
||||||
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
|
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
|
||||||
getResourceIdentifierOrThrow("revanced_color_dot_widget", "layout");
|
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_dot_widget");
|
||||||
public static final int LAYOUT_REVANCED_COLOR_PICKER =
|
public static final int LAYOUT_REVANCED_COLOR_PICKER =
|
||||||
getResourceIdentifierOrThrow("revanced_color_picker", "layout");
|
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes non valid hex characters, converts to all uppercase,
|
* Removes non valid hex characters, converts to all uppercase,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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;
|
||||||
|
|
||||||
@@ -30,14 +31,18 @@ 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 =
|
public static final int ID_REVANCED_CHECK_ICON = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_check_icon", "id");
|
ResourceType.ID, "revanced_check_icon");
|
||||||
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER =
|
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_check_icon_placeholder", "id");
|
ResourceType.ID, "revanced_check_icon_placeholder");
|
||||||
public static final int ID_REVANCED_ITEM_TEXT =
|
public static final int ID_REVANCED_ITEM_TEXT = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_item_text", "id");
|
ResourceType.ID, "revanced_item_text");
|
||||||
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED =
|
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_custom_list_item_checked", "layout");
|
ResourceType.LAYOUT, "revanced_custom_list_item_checked");
|
||||||
|
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;
|
||||||
@@ -125,9 +130,13 @@ 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,6 +38,7 @@ 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;
|
||||||
@@ -52,25 +53,26 @@ 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("revanced_settings_select_all", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
|
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_deselect_all", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
|
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_copy_all", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_arrow_right_one", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_arrow_right_double", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_arrow_left_one", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one");
|
||||||
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
|
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
|
||||||
getResourceIdentifierOrThrow("revanced_settings_arrow_left_double", "drawable");
|
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_double");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ import android.widget.Toolbar;
|
|||||||
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;
|
||||||
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;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
@SuppressWarnings({"deprecation", "NewApi"})
|
@SuppressWarnings({"deprecation", "NewApi"})
|
||||||
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
@@ -133,8 +135,10 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
*/
|
*/
|
||||||
@SuppressLint("UseCompatLoadingForDrawables")
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
public static Drawable getBackButtonDrawable() {
|
public static Drawable getBackButtonDrawable() {
|
||||||
final int backButtonResource = Utils.getResourceIdentifierOrThrow(
|
final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE,
|
||||||
"revanced_settings_toolbar_arrow_left", "drawable");
|
Utils.appIsUsingBoldIcons()
|
||||||
|
? "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;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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;
|
||||||
@@ -38,18 +39,18 @@ public abstract class BaseSearchResultItem {
|
|||||||
// Get the corresponding layout resource ID.
|
// Get the corresponding layout resource ID.
|
||||||
public int getLayoutResourceId() {
|
public int getLayoutResourceId() {
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
|
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
|
||||||
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
|
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
|
||||||
case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
|
case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
|
||||||
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
|
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
|
||||||
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
|
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
|
||||||
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
|
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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(name, "layout");
|
return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -33,6 +32,7 @@ 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;
|
||||||
@@ -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(
|
||||||
"preference_title", "id");
|
ResourceType.ID, "preference_title");
|
||||||
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
"preference_summary", "id");
|
ResourceType.ID, "preference_summary");
|
||||||
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
|
||||||
"preference_path", "id");
|
ResourceType.ID, "preference_path");
|
||||||
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
|
||||||
"preference_switch", "id");
|
ResourceType.ID, "preference_switch");
|
||||||
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
|
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
|
||||||
"preference_color_dot", "id");
|
ResourceType.ID, "preference_color_dot");
|
||||||
|
|
||||||
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(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
|
holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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;
|
||||||
@@ -37,6 +38,7 @@ 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;
|
||||||
@@ -70,14 +72,29 @@ 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("revanced_search_view", "id");
|
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow(
|
||||||
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow("revanced_search_view_container", "id");
|
ResourceType.ID, "revanced_search_view");
|
||||||
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow("action_search", "id");
|
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow(
|
||||||
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow("revanced_settings_fragments", "id");
|
ResourceType.ID, "revanced_search_view_container");
|
||||||
public static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON =
|
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_settings_search_icon", "drawable");
|
ResourceType.ID, "action_search");
|
||||||
protected static final int MENU_REVANCED_SEARCH_MENU =
|
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow(
|
||||||
getResourceIdentifierOrThrow("revanced_search_menu", "menu");
|
ResourceType.ID, "revanced_settings_fragments");
|
||||||
|
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.
|
||||||
@@ -112,7 +129,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(
|
||||||
"android:id/search_src_text", null));
|
null, "android:id/search_src_text"));
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -248,6 +265,10 @@ public abstract class BaseSearchViewController {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set bold icon if needed.
|
||||||
|
MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH);
|
||||||
|
search.setIcon(getSearchIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -524,7 +545,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(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
|
noResultsPreference.setIcon(getSearchIcon());
|
||||||
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
|
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ 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;
|
||||||
|
|
||||||
@@ -37,25 +39,35 @@ 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(
|
||||||
"clear_history_button", "id");
|
ResourceType.ID, "clear_history_button");
|
||||||
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
|
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
|
||||||
"history_text", "id");
|
ResourceType.ID, "history_text");
|
||||||
|
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(
|
||||||
"delete_icon", "id");
|
ResourceType.ID, "delete_icon");
|
||||||
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
|
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
|
||||||
"empty_history_title", "id");
|
ResourceType.ID, "empty_history_title");
|
||||||
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
|
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
"empty_history_summary", "id");
|
ResourceType.ID, "empty_history_summary");
|
||||||
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
|
||||||
"search_history_header", "id");
|
ResourceType.ID, "search_history_header");
|
||||||
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
|
||||||
"revanced_settings_search_tips_summary", "id");
|
ResourceType.ID, "revanced_settings_search_tips_summary");
|
||||||
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
|
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
|
||||||
"revanced_preference_search_history_screen", "layout");
|
ResourceType.LAYOUT, "revanced_preference_search_history_screen");
|
||||||
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
|
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
|
||||||
"revanced_preference_search_history_item", "layout");
|
ResourceType.LAYOUT, "revanced_preference_search_history_item");
|
||||||
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
|
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
|
||||||
"search_history_list", "id");
|
ResourceType.ID, "search_history_list");
|
||||||
|
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;
|
||||||
@@ -97,7 +109,8 @@ 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, searchHistoryContainer, false);
|
View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN,
|
||||||
|
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));
|
||||||
@@ -320,17 +333,29 @@ 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, container, false);
|
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM,
|
||||||
|
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"),
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class SpoofVideoStreamsPatch {
|
|||||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||||
return isPatchIncluded()
|
return isPatchIncluded()
|
||||||
&& SPOOF_STREAMING_DATA
|
&& SPOOF_STREAMING_DATA
|
||||||
&& preferredClient != ClientType.IPADOS;
|
&& !preferredClient.supportsMultiAudioTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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;
|
||||||
@@ -83,22 +82,15 @@ 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
* But instead use a much larger value, to handle if a video viewed a while ago
|
||||||
|
* is somehow still referenced. Each stream is a small array of Strings
|
||||||
|
* so memory usage is not a concern.
|
||||||
|
*/
|
||||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||||
new LinkedHashMap<>(100) {
|
Utils.createSizeRestrictedMap(50));
|
||||||
/**
|
|
||||||
* 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).
|
|
||||||
* But instead use a much larger value, to handle if a video viewed a while ago
|
|
||||||
* is somehow still referenced. Each stream is a small array of Strings
|
|
||||||
* so memory usage is not a concern.
|
|
||||||
*/
|
|
||||||
private static final int CACHE_LIMIT = 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.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
@@ -16,7 +17,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("navigationbar_musicappitems_create_title", "string"),
|
new ResourceIdComponentFilter(ResourceType.STRING, "navigationbar_musicappitems_create_title"),
|
||||||
// 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.
|
||||||
@@ -28,7 +29,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("bottom_navigation_bar_create_tab_title", "string");
|
new ResourceIdComponentFilter(ResourceType.STRING, "bottom_navigation_bar_create_tab_title");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 {
|
||||||
@@ -19,21 +20,26 @@ 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 String resourceType;
|
public final ResourceType 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(resourceName, resourceType);
|
resourceId = Utils.getResourceIdentifier(resourceType, resourceName);
|
||||||
}
|
}
|
||||||
return resourceId;
|
return resourceId;
|
||||||
}
|
}
|
||||||
|
|||||||
5
extensions/strava/build.gradle.kts
Normal file
5
extensions/strava/build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:strava:stub"))
|
||||||
|
compileOnly(libs.okhttp)
|
||||||
|
}
|
||||||
1
extensions/strava/src/main/AndroidManifest.xml
Normal file
1
extensions/strava/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package app.revanced.extension.strava;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.ResourceType;
|
||||||
|
import com.strava.mediamodels.data.MediaType;
|
||||||
|
import com.strava.photos.data.Media;
|
||||||
|
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public final class AddMediaDownloadPatch {
|
||||||
|
public static final int ACTION_DOWNLOAD = -1;
|
||||||
|
public static final int ACTION_OPEN_LINK = -2;
|
||||||
|
public static final int ACTION_COPY_LINK = -3;
|
||||||
|
|
||||||
|
private static final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
public static boolean handleAction(int actionId, Media media) {
|
||||||
|
String url = getUrl(media);
|
||||||
|
switch (actionId) {
|
||||||
|
case ACTION_DOWNLOAD:
|
||||||
|
String name = media.getId();
|
||||||
|
if (media.getType() == MediaType.VIDEO) {
|
||||||
|
downloadVideo(url, name);
|
||||||
|
} else {
|
||||||
|
downloadPhoto(url, name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case ACTION_OPEN_LINK:
|
||||||
|
Utils.openLink(url);
|
||||||
|
return true;
|
||||||
|
case ACTION_COPY_LINK:
|
||||||
|
copyLink(url);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyLink(CharSequence url) {
|
||||||
|
Utils.setClipboard(url);
|
||||||
|
showInfoToast("link_copied_to_clipboard", "🔗");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void downloadPhoto(String url, String name) {
|
||||||
|
showInfoToast("loading", "⏳");
|
||||||
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try (Response response = fetch(url)) {
|
||||||
|
ResponseBody body = response.body();
|
||||||
|
String mimeType = body.contentType().toString();
|
||||||
|
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
|
||||||
|
ContentResolver resolver = Utils.getContext().getContentResolver();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(MediaStore.Images.Media.DISPLAY_NAME, name + '.' + extension);
|
||||||
|
values.put(MediaStore.Images.Media.IS_PENDING, 1);
|
||||||
|
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
|
||||||
|
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Strava");
|
||||||
|
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
|
? MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
||||||
|
: MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
Uri row = resolver.insert(collection, values);
|
||||||
|
try (OutputStream outputStream = resolver.openOutputStream(row)) {
|
||||||
|
transferTo(body.byteStream(), outputStream);
|
||||||
|
} finally {
|
||||||
|
values.clear();
|
||||||
|
values.put(MediaStore.Images.Media.IS_PENDING, 0);
|
||||||
|
resolver.update(row, values, null);
|
||||||
|
}
|
||||||
|
showInfoToast("yis_2024_local_save_image_success", "✔️");
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorToast("download_failure", "❌", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a video in the M3U8 / HLS (HTTP Live Streaming) format.
|
||||||
|
*/
|
||||||
|
public static void downloadVideo(String url, String name) {
|
||||||
|
// The first request yields multiple URLs with different stream options.
|
||||||
|
// In case of Strava, the first one is always of highest quality.
|
||||||
|
// Each stream can consist of multiple chunks.
|
||||||
|
// The second request yields the URLs of all of these chunks.
|
||||||
|
// Fetch all of them concurrently and pipe their streams into the file in order.
|
||||||
|
showInfoToast("loading", "⏳");
|
||||||
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try {
|
||||||
|
String highestQualityStreamUrl;
|
||||||
|
try (Response response = fetch(url)) {
|
||||||
|
highestQualityStreamUrl = replaceFileName(url, lines(response).findFirst().get());
|
||||||
|
}
|
||||||
|
List<Future<Response>> futures;
|
||||||
|
try (Response response = fetch(highestQualityStreamUrl)) {
|
||||||
|
futures = lines(response)
|
||||||
|
.map(line -> replaceFileName(highestQualityStreamUrl, line))
|
||||||
|
.map(chunkUrl -> Utils.submitOnBackgroundThread(() -> fetch(chunkUrl)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
ContentResolver resolver = Utils.getContext().getContentResolver();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(MediaStore.Video.Media.DISPLAY_NAME, name + '.' + "mp4");
|
||||||
|
values.put(MediaStore.Video.Media.IS_PENDING, 1);
|
||||||
|
values.put(MediaStore.Video.Media.MIME_TYPE, MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp4"));
|
||||||
|
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/Strava");
|
||||||
|
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
|
? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
||||||
|
: MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
Uri row = resolver.insert(collection, values);
|
||||||
|
try (OutputStream outputStream = resolver.openOutputStream(row)) {
|
||||||
|
Throwable error = null;
|
||||||
|
for (Future<Response> future : futures) {
|
||||||
|
if (error != null) {
|
||||||
|
if (future.cancel(true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (Response response = future.get()) {
|
||||||
|
if (error == null) {
|
||||||
|
transferTo(response.body().byteStream(), outputStream);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | IOException e) {
|
||||||
|
error = e;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
error = e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error != null) {
|
||||||
|
throw new IOException(error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
values.clear();
|
||||||
|
values.put(MediaStore.Video.Media.IS_PENDING, 0);
|
||||||
|
resolver.update(row, values, null);
|
||||||
|
}
|
||||||
|
showInfoToast("yis_2024_local_save_video_success", "✔️");
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorToast("download_failure", "❌", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getUrl(Media media) {
|
||||||
|
return media.getType() == MediaType.VIDEO
|
||||||
|
? ((Media.Video) media).getVideoUrl()
|
||||||
|
: media.getLargestUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(String name, String fallback) {
|
||||||
|
int id = Utils.getResourceIdentifier(ResourceType.STRING, name);
|
||||||
|
return id != 0
|
||||||
|
? Utils.getResourceString(id)
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showInfoToast(String resourceName, String fallback) {
|
||||||
|
String text = getString(resourceName, fallback);
|
||||||
|
Utils.showToastShort(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showErrorToast(String resourceName, String fallback, IOException exception) {
|
||||||
|
String text = getString(resourceName, fallback);
|
||||||
|
Utils.showToastLong(text + ' ' + exception.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response fetch(String url) throws IOException {
|
||||||
|
Request request = new Request.Builder().url(url).build();
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("Got HTTP status code " + response.code());
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code inputStream.transferTo(outputStream)} is "too new".
|
||||||
|
*/
|
||||||
|
private static void transferTo(InputStream in, OutputStream out) throws IOException {
|
||||||
|
byte[] buffer = new byte[1024 * 8];
|
||||||
|
int length;
|
||||||
|
while ((length = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all file names.
|
||||||
|
*/
|
||||||
|
private static Stream<String> lines(Response response) {
|
||||||
|
BufferedReader reader = new BufferedReader(response.body().charStream());
|
||||||
|
return reader.lines().filter(line -> !line.startsWith("#"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String replaceFileName(String uri, String newName) {
|
||||||
|
return uri.substring(0, uri.lastIndexOf('/') + 1) + newName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package app.revanced.extension.strava;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import com.strava.modularframework.data.Destination;
|
||||||
|
import com.strava.modularframework.data.GenericLayoutModule;
|
||||||
|
import com.strava.modularframework.data.GenericModuleField;
|
||||||
|
import com.strava.modularframework.data.ListField;
|
||||||
|
import com.strava.modularframework.data.ListProperties;
|
||||||
|
import com.strava.modularframework.data.ModularComponent;
|
||||||
|
import com.strava.modularframework.data.ModularEntry;
|
||||||
|
import com.strava.modularframework.data.ModularEntryContainer;
|
||||||
|
import com.strava.modularframework.data.ModularMenuItem;
|
||||||
|
import com.strava.modularframework.data.Module;
|
||||||
|
import com.strava.modularframework.data.MultiStateFieldDescriptor;
|
||||||
|
import com.strava.modularframeworknetwork.ModularEntryNetworkContainer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public class HideDistractionsPatch {
|
||||||
|
public static boolean upselling;
|
||||||
|
public static boolean promo;
|
||||||
|
public static boolean followSuggestions;
|
||||||
|
public static boolean challengeSuggestions;
|
||||||
|
public static boolean joinChallenge;
|
||||||
|
public static boolean joinClub;
|
||||||
|
public static boolean activityLookback;
|
||||||
|
|
||||||
|
public static List<ModularEntry> filterChildrenEntries(ModularEntry modularEntry) {
|
||||||
|
if (hideModularEntry(modularEntry)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularEntry.getChildrenEntries$original().stream()
|
||||||
|
.filter(childrenEntry -> !hideModularEntry(childrenEntry))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ModularEntry> filterEntries(ModularEntryContainer modularEntryContainer) {
|
||||||
|
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularEntryContainer.getEntries$original().stream()
|
||||||
|
.filter(entry -> !hideModularEntry(entry))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ModularEntry> filterEntries(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||||
|
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularEntryNetworkContainer.getEntries$original().stream()
|
||||||
|
.filter(entry -> !hideModularEntry(entry))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ModularMenuItem> filterMenuItems(ModularEntryContainer modularEntryContainer) {
|
||||||
|
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularEntryContainer.getMenuItems$original().stream()
|
||||||
|
.filter(menuItem -> !hideModularMenuItem(menuItem))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ListProperties filterProperties(ModularEntryContainer modularEntryContainer) {
|
||||||
|
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return modularEntryContainer.getProperties$original();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ListProperties filterProperties(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||||
|
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return modularEntryNetworkContainer.getProperties$original();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ListField filterField(ListProperties listProperties, String key) {
|
||||||
|
ListField listField = listProperties.getField$original(key);
|
||||||
|
if (hideListField(listField)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return listField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ListField> filterFields(ListField listField) {
|
||||||
|
if (hideListField(listField)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return listField.getFields$original().stream()
|
||||||
|
.filter(field -> !hideListField(field))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Module> filterModules(ModularEntry modularEntry) {
|
||||||
|
if (hideModularEntry(modularEntry)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularEntry.getModules$original().stream()
|
||||||
|
.filter(module -> !hideModule(module))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericModuleField filterField(GenericLayoutModule genericLayoutModule, String key) {
|
||||||
|
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GenericModuleField field = genericLayoutModule.getField$original(key);
|
||||||
|
if (hideGenericModuleField(field)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericModuleField[] filterFields(GenericLayoutModule genericLayoutModule) {
|
||||||
|
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||||
|
return new GenericModuleField[0];
|
||||||
|
}
|
||||||
|
return Arrays.stream(genericLayoutModule.getFields$original())
|
||||||
|
.filter(field -> !hideGenericModuleField(field))
|
||||||
|
.toArray(GenericModuleField[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericLayoutModule[] filterSubmodules(GenericLayoutModule genericLayoutModule) {
|
||||||
|
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||||
|
return new GenericLayoutModule[0];
|
||||||
|
}
|
||||||
|
return Arrays.stream(genericLayoutModule.getSubmodules$original())
|
||||||
|
.filter(submodule -> !hideGenericLayoutModule(submodule))
|
||||||
|
.toArray(GenericLayoutModule[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Module> filterSubmodules(ModularComponent modularComponent) {
|
||||||
|
if (hideByName(modularComponent.getPage()) || hideByName(modularComponent.getElement())) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return modularComponent.getSubmodules$original().stream()
|
||||||
|
.filter(submodule -> !hideModule(submodule))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, GenericModuleField> filterStateMap(MultiStateFieldDescriptor multiStateFieldDescriptor) {
|
||||||
|
return multiStateFieldDescriptor.getStateMap$original().entrySet().stream()
|
||||||
|
.filter(entry -> !hideGenericModuleField(entry.getValue()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideModule(Module module) {
|
||||||
|
return module == null ||
|
||||||
|
hideByName(module.getPage()) ||
|
||||||
|
hideByName(module.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideModularEntry(ModularEntry modularEntry) {
|
||||||
|
return modularEntry == null ||
|
||||||
|
hideByName(modularEntry.getPage()) ||
|
||||||
|
hideByName(modularEntry.getElement()) ||
|
||||||
|
hideByDestination(modularEntry.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideGenericLayoutModule(GenericLayoutModule genericLayoutModule) {
|
||||||
|
try {
|
||||||
|
return genericLayoutModule == null ||
|
||||||
|
hideByName(genericLayoutModule.getPage()) ||
|
||||||
|
hideByName(genericLayoutModule.getElement()) ||
|
||||||
|
hideByDestination(genericLayoutModule.getDestination());
|
||||||
|
} catch (RuntimeException getParentEntryOrThrowException) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideListField(ListField listField) {
|
||||||
|
return listField == null ||
|
||||||
|
hideByName(listField.getElement()) ||
|
||||||
|
hideByDestination(listField.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideGenericModuleField(GenericModuleField genericModuleField) {
|
||||||
|
return genericModuleField == null ||
|
||||||
|
hideByName(genericModuleField.getElement()) ||
|
||||||
|
hideByDestination(genericModuleField.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideModularEntryContainer(ModularEntryContainer modularEntryContainer) {
|
||||||
|
return modularEntryContainer == null ||
|
||||||
|
hideByName(modularEntryContainer.getPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideModularEntryNetworkContainer(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||||
|
return modularEntryNetworkContainer == null ||
|
||||||
|
hideByName(modularEntryNetworkContainer.getPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideModularMenuItem(ModularMenuItem modularMenuItem) {
|
||||||
|
return modularMenuItem == null ||
|
||||||
|
hideByName(modularMenuItem.getElementName()) ||
|
||||||
|
hideByDestination(modularMenuItem.getDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideByName(String name) {
|
||||||
|
return name != null && (
|
||||||
|
upselling && name.contains("_upsell") ||
|
||||||
|
promo && (name.equals("promo") || name.equals("top_of_tab_promo")) ||
|
||||||
|
followSuggestions && name.equals("suggested_follows") ||
|
||||||
|
challengeSuggestions && name.equals("suggested_challenges") ||
|
||||||
|
joinChallenge && name.equals("challenge") ||
|
||||||
|
joinClub && name.equals("club") ||
|
||||||
|
activityLookback && name.equals("highlighted_activity_lookback")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hideByDestination(Destination destination) {
|
||||||
|
if (destination == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String url = destination.getUrl();
|
||||||
|
return url != null && (
|
||||||
|
upselling && url.startsWith("strava://subscription/checkout")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
extensions/strava/stub/build.gradle.kts
Normal file
12
extensions/strava/stub/build.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/strava/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/strava/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public interface MediaContent extends Serializable {
|
||||||
|
String getCaption();
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
String getReferenceId();
|
||||||
|
|
||||||
|
MediaType getType();
|
||||||
|
|
||||||
|
void setCaption(String caption);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public final class MediaDimension implements Comparable<MediaDimension>, Serializable {
|
||||||
|
private final int height;
|
||||||
|
private final int width;
|
||||||
|
|
||||||
|
public MediaDimension(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHeightScale() {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
return height / width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getWidthScale() {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
return width / height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLandscape() {
|
||||||
|
return width > 0 && width >= height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(MediaDimension other) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
|
public enum MediaType {
|
||||||
|
PHOTO(1),
|
||||||
|
VIDEO(2);
|
||||||
|
|
||||||
|
private final int remoteValue;
|
||||||
|
|
||||||
|
private MediaType(int remoteValue) {
|
||||||
|
this.remoteValue = remoteValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemoteValue() {
|
||||||
|
return remoteValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
|
import java.util.SortedMap;
|
||||||
|
|
||||||
|
public interface RemoteMediaContent extends MediaContent {
|
||||||
|
MediaDimension getLargestSize();
|
||||||
|
|
||||||
|
String getLargestUrl();
|
||||||
|
|
||||||
|
SortedMap<Integer, MediaDimension> getSizes();
|
||||||
|
|
||||||
|
String getSmallestUrl();
|
||||||
|
|
||||||
|
RemoteMediaStatus getStatus();
|
||||||
|
|
||||||
|
SortedMap<Integer, String> getUrls();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
|
public enum RemoteMediaStatus {
|
||||||
|
NEW,
|
||||||
|
PENDING,
|
||||||
|
PROCESSED,
|
||||||
|
REPORTED,
|
||||||
|
REINSTATED,
|
||||||
|
DELETED,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class Destination implements Serializable {
|
||||||
|
public abstract String getUrl();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class GenericLayoutModule implements Serializable, Module {
|
||||||
|
public abstract Destination getDestination();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getElement();
|
||||||
|
|
||||||
|
public abstract GenericModuleField getField(String key);
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract GenericModuleField getField$original(String key);
|
||||||
|
|
||||||
|
public abstract GenericModuleField[] getFields();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract GenericModuleField[] getFields$original();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getPage();
|
||||||
|
|
||||||
|
public abstract GenericLayoutModule[] getSubmodules();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract GenericLayoutModule[] getSubmodules$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class GenericModuleField implements Serializable {
|
||||||
|
public abstract Destination getDestination();
|
||||||
|
|
||||||
|
public abstract String getElement();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class ListField {
|
||||||
|
public abstract Destination getDestination();
|
||||||
|
|
||||||
|
public abstract String getElement();
|
||||||
|
|
||||||
|
public abstract List<ListField> getFields();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract List<ListField> getFields$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
public abstract class ListProperties {
|
||||||
|
public abstract ListField getField(String key);
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract ListField getField$original(String key);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class ModularComponent implements Module {
|
||||||
|
@Override
|
||||||
|
public abstract String getElement();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getPage();
|
||||||
|
|
||||||
|
public abstract List<Module> getSubmodules();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract List<Module> getSubmodules$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ModularEntry {
|
||||||
|
List<ModularEntry> getChildrenEntries();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
List<ModularEntry> getChildrenEntries$original();
|
||||||
|
|
||||||
|
Destination getDestination();
|
||||||
|
|
||||||
|
String getElement();
|
||||||
|
|
||||||
|
List<Module> getModules();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
List<Module> getModules$original();
|
||||||
|
|
||||||
|
String getPage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class ModularEntryContainer {
|
||||||
|
public abstract List<ModularEntry> getEntries();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract List<ModularEntry> getEntries$original();
|
||||||
|
|
||||||
|
public abstract List<ModularMenuItem> getMenuItems();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract List<ModularMenuItem> getMenuItems$original();
|
||||||
|
|
||||||
|
public abstract String getPage();
|
||||||
|
|
||||||
|
public abstract ListProperties getProperties();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract ListProperties getProperties$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
public abstract class ModularMenuItem {
|
||||||
|
public abstract Destination getDestination();
|
||||||
|
|
||||||
|
public abstract String getElementName();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
public interface Module {
|
||||||
|
String getElement();
|
||||||
|
|
||||||
|
String getPage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class MultiStateFieldDescriptor {
|
||||||
|
public abstract Map<String, GenericModuleField> getStateMap();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract Map<String, GenericModuleField> getStateMap$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.strava.modularframeworknetwork;
|
||||||
|
|
||||||
|
import com.strava.modularframework.data.ListProperties;
|
||||||
|
import com.strava.modularframework.data.ModularEntry;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class ModularEntryNetworkContainer {
|
||||||
|
public abstract List<ModularEntry> getEntries();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract List<ModularEntry> getEntries$original();
|
||||||
|
|
||||||
|
public abstract String getPage();
|
||||||
|
|
||||||
|
public abstract ListProperties getProperties();
|
||||||
|
|
||||||
|
// Added by patch.
|
||||||
|
public abstract ListProperties getProperties$original();
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
package com.strava.photos.data;
|
||||||
|
|
||||||
|
import com.strava.mediamodels.data.MediaDimension;
|
||||||
|
import com.strava.mediamodels.data.MediaType;
|
||||||
|
import com.strava.mediamodels.data.RemoteMediaContent;
|
||||||
|
import com.strava.mediamodels.data.RemoteMediaStatus;
|
||||||
|
|
||||||
|
import java.util.SortedMap;
|
||||||
|
|
||||||
|
public abstract class Media implements RemoteMediaContent {
|
||||||
|
public static final class Photo extends Media {
|
||||||
|
private final Long activityId;
|
||||||
|
private final String activityName;
|
||||||
|
private final long athleteId;
|
||||||
|
private String caption;
|
||||||
|
private final String createdAt;
|
||||||
|
private final String createdAtLocal;
|
||||||
|
private final String cursor;
|
||||||
|
private final String id;
|
||||||
|
private final SortedMap<Integer, MediaDimension> sizes;
|
||||||
|
private final RemoteMediaStatus status;
|
||||||
|
private final String tag;
|
||||||
|
private final MediaType type;
|
||||||
|
private final SortedMap<Integer, String> urls;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getActivityId() {
|
||||||
|
return activityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getActivityName() {
|
||||||
|
return activityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAthleteId() {
|
||||||
|
return athleteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreatedAtLocal() {
|
||||||
|
return createdAtLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedMap<Integer, MediaDimension> getSizes() {
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMediaStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedMap<Integer, String> getUrls() {
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCaption(String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Photo(String id,
|
||||||
|
String caption,
|
||||||
|
SortedMap<Integer, String> urls,
|
||||||
|
SortedMap<Integer, MediaDimension> sizes,
|
||||||
|
long athleteId,
|
||||||
|
String createdAt,
|
||||||
|
String createdAtLocal,
|
||||||
|
Long activityId,
|
||||||
|
String activityName,
|
||||||
|
RemoteMediaStatus status,
|
||||||
|
String tag,
|
||||||
|
String cursor) {
|
||||||
|
this.id = id;
|
||||||
|
this.caption = caption;
|
||||||
|
this.urls = urls;
|
||||||
|
this.sizes = sizes;
|
||||||
|
this.athleteId = athleteId;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.createdAtLocal = createdAtLocal;
|
||||||
|
this.activityId = activityId;
|
||||||
|
this.activityName = activityName;
|
||||||
|
this.status = status;
|
||||||
|
this.tag = tag;
|
||||||
|
this.cursor = cursor;
|
||||||
|
this.type = MediaType.PHOTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Video extends Media {
|
||||||
|
private final Long activityId;
|
||||||
|
private final String activityName;
|
||||||
|
private final long athleteId;
|
||||||
|
private String caption;
|
||||||
|
private final String createdAt;
|
||||||
|
private final String createdAtLocal;
|
||||||
|
private final String cursor;
|
||||||
|
private final Float durationSeconds;
|
||||||
|
private final String id;
|
||||||
|
private final SortedMap<Integer, MediaDimension> sizes;
|
||||||
|
private final RemoteMediaStatus status;
|
||||||
|
private final String tag;
|
||||||
|
private final MediaType type;
|
||||||
|
private final SortedMap<Integer, String> urls;
|
||||||
|
private final String videoUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getActivityId() {
|
||||||
|
return activityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getActivityName() {
|
||||||
|
return activityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAthleteId() {
|
||||||
|
return athleteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCreatedAtLocal() {
|
||||||
|
return createdAtLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Float getDurationSeconds() {
|
||||||
|
return durationSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedMap<Integer, MediaDimension> getSizes() {
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMediaStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SortedMap<Integer, String> getUrls() {
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getVideoUrl() {
|
||||||
|
return videoUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCaption(String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Video(String id,
|
||||||
|
String caption,
|
||||||
|
SortedMap<Integer, String> urls,
|
||||||
|
SortedMap<Integer, MediaDimension> sizes,
|
||||||
|
long athleteId,
|
||||||
|
String createdAt,
|
||||||
|
String createdAtLocal,
|
||||||
|
Long activityId,
|
||||||
|
String activityName,
|
||||||
|
RemoteMediaStatus status,
|
||||||
|
String videoUrl,
|
||||||
|
Float durationSeconds,
|
||||||
|
String tag,
|
||||||
|
String cursor) {
|
||||||
|
this.id = id;
|
||||||
|
this.caption = caption;
|
||||||
|
this.urls = urls;
|
||||||
|
this.sizes = sizes;
|
||||||
|
this.athleteId = athleteId;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.createdAtLocal = createdAtLocal;
|
||||||
|
this.activityId = activityId;
|
||||||
|
this.activityName = activityName;
|
||||||
|
this.status = status;
|
||||||
|
this.videoUrl = videoUrl;
|
||||||
|
this.durationSeconds = durationSeconds;
|
||||||
|
this.tag = tag;
|
||||||
|
this.cursor = cursor;
|
||||||
|
this.type = MediaType.VIDEO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Long getActivityId();
|
||||||
|
|
||||||
|
public abstract String getActivityName();
|
||||||
|
|
||||||
|
public abstract long getAthleteId();
|
||||||
|
|
||||||
|
public abstract String getCreatedAt();
|
||||||
|
|
||||||
|
public abstract String getCreatedAtLocal();
|
||||||
|
|
||||||
|
public abstract String getCursor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaDimension getLargestSize() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLargestUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSmallestUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getTag();
|
||||||
|
|
||||||
|
private Media() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
package app.revanced.extension.twitch;
|
package app.revanced.extension.twitch;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.ResourceType;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
/* Called from SettingsPatch smali */
|
/* Called from SettingsPatch smali */
|
||||||
public static int getStringId(String name) {
|
public static int getStringId(String name) {
|
||||||
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "string");
|
return app.revanced.extension.shared.Utils.getResourceIdentifier(
|
||||||
|
ResourceType.STRING, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called from SettingsPatch smali */
|
/* Called from SettingsPatch smali */
|
||||||
public static int getDrawableId(String name) {
|
public static int getDrawableId(String name) {
|
||||||
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "drawable");
|
return app.revanced.extension.shared.Utils.getResourceIdentifier(
|
||||||
|
ResourceType.DRAWABLE, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,21 @@ import static app.revanced.extension.twitch.Utils.getStringId;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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.twitch.settings.preference.TwitchPreferenceFragment;
|
import app.revanced.extension.twitch.settings.preference.TwitchPreferenceFragment;
|
||||||
|
|
||||||
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
||||||
import tv.twitch.android.settings.SettingsActivity;
|
import tv.twitch.android.settings.SettingsActivity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}.
|
* Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}.
|
||||||
*/
|
*/
|
||||||
@@ -108,7 +110,7 @@ public class TwitchActivityHook {
|
|||||||
|
|
||||||
base.getFragmentManager()
|
base.getFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment)
|
.replace(Utils.getResourceIdentifier(ResourceType.ID, "fragment_container"), fragment)
|
||||||
.commit();
|
.commit();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,14 +528,8 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
* Cache used to verify if an alternative thumbnails exists for a given video id.
|
* Cache used to verify if an alternative thumbnails exists for a given video id.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
private static final Map<String, VerifiedQualities> altVideoIdLookup = new LinkedHashMap<>(100) {
|
private static final Map<String, VerifiedQualities> altVideoIdLookup =
|
||||||
private static final int CACHE_LIMIT = 1000;
|
Utils.createSizeRestrictedMap(1000);
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean removeEldestEntry(Entry eldest) {
|
|
||||||
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) {
|
private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) {
|
||||||
synchronized (altVideoIdLookup) {
|
synchronized (altVideoIdLookup) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ public class ChangeHeaderPatch {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int identifier = Utils.getResourceIdentifier(attributeName, "attr");
|
final int identifier = Utils.getResourceIdentifier(ResourceType.ATTR, attributeName);
|
||||||
if (identifier == 0) {
|
if (identifier == 0) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
Logger.printException(() -> "Could not find attribute: " + drawableName);
|
Logger.printException(() -> "Could not find attribute: " + drawableName);
|
||||||
@@ -71,7 +72,7 @@ public class ChangeHeaderPatch {
|
|||||||
? "_dark"
|
? "_dark"
|
||||||
: "_light");
|
: "_light");
|
||||||
|
|
||||||
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
|
final int identifier = Utils.getResourceIdentifier(ResourceType.DRAWABLE, drawableFullName);
|
||||||
if (identifier != 0) {
|
if (identifier != 0) {
|
||||||
return Utils.getContext().getDrawable(identifier);
|
return Utils.getContext().getDrawable(identifier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public final class DownloadsPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void activityCreated(Activity mainActivity) {
|
public static void setMainActivity(Activity mainActivity) {
|
||||||
activityRef = new WeakReference<>(mainActivity);
|
activityRef = new WeakReference<>(mainActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class FixContentProviderPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void removeNullMapEntries(Map<?, ?> map) {
|
||||||
|
map.entrySet().removeIf(entry -> {
|
||||||
|
Object value = entry.getValue();
|
||||||
|
if (value == null) {
|
||||||
|
Logger.printDebug(() -> "Removing content provider key with null value: " + entry.getKey());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
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.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -29,6 +30,15 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
|
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean getCastButtonOverrideV2(boolean original) {
|
||||||
|
if (Settings.HIDE_CAST_BUTTON.get()) return false;
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -40,10 +50,10 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
|
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
|
||||||
|
|
||||||
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
||||||
"player_control_previous_button_touch_area", "id");
|
ResourceType.ID, "player_control_previous_button_touch_area");
|
||||||
|
|
||||||
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
|
||||||
"player_control_next_button_touch_area", "id");
|
ResourceType.ID, "player_control_next_button_touch_area");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
|
|||||||
@@ -4,7 +4,17 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class HideSeekbarPatch {
|
public class HideSeekbarPatch {
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean hideSeekbar() {
|
public static boolean hideSeekbar() {
|
||||||
return Settings.HIDE_SEEKBAR.get();
|
return Settings.HIDE_SEEKBAR.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useFullscreenLargeSeekbar(boolean original) {
|
||||||
|
return Settings.FULLSCREEN_LARGE_SEEKBAR.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
|
|||||||
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.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
@@ -115,7 +116,7 @@ public final class MiniplayerPatch {
|
|||||||
* Resource is not present in older targets, and this field will be zero.
|
* Resource is not present in older targets, and this field will be zero.
|
||||||
*/
|
*/
|
||||||
private static final int MODERN_OVERLAY_SUBTITLE_TEXT
|
private static final int MODERN_OVERLAY_SUBTITLE_TEXT
|
||||||
= Utils.getResourceIdentifier("modern_miniplayer_subtitle_text", "id");
|
= Utils.getResourceIdentifier(ResourceType.ID, "modern_miniplayer_subtitle_text");
|
||||||
|
|
||||||
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
|
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
|
||||||
|
|
||||||
@@ -378,6 +379,19 @@ public final class MiniplayerPatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean allowBoldIcons(boolean original) {
|
||||||
|
if (CURRENT_TYPE == MINIMAL) {
|
||||||
|
// Minimal player does not have the correct pause/play icon (it's too large).
|
||||||
|
// Use the non bold icons instead.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
|
|||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -30,13 +29,13 @@ public final class NavigationButtonsPatch {
|
|||||||
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||||
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||||
|
|
||||||
private static final Boolean DISABLE_TRANSLUCENT_STATUS_BAR
|
private static final boolean DISABLE_TRANSLUCENT_STATUS_BAR
|
||||||
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
|
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
|
||||||
|
|
||||||
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
|
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
|
||||||
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
|
||||||
|
|
||||||
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||||
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +61,13 @@ public final class NavigationButtonsPatch {
|
|||||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useAnimatedNavigationButtons(boolean original) {
|
||||||
|
return Settings.NAVIGATION_BAR_ANIMATIONS.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,15 +20,6 @@ public class OpenShortsInRegularPlayerPatch {
|
|||||||
REGULAR_PLAYER_FULLSCREEN
|
REGULAR_PLAYER_FULLSCREEN
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
|
||||||
if (!VersionCheckPatch.IS_19_46_OR_GREATER
|
|
||||||
&& Settings.SHORTS_PLAYER_TYPE.get() == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN) {
|
|
||||||
// User imported newer settings to an older app target.
|
|
||||||
Logger.printInfo(() -> "Resetting " + Settings.SHORTS_PLAYER_TYPE);
|
|
||||||
Settings.SHORTS_PLAYER_TYPE.resetToDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
|
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
|
||||||
|
|
||||||
private static volatile boolean overrideBackPressToExit;
|
private static volatile boolean overrideBackPressToExit;
|
||||||
|
|||||||
@@ -24,18 +24,20 @@ public class OpenVideosFullscreenHookPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Returns negated value.
|
||||||
*/
|
*/
|
||||||
public static boolean openVideoFullscreenPortrait(boolean original) {
|
public static boolean doNotOpenVideoFullscreenPortrait(boolean original) {
|
||||||
Boolean openFullscreen = openNextVideoFullscreen;
|
Boolean openFullscreen = openNextVideoFullscreen;
|
||||||
if (openFullscreen != null) {
|
if (openFullscreen != null) {
|
||||||
openNextVideoFullscreen = null;
|
openNextVideoFullscreen = null;
|
||||||
return openFullscreen;
|
return !openFullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFullScreenPatchIncluded()) {
|
if (!isFullScreenPatchIncluded()) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
|
return !Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class PauseOnAudioInterruptPatch {
|
||||||
|
|
||||||
|
private static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3;
|
||||||
|
private static final int AUDIOFOCUS_LOSS_TRANSIENT = -2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point for AudioFocusRequest builder.
|
||||||
|
* Returns true if audio ducking should be disabled (willPauseWhenDucked = true).
|
||||||
|
*/
|
||||||
|
public static boolean shouldPauseOnAudioInterrupt() {
|
||||||
|
return Settings.PAUSE_ON_AUDIO_INTERRUPT.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point for onAudioFocusChange callback.
|
||||||
|
* Converts AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK to AUDIOFOCUS_LOSS_TRANSIENT
|
||||||
|
* when the setting is enabled, causing YouTube to pause instead of ducking.
|
||||||
|
*/
|
||||||
|
public static int overrideAudioFocusChange(int focusChange) {
|
||||||
|
if (Settings.PAUSE_ON_AUDIO_INTERRUPT.get() && focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
|
||||||
|
return AUDIOFOCUS_LOSS_TRANSIENT;
|
||||||
|
}
|
||||||
|
return focusChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ public class PlayerControlsPatch {
|
|||||||
|
|
||||||
Logger.printDebug(() -> "fullscreen button visibility: "
|
Logger.printDebug(() -> "fullscreen button visibility: "
|
||||||
+ (visibility == View.VISIBLE ? "VISIBLE" :
|
+ (visibility == View.VISIBLE ? "VISIBLE" :
|
||||||
visibility == View.GONE ? "GONE" : "INVISIBLE"));
|
visibility == View.GONE ? "GONE" : "INVISIBLE"));
|
||||||
|
|
||||||
fullscreenButtonVisibilityChanged(visibility == View.VISIBLE);
|
fullscreenButtonVisibilityChanged(visibility == View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/** @noinspection unused*/
|
@SuppressWarnings("unused")
|
||||||
public class RemoveViewerDiscretionDialogPatch {
|
public class RemoveViewerDiscretionDialogPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static void confirmDialog(AlertDialog dialog) {
|
public static void confirmDialog(AlertDialog dialog) {
|
||||||
if (!Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
|
if (Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
|
||||||
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
|
Logger.printDebug(() -> "Clicking alert dialog dismiss button");
|
||||||
dialog.show();
|
|
||||||
|
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
button.setSoundEffectsEnabled(false);
|
||||||
|
button.performClick();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
|
||||||
button.setSoundEffectsEnabled(false);
|
Logger.printDebug(() -> "Showing alert dialog");
|
||||||
button.performClick();
|
dialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
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.settings.BaseSettings;
|
||||||
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
|
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
@@ -131,6 +133,10 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
|
|
||||||
String conversionContextString = conversionContext.toString();
|
String conversionContextString = conversionContext.toString();
|
||||||
|
|
||||||
|
if (Settings.RYD_ENABLED.get()) { // FIXME: Remove this.
|
||||||
|
Logger.printDebug(() -> "RYD conversion context: " + conversionContext);
|
||||||
|
}
|
||||||
|
|
||||||
if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) {
|
if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package app.revanced.extension.youtube.patches;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ public class ShortsAutoplayPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) {
|
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
|
||||||
try {
|
try {
|
||||||
final boolean autoplay;
|
final boolean autoplay;
|
||||||
|
|
||||||
@@ -95,19 +93,19 @@ public class ShortsAutoplayPatch {
|
|||||||
autoplay = Settings.SHORTS_AUTOPLAY.get();
|
autoplay = Settings.SHORTS_AUTOPLAY.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
final ShortsLoopBehavior behavior = autoplay
|
Enum<?> overrideBehavior = (autoplay
|
||||||
? ShortsLoopBehavior.SINGLE_PLAY
|
? ShortsLoopBehavior.SINGLE_PLAY
|
||||||
: ShortsLoopBehavior.REPEAT;
|
: ShortsLoopBehavior.REPEAT).ytEnumValue;
|
||||||
|
|
||||||
if (behavior.ytEnumValue != null) {
|
if (overrideBehavior != null) {
|
||||||
Logger.printDebug(() -> {
|
Logger.printDebug(() -> {
|
||||||
String name = (original == null ? "unknown (null)" : original.name());
|
String name = (original == null ? "unknown (null)" : original.name());
|
||||||
return behavior == original
|
return overrideBehavior == original
|
||||||
? "Behavior setting is same as original. Using original: " + name
|
? "Behavior setting is same as original. Using original: " + name
|
||||||
: "Changing Shorts repeat behavior from: " + name + " to: " + behavior.name();
|
: "Changing Shorts repeat behavior from: " + name + " to: " + overrideBehavior.name();
|
||||||
});
|
});
|
||||||
|
|
||||||
return behavior.ytEnumValue;
|
return overrideBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (original == null) {
|
if (original == null) {
|
||||||
@@ -118,13 +116,12 @@ public class ShortsAutoplayPatch {
|
|||||||
return unknown;
|
return unknown;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "changeShortsRepeatBehavior failure", ex);
|
Logger.printException(() -> "changeShortsRepeatState failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,5 +19,12 @@ public class VersionCheckPatch {
|
|||||||
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
|
||||||
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
|
|
||||||
|
public static final boolean IS_20_21_OR_GREATER = isVersionOrGreater("20.21.00");
|
||||||
|
|
||||||
|
public static final boolean IS_20_22_OR_GREATER = isVersionOrGreater("20.22.00");
|
||||||
|
|
||||||
|
public static final boolean IS_20_31_OR_GREATER = isVersionOrGreater("20.31.00");
|
||||||
|
|
||||||
|
public static final boolean IS_20_37_OR_GREATER = isVersionOrGreater("20.37.00");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import java.util.List;
|
|||||||
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.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
|
import app.revanced.extension.shared.patches.litho.Filter;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||||
|
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -153,8 +156,8 @@ public final class AdsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, 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) {
|
||||||
if (matchedGroup == playerShoppingShelf) {
|
if (matchedGroup == playerShoppingShelf) {
|
||||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user