mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 18:03:55 +01:00
Compare commits
30 Commits
v5.30.0-de
...
v2.165.0-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2888fd69 | ||
|
|
61830b193c | ||
|
|
136201cea6 | ||
|
|
5fd74a1369 | ||
|
|
0bc2ee11e2 | ||
|
|
e9cb633350 | ||
|
|
a66bcb1d33 | ||
|
|
d8c9a0f95b | ||
|
|
66c3070d6e | ||
|
|
6b71456a42 | ||
|
|
ec5bb79f97 | ||
|
|
f63190ae31 | ||
|
|
263aaf1800 | ||
|
|
4c580b9fa0 | ||
|
|
618f6746cc | ||
|
|
24cb30f520 | ||
|
|
b379911b8b | ||
|
|
766172b8ff | ||
|
|
e21ce083e7 | ||
|
|
cbd733c226 | ||
|
|
a7e53adaa1 | ||
|
|
9d62835db4 | ||
|
|
59647d7cb2 | ||
|
|
aaaca326e7 | ||
|
|
f3ab06beea | ||
|
|
bc43d9c76f | ||
|
|
3bb9e3c504 | ||
|
|
5ff256509a | ||
|
|
dd16fbf0b2 | ||
|
|
e584da9cbf |
@@ -1,3 +0,0 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
9
.gitattributes
vendored
9
.gitattributes
vendored
@@ -1,9 +0,0 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# Linux start script should use lf
|
||||
/gradlew text eol=lf
|
||||
|
||||
# These are Windows script files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
73
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a very clearly broken issue.
|
||||
title: 'bug: <title>'
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# ReVanced bug report
|
||||
|
||||
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-patches/labels/bug).
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Error while patching
|
||||
- Error at runtime
|
||||
- Cosmetic
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: How did you find the bug? Any additional details that might help?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Add the steps to reproduce this bug including your environment.
|
||||
placeholder: Step 1. Download some files. Step 2. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. Capture crash logs by running `logcat | grep AndroidRuntime`.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots or videos
|
||||
description: Add screenshots or videos that show the bug here.
|
||||
placeholder: Drag and drop the screenshots/videos into this box.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solution
|
||||
description: If applicable, add a possible solution.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
||||
110
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
110
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,110 +0,0 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: 'bug: '
|
||||
labels: ['Bug report']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced Patches bug report
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: |
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
- List used patches if applicable
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Error logs
|
||||
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solution
|
||||
description: If applicable, add a possible solution to the bug.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your bug report will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed bug reports and this is not a duplicate.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃 Documentation
|
||||
url: https://github.com/revanced/revanced-documentation/
|
||||
about: Don't know how or where to start? Check out our documentation!
|
||||
- name: 🗨 Discussions
|
||||
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||
about: Have something unspecific to ReVanced Patches in mind? Search for or start a new discussion!
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
106
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,106 +0,0 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a detailed request for a new feature.
|
||||
title: 'feat: '
|
||||
labels: ['Feature request']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-patches/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# ReVanced Patches feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: |
|
||||
- Describe your feature in detail
|
||||
- Add images, videos, links, examples, references, etc. if possible
|
||||
- Add the target application name in case you request a new patch
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
A strong motivation is necessary for a feature request to be considered.
|
||||
|
||||
- Why should this feature be implemented?
|
||||
- What is the explicit use case?
|
||||
- What are the benefits?
|
||||
- What makes this feature important?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your feature request will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed feature requests and this is not a duplicate
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
41
.github/ISSUE_TEMPLATE/patch-request.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/patch-request.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: ⭐ Patch Request
|
||||
description: Create a detailed patch request.
|
||||
title: 'feat(patch): '
|
||||
labels: [patch-request]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Application
|
||||
description: Which application is this patch for?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Issue
|
||||
description: What is the issue this patch should solve?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Patch
|
||||
description: "Describe the patch you'd like to see in detail."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: Why should your patch request should be considered? What makes it valuable to the community?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
||||
2
.github/config.yml
vendored
2
.github/config.yml
vendored
@@ -1,2 +0,0 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
|
||||
22
.github/dependabot.yml
vendored
22
.github/dependabot.yml
vendored
@@ -1,22 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: npm
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: gradle
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
37
.github/workflows/build_pull_request.yml
vendored
37
.github/workflows/build_pull_request.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Build pull request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew :patches:buildAndroid --no-daemon
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: revanced-patches
|
||||
path: patches/build/libs
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Open a PR to main
|
||||
name: PR to main
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
@@ -15,17 +15,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
uses: actions/checkout@v3
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: main
|
||||
destination_branch: 'main'
|
||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||
pr_body: |
|
||||
This pull request will ${{ env.MESSAGE }}.
|
||||
|
||||
## Before merging this PR
|
||||
|
||||
- [ ] Pull translations from Crowdin
|
||||
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||
pr_draft: true
|
||||
44
.github/workflows/pull_strings.yml
vendored
44
.github/workflows/pull_strings.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Pull strings
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pull:
|
||||
name: Pull strings
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: dev
|
||||
fetch-depth: 0
|
||||
clean: true
|
||||
|
||||
- name: Pull strings
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
skip_ref_checkout: true
|
||||
localization_branch_name: feat/translations
|
||||
create_pull_request: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Open pull request
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
source_branch: feat/translations
|
||||
destination_branch: dev
|
||||
pr_title: "chore: Sync translations"
|
||||
pr_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)"
|
||||
33
.github/workflows/push_strings.yml
vendored
33
.github/workflows/push_strings.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: Push strings
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- patches/src/main/resources/addresources/values/strings.xml
|
||||
|
||||
jobs:
|
||||
push:
|
||||
name: Push strings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Preprocess strings
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew clean preprocessCrowdinStrings
|
||||
|
||||
- name: Push strings
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: true
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
68
.github/workflows/release.yml
vendored
68
.github/workflows/release.yml
vendored
@@ -6,61 +6,47 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew :patches:buildAndroid clean
|
||||
|
||||
distribution: 'zulu'
|
||||
cache: gradle
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
- name: Setup Android SDK
|
||||
uses: amyu/setup-android@v2
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
id: release
|
||||
cache-disabled: false
|
||||
sdk-version: '33'
|
||||
build-tools-version: '33.0.1'
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Attest
|
||||
if: steps.release.outputs.new_release_published == 'true'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
||||
subject-path: patches/build/libs/patches-*.rvp
|
||||
run: ./gradlew generateMeta clean --no-daemon
|
||||
- name: Setup semantic-release
|
||||
run: npm install
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
||||
|
||||
18
.github/workflows/update-gradle-wrapper.yml
vendored
18
.github/workflows/update-gradle-wrapper.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Update Gradle wrapper
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 1 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||
with:
|
||||
target-branch: dev
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -122,8 +122,5 @@ gradle-app.setting
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Gradle properties, due to Github token
|
||||
# gradle properties, due to Github token
|
||||
./gradle.properties
|
||||
|
||||
# One package is called the same as the Gradle build folder
|
||||
!**/src/**/build/
|
||||
26
.releaserc
26
.releaserc
@@ -7,13 +7,7 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
@@ -21,10 +15,11 @@
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"gradle.properties"
|
||||
],
|
||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
"gradle.properties",
|
||||
"patches.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -32,17 +27,20 @@
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)"
|
||||
"path": "build/libs/*.jar"
|
||||
},
|
||||
{
|
||||
"path": "patches.json"
|
||||
}
|
||||
],
|
||||
"successComment": false
|
||||
successComment: false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"backmergeBranches": [{"from": "main", "to": "dev"}],
|
||||
"clearWorkspace": true
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
8483
CHANGELOG.md
8483
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
128
CONTRIBUTING.md
128
CONTRIBUTING.md
@@ -1,128 +0,0 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 👋 Contribution guidelines
|
||||
|
||||
This document describes how to contribute to ReVanced Patches.
|
||||
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
* The [documentation](https://github.com/ReVanced/revanced-patcher/tree/main/docs) contains the fundamentals
|
||||
of ReVanced Patcher and how to use ReVanced Patcher to create patches
|
||||
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
||||
* [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests
|
||||
|
||||
## 🙏 Submitting a feature request
|
||||
|
||||
Features can be requested by opening an issue using the
|
||||
[Feature request issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||
|
||||
> **Note**
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Patches.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
If you encounter a bug while using ReVanced Patches, open an issue using the
|
||||
[Bug report issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||
|
||||
## 🌐 Submitting translations
|
||||
|
||||
You can contribute translations at [translate.revanced.app](https://translate.revanced.app).
|
||||
|
||||
## 🧑⚖️ Guidelines for requesting or contributing patches
|
||||
|
||||
To maintain a high-quality and ethical collection of patches, the following guidelines for requesting
|
||||
or contributing patches are effective as of September 14, 2023. Any patches present prior to this date
|
||||
are unaffected by this change.
|
||||
|
||||
> **Note**
|
||||
> We generally adhere to the guidelines outlined below. However, we may make exceptions
|
||||
> in specific cases based on our discretion. Pull requests for patches that deviate from the guidelines
|
||||
> will be evaluated individually. While a patch may not align with our general guidelines,
|
||||
> we will consider its acceptance on a case-by-case basis, taking into account its impact on user experience
|
||||
> and ethical considerations. We reserve the right to make exceptions for patches that provide significant value.
|
||||
|
||||
✅ Examples for acceptable patches include:
|
||||
|
||||
* Customizations: Feel free to contribute patches that allow users to personalize their experience
|
||||
* Ad-Blocking: Patches aimed at enhancing user privacy and blocking intrusive advertisements are appreciated
|
||||
* Feature additions: Patches that add new features or change behaviour to the app are welcome
|
||||
|
||||
❌ Examples for unacceptable patches include:
|
||||
|
||||
* Payment circumvention: We do not accept patches that exist solely to bypass payment for the app or any of its features
|
||||
* Malicious patches: Patches that are malicious in nature are not allowed
|
||||
|
||||
## 📝 How to contribute
|
||||
|
||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||
with the maintainers of ReVanced Patches. This will help you determine whether your change is acceptable
|
||||
and whether it is worth your time to implement it
|
||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||
3. Commit your changes. In case you are contributing a new patch, make sure to follow the conventions for patches
|
||||
described in the [ReVanced Patcher documentation](https://github.com/ReVanced/revanced-patcher/tree/main/docs)
|
||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||
that your pull request closes in the description of your pull request
|
||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Patches
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Patches,
|
||||
ReVanced
|
||||
122
README-template.md
Normal file
122
README-template.md
Normal file
@@ -0,0 +1,122 @@
|
||||
## 🧩 Patches
|
||||
|
||||
The official Patch bundle provided by ReVanced and the community.
|
||||
|
||||
> Looking for the JSON variant of this? [Click here](patches.json).
|
||||
|
||||
{{ table }}
|
||||
|
||||
## 📝 JSON Format
|
||||
|
||||
This section explains the JSON format for the [patches.json](patches.json) file.
|
||||
|
||||
The file contains an array of objects, each object representing a patch. The object contains the following properties:
|
||||
|
||||
| key | description |
|
||||
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name` | The name of the patch. |
|
||||
| `description` | The description of the patch. |
|
||||
| `version` | The version of the patch. |
|
||||
| `excluded` | Whether the patch is excluded by default. If `true`, the patch must never be included by default. |
|
||||
| `options` | An array of options for this patch. |
|
||||
| `options.key` | The key of the option. |
|
||||
| `options.title` | The title of the option. |
|
||||
| `options.description` | The description of the option. |
|
||||
| `options.required` | Whether the option is required. |
|
||||
| `options.choices?` | An array of choices of the option. This may be `null` if this option has no choices. The element type of this array may be any type. It can be a `String`, `Int` or something else. |
|
||||
| `dependencies` | An array of dependencies, which are patch names. |
|
||||
| `compatiblePackages` | An array of packages compatible with this patch. |
|
||||
| `compatiblePackages.name` | The name of the package. |
|
||||
| `compatiblePackages.versions` | An array of versions of the package compatible with this patch. If empty, all versions are seemingly compatible. |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "remember-video-quality",
|
||||
"description": "Adds the ability to remember the video quality you chose in the video quality flyout.",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"options": [],
|
||||
"dependencies": [
|
||||
"integrations",
|
||||
"video-id-hook"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": [
|
||||
"17.22.36",
|
||||
"17.24.35",
|
||||
"17.26.35",
|
||||
"17.27.39",
|
||||
"17.28.34",
|
||||
"17.29.34",
|
||||
"17.32.35",
|
||||
"17.33.42"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"description": "Enables a custom theme.",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
"key": "theme",
|
||||
"title": "Theme",
|
||||
"description": "Select a theme.",
|
||||
"required": true,
|
||||
"choices": [
|
||||
"Amoled"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"locale-config-fix"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "custom-branding",
|
||||
"description": "Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
"key": "appName",
|
||||
"title": "Application Name",
|
||||
"description": "The name of the application it will show on your home screen.",
|
||||
"required": true,
|
||||
"choices": null
|
||||
},
|
||||
{
|
||||
"key": "appIconPath",
|
||||
"title": "Application Icon Path",
|
||||
"description": "A path to the icon of the application.",
|
||||
"required": false,
|
||||
"choices": null
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"locale-config-fix"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
532
README.md
532
README.md
@@ -1,105 +1,463 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
## 🧩 Patches
|
||||
|
||||
# 🧩 ReVanced Patches
|
||||
The official Patch bundle provided by ReVanced and the community.
|
||||
|
||||

|
||||

|
||||
> Looking for the JSON variant of this? [Click here](patches.json).
|
||||
|
||||
This repository contains a collection of ReVanced Patches.
|
||||
### [📦 `com.google.android.youtube`](https://play.google.com/store/apps/details?id=com.google.android.youtube)
|
||||
<details>
|
||||
|
||||
## ❓ About
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `always-autorepeat` | Always repeats the playing video again. | 18.03.36 |
|
||||
| `client-spoof` | Spoofs the YouTube or Vanced client to prevent playback issues. | all |
|
||||
| `comments` | Hides components related to comments. | 18.03.36 |
|
||||
| `copy-video-url` | Adds buttons in player to copy video links. | 18.03.36 |
|
||||
| `custom-branding` | Changes the YouTube launcher icon and name to your choice (defaults to ReVanced). | all |
|
||||
| `custom-video-buffer` | Lets you change the buffers of videos. | 18.03.36 |
|
||||
| `custom-video-speed` | Adds more video speed options. | 18.03.36 |
|
||||
| `debugging` | Adds debugging options. | all |
|
||||
| `disable-auto-captions` | Disable forced captions from being automatically enabled. | 18.03.36 |
|
||||
| `disable-fullscreen-panels` | Disables video description and comments panel in fullscreen view. | 18.03.36 |
|
||||
| `disable-player-popup-panels` | Disables panels from appearing automatically when going into fullscreen (playlist or live chat). | 18.03.36 |
|
||||
| `disable-shorts-on-startup` | Disables playing YouTube Shorts when launching YouTube. | 18.03.36 |
|
||||
| `disable-zoom-haptics` | Disables haptics when zooming. | all |
|
||||
| `downloads` | Enables downloading music and videos from YouTube. | 18.03.36 |
|
||||
| `enable-wide-searchbar` | Replaces the search icon with a wide search bar. This will hide the YouTube logo when active. | 18.03.36 |
|
||||
| `general-ads` | Removes general ads. | 18.03.36 |
|
||||
| `hdr-auto-brightness` | Makes the brightness of HDR videos follow the system default. | 18.03.36 |
|
||||
| `hide-album-cards` | Hides the album cards below the artist description. | 18.03.36 |
|
||||
| `hide-artist-card` | Hides the artist card below the searchbar. | 18.03.36 |
|
||||
| `hide-autoplay-button` | Hides the autoplay button in the video player. | 18.03.36 |
|
||||
| `hide-breaking-news-shelf` | Hides the breaking news shelf on the homepage tab. | 18.03.36 |
|
||||
| `hide-captions-button` | Hides the captions button on video player. | 18.03.36 |
|
||||
| `hide-cast-button` | Hides the cast button in the video player. | all |
|
||||
| `hide-create-button` | Hides the create button in the navigation bar. | 18.03.36 |
|
||||
| `hide-crowdfunding-box` | Hides the crowdfunding box between the player and video description. | 18.03.36 |
|
||||
| `hide-email-address` | Hides the email address in the account switcher. | 18.03.36 |
|
||||
| `hide-endscreen-cards` | Hides the suggested video cards at the end of a video in fullscreen. | 18.03.36 |
|
||||
| `hide-floating-microphone-button` | Hides the floating microphone button which appears in search. | 18.03.36 |
|
||||
| `hide-info-cards` | Hides info cards in videos. | 18.03.36 |
|
||||
| `hide-my-mix` | Hides mix playlists. | 18.03.36 |
|
||||
| `hide-player-buttons` | Adds the option to hide video player previous and next buttons. | all |
|
||||
| `hide-seekbar` | Hides the seekbar. | 18.03.36 |
|
||||
| `hide-shorts-button` | Hides the shorts button on the navigation bar. | 18.03.36 |
|
||||
| `hide-timestamp` | Hides timestamp in video player. | 18.03.36 |
|
||||
| `hide-video-action-buttons` | Adds the options to hide action buttons under a video. | 18.03.36 |
|
||||
| `hide-watch-in-vr` | Hides the option to watch in VR from the player settings flyout panel. | 18.03.36 |
|
||||
| `hide-watermark` | Hides creator's watermarks on videos. | 18.03.36 |
|
||||
| `microg-support` | Allows YouTube ReVanced to run without root and under a different package name with Vanced MicroG. | 18.03.36 |
|
||||
| `minimized-playback` | Enables minimized and background playback. | 18.03.36 |
|
||||
| `old-quality-layout` | Enables the original video quality flyout in the video player settings | 18.03.36 |
|
||||
| `open-links-externally` | Open links outside of the app directly in your browser. | 18.03.36 |
|
||||
| `premium-heading` | Shows premium branding on the home screen. | all |
|
||||
| `remember-playback-rate` | Adds the ability to remember the playback rate you chose in the video playback rate flyout. | 18.03.36 |
|
||||
| `remember-video-quality` | Adds the ability to remember the video quality you chose in the video quality flyout. | 18.03.36 |
|
||||
| `remove-player-button-background` | Removes the background from the video player buttons. | 18.03.36 |
|
||||
| `return-youtube-dislike` | Shows the dislike count of videos using the Return YouTube Dislike API. | 18.03.36 |
|
||||
| `seekbar-tapping` | Enables tap-to-seek on the seekbar of the video player. | 18.03.36 |
|
||||
| `sponsorblock` | Integrates SponsorBlock which allows skipping video segments such as sponsored content. | 18.03.36 |
|
||||
| `spoof-app-version` | Tricks YouTube into thinking, you are running an older version of the app. One of the side effects also includes restoring the old UI. | 18.03.36 |
|
||||
| `swipe-controls` | Adds volume and brightness swipe controls. | 18.03.36 |
|
||||
| `tablet-mini-player` | Enables the tablet mini player layout. | 18.03.36 |
|
||||
| `theme` | Applies a custom theme. | all |
|
||||
| `video-ads` | Removes ads in the video player. | 18.03.36 |
|
||||
</details>
|
||||
|
||||
Patches are small modifications to Android apps that allow you to change the behavior of or add new features,
|
||||
block ads, customize the appearance, and much more.
|
||||
### [📦 `com.google.android.apps.youtube.music`](https://play.google.com/store/apps/details?id=com.google.android.apps.youtube.music)
|
||||
<details>
|
||||
|
||||
## 💪 Features
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `background-play` | Enables playing music in the background. | all |
|
||||
| `codecs-unlock` | Adds more audio codec options. The new audio codecs usually result in better audio quality. | all |
|
||||
| `compact-header` | Hides the music category bar at the top of the homepage. | all |
|
||||
| `exclusive-audio-playback` | Enables the option to play music without video. | all |
|
||||
| `hide-get-premium` | Removes all "Get Premium" evidences from the avatar menu. | 5.39.52 |
|
||||
| `minimized-playback-music` | Enables minimized playback on Kids music. | all |
|
||||
| `music-microg-support` | Allows YouTube Music ReVanced to run without root and under a different package name. | all |
|
||||
| `music-video-ads` | Removes ads in the music player. | all |
|
||||
| `tasteBuilder-remover` | Removes the "Tell us which artists you like" card from the home screen. | all |
|
||||
| `upgrade-button-remover` | Removes the upgrade tab from the pivot bar. | all |
|
||||
</details>
|
||||
|
||||
Some of the features the patches provide are:
|
||||
### [📦 `com.ss.android.ugc.trill`](https://play.google.com/store/apps/details?id=com.ss.android.ugc.trill)
|
||||
<details>
|
||||
|
||||
* 🚫 **Block ads**: Say goodbye to ads
|
||||
* ⭐ **Customize your app**: Personalize the appearance of apps with various layouts and themes
|
||||
* 🪄 **Add new features**: Extend the functionality of apps with lots of new features
|
||||
* ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions,
|
||||
export activities, etc.
|
||||
* ✨ **And much more!**
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `disable-login-requirement` | Do not force login. | all |
|
||||
| `downloads` | Removes download restrictions and changes the default path to download to. | 27.8.3 |
|
||||
| `feed-filter` | Filters tiktok videos: removing ads, removing livestreams. | 27.8.3 |
|
||||
| `fix-google-login` | Allows logging in with a Google account. | all |
|
||||
| `hide-ads` | Removes ads from TikTok. | all |
|
||||
| `playback-speed` | Enables the playback speed option for all videos. | all |
|
||||
| `settings` | Adds ReVanced settings to TikTok. | 27.8.3 |
|
||||
| `show-seekbar` | Shows progress bar for all video. | all |
|
||||
| `sim-spoof` | Spoofs the information which is retrieved from the sim-card. | 27.8.3 |
|
||||
</details>
|
||||
|
||||
For a complete list of all available patches, visit [revanced.app/patches](https://revanced.app/patches).
|
||||
### [📦 `com.zhiliaoapp.musically`](https://play.google.com/store/apps/details?id=com.zhiliaoapp.musically)
|
||||
<details>
|
||||
|
||||
## 🚀 How to get started
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `disable-login-requirement` | Do not force login. | all |
|
||||
| `downloads` | Removes download restrictions and changes the default path to download to. | 27.8.3 |
|
||||
| `feed-filter` | Filters tiktok videos: removing ads, removing livestreams. | 27.8.3 |
|
||||
| `fix-google-login` | Allows logging in with a Google account. | all |
|
||||
| `hide-ads` | Removes ads from TikTok. | all |
|
||||
| `playback-speed` | Enables the playback speed option for all videos. | all |
|
||||
| `settings` | Adds ReVanced settings to TikTok. | 27.8.3 |
|
||||
| `show-seekbar` | Shows progress bar for all video. | all |
|
||||
| `sim-spoof` | Spoofs the information which is retrieved from the sim-card. | 27.8.3 |
|
||||
</details>
|
||||
|
||||
You can use [ReVanced CLI](https://github.com/ReVanced/revanced-cli) or [ReVanced Manager](https://github.com/ReVanced/revanced-manager) to use ReVanced Patches.
|
||||
### [📦 `tv.twitch.android.app`](https://play.google.com/store/apps/details?id=tv.twitch.android.app)
|
||||
<details>
|
||||
|
||||
## 📚 Everything else
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `block-audio-ads` | Blocks audio ads in streams and VODs. | 14.6.1 |
|
||||
| `block-embedded-ads` | Blocks embedded steam ads using services like TTV.lol or PurpleAdBlocker. | 14.6.1 |
|
||||
| `block-video-ads` | Blocks video ads in streams and VODs. | 14.6.1 |
|
||||
| `debug-mode` | Enables Twitch's internal debugging mode. | all |
|
||||
| `settings` | Adds settings menu to Twitch. | all |
|
||||
| `show-deleted-messages` | Shows deleted chat messages behind a clickable spoiler. | all |
|
||||
</details>
|
||||
|
||||
### 📙 Contributing
|
||||
### [📦 `com.twitter.android`](https://play.google.com/store/apps/details?id=com.twitter.android)
|
||||
<details>
|
||||
|
||||
Thank you for considering contributing to ReVanced Patches. You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `dynamic-color` | Replaces the default Twitter Blue with the users Material You palette. | all |
|
||||
| `hide-ads` | Hides ads. | all |
|
||||
| `hide-recommended-users` | Hides recommended users. | all |
|
||||
| `hide-views-stats` | Hides the view stats under tweets. | 9.71.0-release.0 |
|
||||
| `monochrome-icon` | Adds a monochrome icon. | all |
|
||||
</details>
|
||||
|
||||
### 🛠️ Building
|
||||
### [📦 `com.spotify.music`](https://play.google.com/store/apps/details?id=com.spotify.music)
|
||||
<details>
|
||||
|
||||
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `disable-capture-restriction` | Allows capturing Spotify's audio output while screen sharing or screen recording. | all |
|
||||
| `hide-premium-navbar` | Removes the premium tab from the navbar. | all |
|
||||
| `spotify-theme` | Applies a custom theme. | all |
|
||||
</details>
|
||||
|
||||
## 📜 Licence
|
||||
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage)
|
||||
<details>
|
||||
|
||||
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.
|
||||
Any modifications to ReVanced Patches must also be made available under the GPL,
|
||||
along with build & install instructions.
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `general-reddit-ads` | Removes general ads from the Reddit frontpage and subreddits. | 2023.09.1 |
|
||||
| `hide-subreddit-banner` | Hides banner ads from comments on subreddits. | 2023.09.1 |
|
||||
| `premium-icon-reddit` | Unlocks premium Reddit app icons. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.vanced.android.youtube`](https://play.google.com/store/apps/details?id=com.vanced.android.youtube)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `client-spoof` | Spoofs the YouTube or Vanced client to prevent playback issues. | all |
|
||||
| `hide-ads` | Removes general ads. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `at.gv.bmf.bmf2go`](https://play.google.com/store/apps/details?id=at.gv.bmf.bmf2go)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `remove-bootloader-detection` | Removes the check for an unlocked bootloader. | 2.2.0 |
|
||||
| `remove-root-detection` | Removes the check for root permissions. | 2.2.0 |
|
||||
</details>
|
||||
|
||||
### [📦 `at.gv.oe.app`](https://play.google.com/store/apps/details?id=at.gv.oe.app)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `remove-root-detection` | Removes the check for root permissions and unlocked bootloader. | 2.5.2 |
|
||||
| `spoof-signature` | Spoofs the signature of the app. | 2.5.2 |
|
||||
</details>
|
||||
|
||||
### [📦 `com.myprog.hexedit`](https://play.google.com/store/apps/details?id=com.myprog.hexedit)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `disable-ads` | Disables ads in HexEditor. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.spotify.lite`](https://play.google.com/store/apps/details?id=com.spotify.lite)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `enable-on-demand` | Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.instagram.android`](https://play.google.com/store/apps/details?id=com.instagram.android)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `hide-timeline-ads` | Removes ads from the timeline. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `org.citra.citra_emu`](https://play.google.com/store/apps/details?id=org.citra.citra_emu)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `premium-unlock` | Unlocks premium functions. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `org.citra.citra_emu.canary`](https://play.google.com/store/apps/details?id=org.citra.citra_emu.canary)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `premium-unlock` | Unlocks premium functions. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.backdrops.wallpapers`](https://play.google.com/store/apps/details?id=com.backdrops.wallpapers)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `pro-unlock` | Unlocks pro-only functions. | 4.52 |
|
||||
</details>
|
||||
|
||||
### [📦 `de.dwd.warnapp`](https://play.google.com/store/apps/details?id=de.dwd.warnapp)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `promo-code-unlock` | Disables the validation of promo code. Any code will work to unlock all features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `net.binarymode.android.irplus`](https://play.google.com/store/apps/details?id=net.binarymode.android.irplus)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `remove-ads` | Removes all ads from the app. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `eu.faircode.netguard`](https://play.google.com/store/apps/details?id=eu.faircode.netguard)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `remove-broadcasts-restriction` | Enables starting/stopping NetGuard via broadcasts. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.dci.dev.androidtwelvewidgets`](https://play.google.com/store/apps/details?id=com.dci.dev.androidtwelvewidgets)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-paid-widgets` | Unlocks paid widgets of the app | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.microblink.photomath`](https://play.google.com/store/apps/details?id=com.microblink.photomath)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-plus` | Unlocks plus features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `io.yuka.android`](https://play.google.com/store/apps/details?id=io.yuka.android)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-premium` | Unlocks premium features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.teslacoilsw.launcher`](https://play.google.com/store/apps/details?id=com.teslacoilsw.launcher)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-prime` | Unlocks Nova Prime and all functions of the app. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `co.windyapp.android`](https://play.google.com/store/apps/details?id=co.windyapp.android)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-pro` | Unlocks all pro features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.ithebk.expensemanager`](https://play.google.com/store/apps/details?id=com.ithebk.expensemanager)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-pro` | Unlocks pro features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `ginlemon.iconpackstudio`](https://play.google.com/store/apps/details?id=ginlemon.iconpackstudio)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-pro` | Unlocks all pro features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `com.awedea.nyx`](https://play.google.com/store/apps/details?id=com.awedea.nyx)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-pro` | Unlocks all pro features. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `org.totschnig.myexpenses`](https://play.google.com/store/apps/details?id=org.totschnig.myexpenses)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-pro` | Unlocks all professional features. | 3.4.9 |
|
||||
</details>
|
||||
|
||||
### [📦 `com.ticktick.task`](https://play.google.com/store/apps/details?id=com.ticktick.task)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-themes` | Unlocks all themes. | all |
|
||||
</details>
|
||||
|
||||
### [📦 `net.dinglisch.android.taskerm`](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)
|
||||
<details>
|
||||
|
||||
| 💊 Patch | 📜 Description | 🏹 Target Version |
|
||||
|:--------:|:--------------:|:-----------------:|
|
||||
| `unlock-trial` | Unlocks the trial version. | all |
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## 📝 JSON Format
|
||||
|
||||
This section explains the JSON format for the [patches.json](patches.json) file.
|
||||
|
||||
The file contains an array of objects, each object representing a patch. The object contains the following properties:
|
||||
|
||||
| key | description |
|
||||
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name` | The name of the patch. |
|
||||
| `description` | The description of the patch. |
|
||||
| `version` | The version of the patch. |
|
||||
| `excluded` | Whether the patch is excluded by default. If `true`, the patch must never be included by default. |
|
||||
| `options` | An array of options for this patch. |
|
||||
| `options.key` | The key of the option. |
|
||||
| `options.title` | The title of the option. |
|
||||
| `options.description` | The description of the option. |
|
||||
| `options.required` | Whether the option is required. |
|
||||
| `options.choices?` | An array of choices of the option. This may be `null` if this option has no choices. The element type of this array may be any type. It can be a `String`, `Int` or something else. |
|
||||
| `dependencies` | An array of dependencies, which are patch names. |
|
||||
| `compatiblePackages` | An array of packages compatible with this patch. |
|
||||
| `compatiblePackages.name` | The name of the package. |
|
||||
| `compatiblePackages.versions` | An array of versions of the package compatible with this patch. If empty, all versions are seemingly compatible. |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "remember-video-quality",
|
||||
"description": "Adds the ability to remember the video quality you chose in the video quality flyout.",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"options": [],
|
||||
"dependencies": [
|
||||
"integrations",
|
||||
"video-id-hook"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": [
|
||||
"17.22.36",
|
||||
"17.24.35",
|
||||
"17.26.35",
|
||||
"17.27.39",
|
||||
"17.28.34",
|
||||
"17.29.34",
|
||||
"17.32.35",
|
||||
"17.33.42"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"description": "Enables a custom theme.",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
"key": "theme",
|
||||
"title": "Theme",
|
||||
"description": "Select a theme.",
|
||||
"required": true,
|
||||
"choices": [
|
||||
"Amoled"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"locale-config-fix"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "custom-branding",
|
||||
"description": "Changes the YouTube launcher icon and name to your choice (defaults to ReVanced).",
|
||||
"version": "0.0.1",
|
||||
"excluded": false,
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
"key": "appName",
|
||||
"title": "Application Name",
|
||||
"description": "The name of the application it will show on your home screen.",
|
||||
"required": true,
|
||||
"choices": null
|
||||
},
|
||||
{
|
||||
"key": "appIconPath",
|
||||
"title": "Application Icon Path",
|
||||
"description": "A path to the icon of the application.",
|
||||
"required": false,
|
||||
"choices": null
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"locale-config-fix"
|
||||
],
|
||||
"compatiblePackages": [
|
||||
{
|
||||
"name": "com.google.android.youtube",
|
||||
"versions": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 11 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,3 +1,66 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library) apply false
|
||||
kotlin("jvm") version "1.7.0"
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials {
|
||||
username = githubUsername
|
||||
password = githubPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("app.revanced:revanced-patcher:7.0.0")
|
||||
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
||||
// Required for meta
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
}
|
||||
|
||||
tasks {
|
||||
register<DefaultTask>("generateBundle") {
|
||||
description = "Generate dex files from build and bundle them in the jar file"
|
||||
dependsOn(build)
|
||||
|
||||
doLast {
|
||||
val androidHome = System.getenv("ANDROID_HOME") ?: throw GradleException("ANDROID_HOME not found")
|
||||
val d8 = "${androidHome}/build-tools/33.0.1/d8"
|
||||
val input = configurations.archives.get().allArtifacts.files.files.first().absolutePath
|
||||
val work = File("${buildDir}/libs")
|
||||
|
||||
exec {
|
||||
workingDir = work
|
||||
commandLine = listOf(d8, input)
|
||||
}
|
||||
|
||||
exec {
|
||||
workingDir = work
|
||||
commandLine = listOf("zip", "-u", input, "classes.dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
register<JavaExec>("generateMeta") {
|
||||
description = "Generate metadata for this bundle"
|
||||
dependsOn(build)
|
||||
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
mainClass.set("app.revanced.meta.PatchesFileGenerator")
|
||||
}
|
||||
// Dummy task to fix the Gradle semantic-release plugin.
|
||||
// Remove this if you forked it to support building only.
|
||||
// Tracking issue: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||
register<DefaultTask>("publish") {
|
||||
group = "publish"
|
||||
description = "Dummy task"
|
||||
dependsOn(named("generateBundle"), named("generateMeta"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
project_id_env: "CROWDIN_PROJECT_ID"
|
||||
api_token_env: "CROWDIN_PERSONAL_TOKEN"
|
||||
|
||||
preserve_hierarchy: false
|
||||
files:
|
||||
- source: patches/src/main/resources/addresources/values/strings.xml
|
||||
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
|
||||
skip_untranslated_strings: true
|
||||
@@ -1,16 +0,0 @@
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,28 +0,0 @@
|
||||
package app.revanced.extension.all.misc.hide.adb;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.provider.Settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class HideAdbPatch {
|
||||
private static final List<String> SPOOF_SETTINGS = Arrays.asList("adb_enabled", "adb_wifi_enabled", "development_settings_enabled");
|
||||
|
||||
public static int getInt(ContentResolver cr, String name) throws Settings.SettingNotFoundException {
|
||||
if (SPOOF_SETTINGS.contains(name)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Settings.Global.getInt(cr, name);
|
||||
}
|
||||
|
||||
public static int getInt(ContentResolver cr, String name, int def) {
|
||||
if (SPOOF_SETTINGS.contains(name)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Settings.Global.getInt(cr, name, def);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
</manifest>
|
||||
@@ -1,424 +0,0 @@
|
||||
package app.revanced.extension.all.misc.connectivity.wifi.spoof;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class SpoofWifiPatch {
|
||||
|
||||
// Used to check what the (real or fake) active network is (take a look at `hasTransport`).
|
||||
private static ConnectivityManager CONNECTIVITY_MANAGER;
|
||||
|
||||
// If Wifi is not enabled, these are types that would pretend to be Wifi for android.net.Network (lower index = higher priority).
|
||||
// This does not apply to android.net.NetworkInfo, because we can pretend that Wifi is always active there.
|
||||
//
|
||||
// VPN should be a fallback, because Reverse Tethering uses VPN.
|
||||
private static final int[] FAKE_FALLBACK_NETWORKS = { NetworkCapabilities.TRANSPORT_ETHERNET, NetworkCapabilities.TRANSPORT_VPN };
|
||||
|
||||
// In order to initialize our own ConnectivityManager, if it isn't initialized yet.
|
||||
public static Object getSystemService(Context context, String name) {
|
||||
Object result = context.getSystemService(name);
|
||||
if (CONNECTIVITY_MANAGER == null) {
|
||||
if (Context.CONNECTIVITY_SERVICE.equals(name)) {
|
||||
CONNECTIVITY_MANAGER = (ConnectivityManager) result;
|
||||
} else {
|
||||
CONNECTIVITY_MANAGER = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// In order to initialize our own ConnectivityManager, if it isn't initialized yet.
|
||||
public static Object getSystemService(Context context, Class<?> serviceClass) {
|
||||
Object result = context.getSystemService(serviceClass);
|
||||
if (CONNECTIVITY_MANAGER == null) {
|
||||
if (serviceClass == ConnectivityManager.class) {
|
||||
CONNECTIVITY_MANAGER = (ConnectivityManager) result;
|
||||
} else {
|
||||
CONNECTIVITY_MANAGER = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simply always return Wifi as active network.
|
||||
public static NetworkInfo getActiveNetworkInfo(ConnectivityManager connectivityManager) {
|
||||
for (NetworkInfo networkInfo : connectivityManager.getAllNetworkInfo()) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return networkInfo;
|
||||
}
|
||||
}
|
||||
return connectivityManager.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
// Pretend Wifi is always connected.
|
||||
public static boolean isConnected(NetworkInfo networkInfo) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return true;
|
||||
}
|
||||
return networkInfo.isConnected();
|
||||
}
|
||||
|
||||
// Pretend Wifi is always connected.
|
||||
public static boolean isConnectedOrConnecting(NetworkInfo networkInfo) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return true;
|
||||
}
|
||||
return networkInfo.isConnectedOrConnecting();
|
||||
}
|
||||
|
||||
// Pretend Wifi is always available.
|
||||
public static boolean isAvailable(NetworkInfo networkInfo) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return true;
|
||||
}
|
||||
return networkInfo.isAvailable();
|
||||
}
|
||||
|
||||
// Pretend Wifi is always connected.
|
||||
public static NetworkInfo.State getState(NetworkInfo networkInfo) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return NetworkInfo.State.CONNECTED;
|
||||
}
|
||||
return networkInfo.getState();
|
||||
}
|
||||
|
||||
// Pretend Wifi is always connected.
|
||||
public static NetworkInfo.DetailedState getDetailedState(NetworkInfo networkInfo) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return NetworkInfo.DetailedState.CONNECTED;
|
||||
}
|
||||
return networkInfo.getDetailedState();
|
||||
}
|
||||
|
||||
// Pretend Wifi is enabled, so connection isn't metered.
|
||||
public static boolean isActiveNetworkMetered(ConnectivityManager connectivityManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the Wifi network, if Wifi is enabled.
|
||||
// Otherwise if one of our fallbacks has a connection, return them.
|
||||
// And as a last resort, return the default active network.
|
||||
public static Network getActiveNetwork(ConnectivityManager connectivityManager) {
|
||||
Network[] prioritizedNetworks = new Network[FAKE_FALLBACK_NETWORKS.length];
|
||||
for (Network network : connectivityManager.getAllNetworks()) {
|
||||
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
if (networkCapabilities == null) {
|
||||
continue;
|
||||
}
|
||||
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
return network;
|
||||
}
|
||||
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
|
||||
for (int i = 0; i < FAKE_FALLBACK_NETWORKS.length; i++) {
|
||||
int transportType = FAKE_FALLBACK_NETWORKS[i];
|
||||
if (networkCapabilities.hasTransport(transportType)) {
|
||||
prioritizedNetworks[i] = network;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Network network : prioritizedNetworks) {
|
||||
if (network != null) {
|
||||
return network;
|
||||
}
|
||||
}
|
||||
return connectivityManager.getActiveNetwork();
|
||||
}
|
||||
|
||||
// If the given network is a real or fake Wifi connection, return a Wifi network.
|
||||
// Otherwise fallback to default implementation.
|
||||
public static NetworkInfo getNetworkInfo(ConnectivityManager connectivityManager, Network network) {
|
||||
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
if (networkCapabilities != null && hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
for (NetworkInfo networkInfo : connectivityManager.getAllNetworkInfo()) {
|
||||
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
|
||||
return networkInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
return connectivityManager.getNetworkInfo(network);
|
||||
}
|
||||
|
||||
// If we are checking if the NetworkCapabilities use Wifi, return yes if
|
||||
// - it is a real Wifi connection,
|
||||
// - or the NetworkCapabilities are from a network pretending being a Wifi network.
|
||||
// Otherwise fallback to default implementation.
|
||||
public static boolean hasTransport(NetworkCapabilities networkCapabilities, int transportType) {
|
||||
if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
|
||||
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
return true;
|
||||
}
|
||||
if (CONNECTIVITY_MANAGER != null) {
|
||||
Network activeNetwork = getActiveNetwork(CONNECTIVITY_MANAGER);
|
||||
NetworkCapabilities activeNetworkCapabilities = CONNECTIVITY_MANAGER.getNetworkCapabilities(activeNetwork);
|
||||
if (activeNetworkCapabilities != null) {
|
||||
for (int fallbackTransportType : FAKE_FALLBACK_NETWORKS) {
|
||||
if (activeNetworkCapabilities.hasTransport(fallbackTransportType) && networkCapabilities.hasTransport(fallbackTransportType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return networkCapabilities.hasTransport(transportType);
|
||||
}
|
||||
|
||||
// If the given network is a real or fake Wifi connection, pretend it has a connection (and some other things).
|
||||
public static boolean hasCapability(NetworkCapabilities networkCapabilities, int capability) {
|
||||
if (hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI) && (
|
||||
capability == NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_FOREGROUND
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_VPN
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_TRUSTED
|
||||
|| capability == NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
|
||||
return true;
|
||||
}
|
||||
return networkCapabilities.hasCapability(capability);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
public static void registerBestMatchingNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(handler),
|
||||
() -> connectivityManager.registerBestMatchingNetworkCallback(request, networkCallback, handler)
|
||||
);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public static void registerDefaultNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.registerDefaultNetworkCallback(networkCallback)
|
||||
);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void registerDefaultNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(handler),
|
||||
() -> connectivityManager.registerDefaultNetworkCallback(networkCallback, handler)
|
||||
);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.registerNetworkCallback(request, networkCallback)
|
||||
);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately.
|
||||
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, PendingIntent operation) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(operation),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.registerNetworkCallback(request, operation)
|
||||
);
|
||||
}
|
||||
|
||||
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(handler),
|
||||
() -> connectivityManager.registerNetworkCallback(request, networkCallback, handler)
|
||||
);
|
||||
}
|
||||
|
||||
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.requestNetwork(request, networkCallback)
|
||||
);
|
||||
}
|
||||
|
||||
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, int timeoutMs) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.requestNetwork(request, networkCallback, timeoutMs)
|
||||
);
|
||||
}
|
||||
|
||||
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(handler),
|
||||
() -> connectivityManager.requestNetwork(request, networkCallback, handler)
|
||||
);
|
||||
}
|
||||
|
||||
// If it requests Wifi connectivity, pretend it is fulfilled immediately.
|
||||
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, PendingIntent operation) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(operation),
|
||||
Utils.Option.empty(),
|
||||
() -> connectivityManager.requestNetwork(request, operation)
|
||||
);
|
||||
}
|
||||
|
||||
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler, int timeoutMs) {
|
||||
Utils.networkCallback(
|
||||
connectivityManager,
|
||||
Utils.Option.of(request),
|
||||
Utils.Option.of(networkCallback),
|
||||
Utils.Option.empty(),
|
||||
Utils.Option.of(handler),
|
||||
() -> connectivityManager.requestNetwork(request, networkCallback, handler, timeoutMs)
|
||||
);
|
||||
}
|
||||
|
||||
public static void unregisterNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback) {
|
||||
try {
|
||||
connectivityManager.unregisterNetworkCallback(networkCallback);
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
// ignore: NetworkCallback was not registered
|
||||
}
|
||||
}
|
||||
|
||||
public static void unregisterNetworkCallback(ConnectivityManager connectivityManager, PendingIntent operation) {
|
||||
try {
|
||||
connectivityManager.unregisterNetworkCallback(operation);
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
// ignore: PendingIntent was not registered
|
||||
}
|
||||
}
|
||||
|
||||
private static class Utils {
|
||||
private static class Option<T> {
|
||||
private final T value;
|
||||
private final boolean isPresent;
|
||||
|
||||
private Option(T value, boolean isPresent) {
|
||||
this.value = value;
|
||||
this.isPresent = isPresent;
|
||||
}
|
||||
|
||||
private static <T> Option<T> of(T value) {
|
||||
return new Option<>(value, true);
|
||||
}
|
||||
|
||||
private static <T> Option<T> empty() {
|
||||
return new Option<>(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void networkCallback(
|
||||
ConnectivityManager connectivityManager,
|
||||
Option<NetworkRequest> request,
|
||||
Option<ConnectivityManager.NetworkCallback> networkCallback,
|
||||
Option<PendingIntent> operation,
|
||||
Option<Handler> handler,
|
||||
Runnable fallback
|
||||
) {
|
||||
if(!request.isPresent || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && request.value != null && requestsWifiNetwork(request.value))) {
|
||||
Runnable runnable = null;
|
||||
if (networkCallback.isPresent && networkCallback.value != null) {
|
||||
Network network = activeWifiNetwork(connectivityManager);
|
||||
if (network != null) {
|
||||
runnable = () -> networkCallback.value.onAvailable(network);
|
||||
}
|
||||
} else if (operation.isPresent && operation.value != null) {
|
||||
runnable = () -> {
|
||||
try {
|
||||
operation.value.send();
|
||||
} catch (PendingIntent.CanceledException ignore) {}
|
||||
};
|
||||
}
|
||||
if (runnable != null) {
|
||||
if (handler.isPresent) {
|
||||
if (handler.value != null) {
|
||||
handler.value.post(runnable);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback.run();
|
||||
}
|
||||
|
||||
// Returns an active (maybe fake) Wifi network if there is one, otherwise null.
|
||||
private static Network activeWifiNetwork(ConnectivityManager connectivityManager) {
|
||||
Network network = getActiveNetwork(connectivityManager);
|
||||
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
if (networkCapabilities != null && hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
return network;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whether a Wifi network with connection is requested.
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private static boolean requestsWifiNetwork(NetworkRequest request) {
|
||||
return request.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
&& (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
|| request.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,336 +0,0 @@
|
||||
package app.revanced.extension.all.misc.directory.documentsprovider;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructStat;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A DocumentsProvider that allows access to the app's internal data directory.
|
||||
*/
|
||||
@SuppressLint("LongLogTag")
|
||||
public class InternalDataDocumentsProvider extends DocumentsProvider {
|
||||
private static final String[] rootColumns =
|
||||
{"root_id", "mime_types", "flags", "icon", "title", "summary", "document_id"};
|
||||
private static final String[] directoryColumns =
|
||||
{"document_id", "mime_type", "_display_name", "last_modified", "flags",
|
||||
"_size", "full_path", "lstat_info"};
|
||||
private static final int S_IFLNK = 0x8000;
|
||||
|
||||
private String packageName;
|
||||
private File dataDirectory;
|
||||
|
||||
/**
|
||||
* Recursively delete a file or directory and all its children.
|
||||
*
|
||||
* @param root The file or directory to delete.
|
||||
* @return True if the file or directory and all its children were successfully deleted.
|
||||
*/
|
||||
private static boolean deleteRecursively(File root) {
|
||||
// If root is a directory, delete all children first
|
||||
if (root.isDirectory()) {
|
||||
try {
|
||||
// Only delete recursively if the directory is not a symlink
|
||||
if ((Os.lstat(root.getPath()).st_mode & S_IFLNK) != S_IFLNK) {
|
||||
File[] files = root.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (!deleteRecursively(file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ErrnoException e) {
|
||||
Log.e("InternalDocumentsProvider", "Failed to lstat " + root.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete file or empty directory
|
||||
return root.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the MIME type of a file based on its extension.
|
||||
*
|
||||
* @param file The file to resolve the MIME type for.
|
||||
* @return The MIME type of the file.
|
||||
*/
|
||||
private static String resolveMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return DocumentsContract.Document.MIME_TYPE_DIR;
|
||||
}
|
||||
|
||||
String name = file.getName();
|
||||
int indexOfExtDot = name.lastIndexOf('.');
|
||||
if (indexOfExtDot < 0) {
|
||||
// No extension
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
String extension = name.substring(indexOfExtDot + 1).toLowerCase();
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
return mimeType != null ? mimeType : "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void attachInfo(Context context, ProviderInfo providerInfo) {
|
||||
super.attachInfo(context, providerInfo);
|
||||
|
||||
this.packageName = context.getPackageName();
|
||||
this.dataDirectory = context.getFilesDir().getParentFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
||||
File directory = resolveDocumentId(parentDocumentId);
|
||||
File file = new File(directory, displayName);
|
||||
|
||||
// If file already exists, append a number to the name
|
||||
int i = 2;
|
||||
while (file.exists()) {
|
||||
file = new File(directory, displayName + " (" + i + ")");
|
||||
i++;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create the file or directory
|
||||
if (mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR) ? file.mkdir() : file.createNewFile()) {
|
||||
// Return the document ID of the new entity
|
||||
if (!parentDocumentId.endsWith("/")) {
|
||||
parentDocumentId = parentDocumentId + "/";
|
||||
}
|
||||
return parentDocumentId + file.getName();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Do nothing. We are throwing a FileNotFoundException later if the file could not be created.
|
||||
}
|
||||
throw new FileNotFoundException("Failed to create document in " + parentDocumentId + " with name " + displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = resolveDocumentId(documentId);
|
||||
if (!deleteRecursively(file)) {
|
||||
throw new FileNotFoundException("Failed to delete document " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
return resolveMimeType(resolveDocumentId(documentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException {
|
||||
File source = resolveDocumentId(sourceDocumentId);
|
||||
File dest = resolveDocumentId(targetParentDocumentId);
|
||||
|
||||
File file = new File(dest, source.getName());
|
||||
if (!file.exists() && source.renameTo(file)) {
|
||||
// Return the new document ID
|
||||
if (targetParentDocumentId.endsWith("/")) {
|
||||
return targetParentDocumentId + file.getName();
|
||||
}
|
||||
return targetParentDocumentId + "/" + file.getName();
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("Failed to move document from " + sourceDocumentId + " to " + targetParentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||
File file = resolveDocumentId(documentId);
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
if (parentDocumentId.endsWith("/")) {
|
||||
parentDocumentId = parentDocumentId.substring(0, parentDocumentId.length() - 1);
|
||||
}
|
||||
|
||||
if (projection == null) {
|
||||
projection = directoryColumns;
|
||||
}
|
||||
|
||||
MatrixCursor cursor = new MatrixCursor(projection);
|
||||
File children = resolveDocumentId(parentDocumentId);
|
||||
|
||||
// Collect all children
|
||||
File[] files = children.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
addRowForDocument(cursor, parentDocumentId + "/" + file.getName(), file);
|
||||
}
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
if (projection == null) {
|
||||
projection = directoryColumns;
|
||||
}
|
||||
|
||||
MatrixCursor cursor = new MatrixCursor(projection);
|
||||
addRowForDocument(cursor, documentId, null);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor queryRoots(String[] projection) {
|
||||
ApplicationInfo info = Objects.requireNonNull(getContext()).getApplicationInfo();
|
||||
String appName = info.loadLabel(getContext().getPackageManager()).toString();
|
||||
|
||||
if (projection == null) {
|
||||
projection = rootColumns;
|
||||
}
|
||||
|
||||
MatrixCursor cursor = new MatrixCursor(projection);
|
||||
MatrixCursor.RowBuilder row = cursor.newRow();
|
||||
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, this.packageName);
|
||||
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, this.packageName);
|
||||
row.add(DocumentsContract.Root.COLUMN_SUMMARY, this.packageName);
|
||||
row.add(DocumentsContract.Root.COLUMN_FLAGS,
|
||||
DocumentsContract.Root.FLAG_LOCAL_ONLY |
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(DocumentsContract.Root.COLUMN_TITLE, appName);
|
||||
row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(DocumentsContract.Root.COLUMN_ICON, info.icon);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
|
||||
deleteDocument(documentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String renameDocument(String documentId, String displayName) throws FileNotFoundException {
|
||||
File file = resolveDocumentId(documentId);
|
||||
if (!file.renameTo(new File(file.getParentFile(), displayName))) {
|
||||
throw new FileNotFoundException("Failed to rename document from " + documentId + " to " + displayName);
|
||||
}
|
||||
|
||||
// Return the new document ID
|
||||
return documentId.substring(0, documentId.lastIndexOf('/', documentId.length() - 2)) + "/" + displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a file instance for a given document ID.
|
||||
*
|
||||
* @param fullContentPath The document ID to resolve.
|
||||
* @return File object for the given document ID.
|
||||
* @throws FileNotFoundException If the document ID is invalid or the file does not exist.
|
||||
*/
|
||||
private File resolveDocumentId(String fullContentPath) throws FileNotFoundException {
|
||||
if (!fullContentPath.startsWith(this.packageName)) {
|
||||
throw new FileNotFoundException(fullContentPath + " not found");
|
||||
}
|
||||
String path = fullContentPath.substring(this.packageName.length());
|
||||
|
||||
// Resolve the relative path within /data/data/{PKG}
|
||||
File file;
|
||||
if (path.equals("/") || path.isEmpty()) {
|
||||
file = this.dataDirectory;
|
||||
} else {
|
||||
// Remove leading slash
|
||||
String relativePath = path.substring(1);
|
||||
file = new File(this.dataDirectory, relativePath);
|
||||
}
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(fullContentPath + " not found");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row containing all file properties to a MatrixCursor for a given document ID.
|
||||
*
|
||||
* @param cursor The cursor to add the row to.
|
||||
* @param documentId The document ID to add the row for.
|
||||
* @param file The file to add the row for. If null, the file will be resolved from the document ID.
|
||||
* @throws FileNotFoundException If the file does not exist.
|
||||
*/
|
||||
private void addRowForDocument(MatrixCursor cursor, String documentId, File file) throws FileNotFoundException {
|
||||
if (file == null) {
|
||||
file = resolveDocumentId(documentId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
// Prefer list view for directories
|
||||
flags = flags | DocumentsContract.Document.FLAG_DIR_PREFERS_LAST_MODIFIED;
|
||||
}
|
||||
|
||||
if (file.canWrite()) {
|
||||
if (file.isDirectory()) {
|
||||
flags = flags | DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
|
||||
flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
|
||||
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
|
||||
DocumentsContract.Document.FLAG_SUPPORTS_RENAME |
|
||||
DocumentsContract.Document.FLAG_SUPPORTS_MOVE;
|
||||
}
|
||||
|
||||
MatrixCursor.RowBuilder row = cursor.newRow();
|
||||
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, documentId);
|
||||
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
row.add(DocumentsContract.Document.COLUMN_SIZE, file.length());
|
||||
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, resolveMimeType(file));
|
||||
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(DocumentsContract.Document.COLUMN_FLAGS, flags);
|
||||
|
||||
// Custom columns
|
||||
row.add("full_path", file.getAbsolutePath());
|
||||
|
||||
// Add lstat column
|
||||
String path = file.getPath();
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StructStat lstat = Os.lstat(path);
|
||||
sb.append(lstat.st_mode);
|
||||
sb.append(";");
|
||||
sb.append(lstat.st_uid);
|
||||
sb.append(";");
|
||||
sb.append(lstat.st_gid);
|
||||
// Append symlink target if it is a symlink
|
||||
if ((lstat.st_mode & S_IFLNK) == S_IFLNK) {
|
||||
sb.append(";");
|
||||
sb.append(Os.readlink(path));
|
||||
}
|
||||
row.add("lstat_info", sb.toString());
|
||||
} catch (Exception ex) {
|
||||
Log.e("InternalDocumentsProvider", "Failed to get lstat info for " + path, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,22 +0,0 @@
|
||||
package app.revanced.extension.all.misc.screencapture.removerestriction;
|
||||
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class RemoveScreenCaptureRestrictionPatch {
|
||||
// Member of AudioAttributes.Builder
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
public static AudioAttributes.Builder setAllowedCapturePolicy(final AudioAttributes.Builder builder, final int capturePolicy) {
|
||||
builder.setAllowedCapturePolicy(AudioAttributes.ALLOW_CAPTURE_BY_ALL);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
// Member of AudioManager static class
|
||||
public static void setAllowedCapturePolicy(final int capturePolicy) {
|
||||
// Ignore request
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,16 +0,0 @@
|
||||
package app.revanced.extension.all.misc.screenshot.removerestriction;
|
||||
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RemoveScreenshotRestrictionPatch {
|
||||
|
||||
public static void addFlags(Window window, int flags) {
|
||||
window.addFlags(flags & ~WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
public static void setFlags(Window window, int flags, int mask) {
|
||||
window.setFlags(flags & ~WindowManager.LayoutParams.FLAG_SECURE, mask & ~WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:boostforreddit:stub"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,26 +0,0 @@
|
||||
package app.revanced.extension.boostforreddit;
|
||||
|
||||
import com.rubenmayayo.reddit.ui.activities.WebViewActivity;
|
||||
|
||||
import app.revanced.extension.shared.fixes.slink.BaseFixSLinksPatch;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class FixSLinksPatch extends BaseFixSLinksPatch {
|
||||
static {
|
||||
INSTANCE = new FixSLinksPatch();
|
||||
}
|
||||
|
||||
private FixSLinksPatch() {
|
||||
webViewActivityClass = WebViewActivity.class;
|
||||
}
|
||||
|
||||
public static boolean patchResolveSLink(String link) {
|
||||
return INSTANCE.resolveSLink(link);
|
||||
}
|
||||
|
||||
public static void patchSetAccessToken(String accessToken) {
|
||||
INSTANCE.setAccessToken(accessToken);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.rubenmayayo.reddit.ui.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class WebViewActivity extends Activity {
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,25 +0,0 @@
|
||||
package app.revanced.extension.messenger.metaai;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RemoveMetaAIPatch {
|
||||
private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public static boolean overrideBooleanFlag(long id, boolean value) {
|
||||
try {
|
||||
if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
|
||||
if (loggedIDs.add(id))
|
||||
Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
|
||||
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "overrideBooleanFlag failure", ex);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,27 +0,0 @@
|
||||
package app.revanced.extension.music.spoof;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class SpoofClientPatch {
|
||||
private static final int CLIENT_TYPE_ID = 26;
|
||||
private static final String CLIENT_VERSION = "6.21";
|
||||
private static final String DEVICE_MODEL = "iPhone16,2";
|
||||
private static final String OS_VERSION = "17.7.2.21H221";
|
||||
|
||||
public static int getClientId() {
|
||||
return CLIENT_TYPE_ID;
|
||||
}
|
||||
|
||||
public static String getClientVersion() {
|
||||
return CLIENT_VERSION;
|
||||
}
|
||||
|
||||
public static String getClientModel() {
|
||||
return DEVICE_MODEL;
|
||||
}
|
||||
|
||||
public static String getOsVersion() {
|
||||
return OS_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:nunl:stub"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,114 +0,0 @@
|
||||
package app.revanced.extension.nunl.ads;
|
||||
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
import nl.nu.performance.api.client.unions.SmallArticleLinkFlavor;
|
||||
import nl.nu.performance.api.client.objects.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideAdsPatch {
|
||||
private static final String[] blockedHeaderBlocks = {
|
||||
"Aanbiedingen (Adverteerders)",
|
||||
"Aangeboden door NUshop"
|
||||
};
|
||||
|
||||
// "Rubrieken" menu links to ads.
|
||||
private static final String[] blockedLinkBlocks = {
|
||||
"Van onze adverteerders"
|
||||
};
|
||||
|
||||
public static void filterAds(List<Block> blocks) {
|
||||
try {
|
||||
ArrayList<Block> cleanedList = new ArrayList<>();
|
||||
|
||||
boolean skipFullHeader = false;
|
||||
boolean skipUntilDivider = false;
|
||||
|
||||
int index = 0;
|
||||
while (index < blocks.size()) {
|
||||
Block currentBlock = blocks.get(index);
|
||||
|
||||
// Because of pagination, we might not see the Divider in front of it.
|
||||
// Just remove it as is and leave potential extra spacing visible on the screen.
|
||||
if (currentBlock instanceof DpgBannerBlock) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index + 1 < blocks.size()) {
|
||||
// Filter Divider -> DpgMediaBanner -> Divider.
|
||||
if (currentBlock instanceof DividerBlock
|
||||
&& blocks.get(index + 1) instanceof DpgBannerBlock) {
|
||||
index += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter Divider -> LinkBlock (... -> LinkBlock -> LinkBlock-> LinkBlock -> Divider).
|
||||
if (currentBlock instanceof DividerBlock
|
||||
&& blocks.get(index + 1) instanceof LinkBlock linkBlock) {
|
||||
Link link = linkBlock.getLink();
|
||||
if (link != null && link.getTitle() != null) {
|
||||
for (String blockedLinkBlock : blockedLinkBlocks) {
|
||||
if (blockedLinkBlock.equals(link.getTitle().getText())) {
|
||||
skipUntilDivider = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipUntilDivider) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip LinkBlocks with a "flavor" claiming to be "isPartner" (sponsored inline ads).
|
||||
if (currentBlock instanceof LinkBlock linkBlock
|
||||
&& linkBlock.getLink() != null
|
||||
&& linkBlock.getLink().getLinkFlavor() instanceof SmallArticleLinkFlavor smallArticleLinkFlavor
|
||||
&& smallArticleLinkFlavor.isPartner() != null
|
||||
&& smallArticleLinkFlavor.isPartner()) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentBlock instanceof DividerBlock) {
|
||||
skipUntilDivider = false;
|
||||
}
|
||||
|
||||
// Filter HeaderBlock with known ads until next HeaderBlock.
|
||||
if (currentBlock instanceof HeaderBlock headerBlock) {
|
||||
StyledText headerText = headerBlock.getTitle();
|
||||
if (headerText != null) {
|
||||
skipFullHeader = false;
|
||||
for (String blockedHeaderBlock : blockedHeaderBlocks) {
|
||||
if (blockedHeaderBlock.equals(headerText.getText())) {
|
||||
skipFullHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipFullHeader) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipFullHeader && !skipUntilDivider) {
|
||||
cleanedList.add(currentBlock);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// Replace list in-place to not deal with moving the result to the correct register in smali.
|
||||
blocks.clear();
|
||||
blocks.addAll(cleanedList);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "filterAds failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,5 +0,0 @@
|
||||
package nl.nu.performance.api.client.interfaces;
|
||||
|
||||
public class Block {
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
|
||||
public class DividerBlock extends Block {
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
|
||||
public class DpgBannerBlock extends Block {
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
|
||||
public class HeaderBlock extends Block {
|
||||
public final StyledText getTitle() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
import nl.nu.performance.api.client.unions.LinkFlavor;
|
||||
|
||||
public class Link {
|
||||
public final StyledText getTitle() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
|
||||
public final LinkFlavor getLinkFlavor() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
|
||||
public abstract class LinkBlock extends Block implements Parcelable {
|
||||
public final Link getLink() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package nl.nu.performance.api.client.objects;
|
||||
|
||||
public class StyledText {
|
||||
public final String getText() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package nl.nu.performance.api.client.unions;
|
||||
|
||||
public interface LinkFlavor {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package nl.nu.performance.api.client.unions;
|
||||
|
||||
public class SmallArticleLinkFlavor implements LinkFlavor {
|
||||
public final Boolean isPartner() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:primevideo:stub"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,36 +0,0 @@
|
||||
package app.revanced.extension.primevideo.ads;
|
||||
|
||||
import com.amazon.avod.fsm.SimpleTrigger;
|
||||
import com.amazon.avod.media.ads.AdBreak;
|
||||
import com.amazon.avod.media.ads.internal.state.AdBreakTrigger;
|
||||
import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType;
|
||||
import com.amazon.avod.media.playback.VideoPlayer;
|
||||
import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class SkipAdsPatch {
|
||||
public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) {
|
||||
try {
|
||||
AdBreak adBreak = trigger.getBreak();
|
||||
|
||||
// There are two scenarios when entering the original method:
|
||||
// 1. Player naturally entered an ad break while watching a video.
|
||||
// 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break,
|
||||
// user is forced to watch an ad before continuing.
|
||||
//
|
||||
// Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing
|
||||
// target. Otherwise, just calculate when the ad break should end and skip to there.
|
||||
if (trigger.getSeekStartPosition() != null)
|
||||
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds());
|
||||
else
|
||||
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds());
|
||||
|
||||
// Send "end of ads" trigger to state machine so everything doesn't get whacky.
|
||||
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed skipping ads", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public final class SimpleTrigger<T> implements Trigger<T> {
|
||||
public SimpleTrigger(T triggerType) {
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public abstract class StateBase<S, T> {
|
||||
// This method orginally has protected access (modified in patch code).
|
||||
public void doTrigger(Trigger<T> trigger) {
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public interface Trigger<T> {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.amazon.avod.media;
|
||||
|
||||
public final class TimeSpan {
|
||||
public long getTotalMilliseconds() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.amazon.avod.media.ads;
|
||||
|
||||
import com.amazon.avod.media.TimeSpan;
|
||||
|
||||
public interface AdBreak {
|
||||
TimeSpan getDurationExcludingAux();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public abstract class AdBreakState extends AdEnabledPlaybackState {
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
import com.amazon.avod.media.ads.AdBreak;
|
||||
import com.amazon.avod.media.TimeSpan;
|
||||
|
||||
public class AdBreakTrigger {
|
||||
public AdBreak getBreak() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public TimeSpan getSeekTarget() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public TimeSpan getSeekStartPosition() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
import com.amazon.avod.fsm.StateBase;
|
||||
import com.amazon.avod.media.playback.state.PlayerStateType;
|
||||
import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType;
|
||||
|
||||
public class AdEnabledPlaybackState extends StateBase<PlayerStateType, PlayerTriggerType> {
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public enum AdEnabledPlayerTriggerType {
|
||||
NO_MORE_ADS_SKIP_TRANSITION
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public class ServerInsertedAdBreakState extends AdBreakState {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.amazon.avod.media.playback;
|
||||
|
||||
public interface VideoPlayer {
|
||||
long getCurrentPosition();
|
||||
|
||||
void seekTo(long positionMs);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.amazon.avod.media.playback.state;
|
||||
|
||||
public interface PlayerStateType {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.amazon.avod.media.playback.state.trigger;
|
||||
|
||||
public interface PlayerTriggerType {
|
||||
}
|
||||
9
extensions/proguard-rules.pro
vendored
9
extensions/proguard-rules.pro
vendored
@@ -1,9 +0,0 @@
|
||||
-dontobfuscate
|
||||
-dontoptimize
|
||||
-keepattributes *
|
||||
-keep class app.revanced.** {
|
||||
*;
|
||||
}
|
||||
-keep class com.google.** {
|
||||
*;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:reddit:stub"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,27 +0,0 @@
|
||||
package app.revanced.extension.reddit.patches;
|
||||
|
||||
import com.reddit.domain.model.ILink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class FilterPromotedLinksPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Filters list from promoted links.
|
||||
**/
|
||||
public static List<?> filterChildren(final Iterable<?> links) {
|
||||
final List<Object> filteredList = new ArrayList<>();
|
||||
|
||||
for (Object item : links) {
|
||||
if (item instanceof ILink && ((ILink) item).getPromoted()) continue;
|
||||
|
||||
filteredList.add(item);
|
||||
}
|
||||
|
||||
return filteredList;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.reddit.domain.model;
|
||||
|
||||
public class ILink {
|
||||
public boolean getPromoted() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
dependencies {
|
||||
implementation(project(":extensions:shared:library"))
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Pair;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class GmsCoreSupport {
|
||||
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
|
||||
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
|
||||
|
||||
private static final String GMS_CORE_PACKAGE_NAME
|
||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||
private static final Uri GMS_CORE_PROVIDER
|
||||
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
||||
private static final String DONT_KILL_MY_APP_URL
|
||||
= "https://dontkillmyapp.com/";
|
||||
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
|
||||
= new Route(GET, "/api/v2/{manufacturer}.json");
|
||||
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
|
||||
= "?app=MicroG";
|
||||
private static final String BUILD_MANUFACTURER
|
||||
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
|
||||
|
||||
/**
|
||||
* If a manufacturer specific page exists on DontKillMyApp.
|
||||
*/
|
||||
@Nullable
|
||||
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
||||
|
||||
private static void open(String queryOrLink) {
|
||||
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||
|
||||
Intent intent;
|
||||
try {
|
||||
// Check if queryOrLink is a valid URL.
|
||||
new URL(queryOrLink);
|
||||
|
||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
|
||||
} catch (MalformedURLException e) {
|
||||
intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
intent.putExtra(SearchManager.QUERY, queryOrLink);
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
Utils.getContext().startActivity(intent);
|
||||
|
||||
// Gracefully exit, otherwise the broken app will continue to run.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static void showBatteryOptimizationDialog(Activity context,
|
||||
String dialogMessageRef,
|
||||
String positiveButtonTextRef,
|
||||
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||
// Use a delay to allow the activity to finish initializing.
|
||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
context,
|
||||
str("gms_core_dialog_title"), // Title.
|
||||
str(dialogMessageRef), // Message.
|
||||
null, // No EditText.
|
||||
str(positiveButtonTextRef), // OK button text.
|
||||
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
|
||||
null, // No Cancel button action.
|
||||
null, // No Neutral button text.
|
||||
null, // No Neutral button action.
|
||||
true // Dismiss dialog when onNeutralClick.
|
||||
);
|
||||
|
||||
Dialog dialog = dialogPair.first;
|
||||
|
||||
// Do not set cancelable to false, to allow using back button to skip the action,
|
||||
// just in case the battery change can never be satisfied.
|
||||
dialog.setCancelable(true);
|
||||
|
||||
// Show the dialog
|
||||
Utils.showDialog(context, dialog);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void checkGmsCore(Activity context) {
|
||||
try {
|
||||
// Verify the user has not included GmsCore for a root installation.
|
||||
// GmsCore Support changes the package name, but with a mounted installation
|
||||
// all manifest changes are ignored and the original package name is used.
|
||||
String packageName = context.getPackageName();
|
||||
if (packageName.equals(PACKAGE_NAME_YOUTUBE) || packageName.equals(PACKAGE_NAME_YOUTUBE_MUSIC)) {
|
||||
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
||||
// Cannot use localize text here, since the app will load
|
||||
// resources from the unpatched app and all patch strings are missing.
|
||||
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
|
||||
|
||||
// Do not exit. If the app exits before launch completes (and without
|
||||
// opening another activity), then on some devices such as Pixel phone Android 10
|
||||
// no toast will be shown and the app will continually relaunch
|
||||
// with the appearance of a hung app.
|
||||
}
|
||||
|
||||
// Verify GmsCore is installed.
|
||||
try {
|
||||
PackageManager manager = context.getPackageManager();
|
||||
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
} catch (PackageManager.NameNotFoundException exception) {
|
||||
Logger.printInfo(() -> "GmsCore was not found");
|
||||
// Cannot show a dialog and must show a toast,
|
||||
// because on some installations the app crashes before a dialog can be displayed.
|
||||
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
|
||||
open(getGmsCoreDownload());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if GmsCore is whitelisted from battery optimizations.
|
||||
if (isAndroidAutomotive(context)) {
|
||||
// Ignore Android Automotive devices (Google built-in),
|
||||
// as there is no way to disable battery optimizations.
|
||||
Logger.printDebug(() -> "Device is Android Automotive");
|
||||
} else if (batteryOptimizationsEnabled(context)) {
|
||||
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
||||
|
||||
showBatteryOptimizationDialog(context,
|
||||
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
||||
"gms_core_dialog_continue_text",
|
||||
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if GmsCore is currently running in the background.
|
||||
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
if (client == null) {
|
||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||
checkIfDontKillMyAppSupportsManufacturer();
|
||||
|
||||
showBatteryOptimizationDialog(context,
|
||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||
"gms_core_dialog_open_website_text",
|
||||
(dialog, id) -> openDontKillMyApp());
|
||||
}
|
||||
} finally {
|
||||
if (client != null) client.close();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
||||
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
|
||||
activity.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private static void checkIfDontKillMyAppSupportsManufacturer() {
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
final long start = System.currentTimeMillis();
|
||||
HttpURLConnection connection = Requester.getConnectionFromRoute(
|
||||
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
|
||||
final boolean supported = connection.getResponseCode() == 200;
|
||||
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
|
||||
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
|
||||
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
|
||||
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
|
||||
} catch (Exception ex) {
|
||||
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
|
||||
+ BUILD_MANUFACTURER, ex);
|
||||
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void openDontKillMyApp() {
|
||||
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
||||
|
||||
String manufacturerPageToOpen;
|
||||
if (manufacturerSupported == null) {
|
||||
// Fetch has not completed yet. Only happens on extremely slow internet connections
|
||||
// and the user spends less than 1 second reading what's on screen.
|
||||
// Instead of waiting for the fetch (which may timeout),
|
||||
// open the website without a vendor.
|
||||
manufacturerPageToOpen = "";
|
||||
} else if (manufacturerSupported) {
|
||||
manufacturerPageToOpen = BUILD_MANUFACTURER;
|
||||
} else {
|
||||
// No manufacturer specific page exists. Open the general page.
|
||||
manufacturerPageToOpen = "general";
|
||||
}
|
||||
|
||||
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||
*/
|
||||
private static boolean batteryOptimizationsEnabled(Context context) {
|
||||
//noinspection ObsoleteSdkInt
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
// Android 5.0 does not have battery optimization settings.
|
||||
return false;
|
||||
}
|
||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
private static boolean isAndroidAutomotive(Context context) {
|
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||
}
|
||||
|
||||
private static String getGmsCoreDownload() {
|
||||
final var vendorGroupId = getGmsCoreVendorGroupId();
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
return switch (vendorGroupId) {
|
||||
case "app.revanced" -> "https://github.com/revanced/gmscore/releases/latest";
|
||||
default -> vendorGroupId + ".android.gms";
|
||||
};
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
private static String getGmsCoreVendorGroupId() {
|
||||
return "app.revanced";
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG;
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE;
|
||||
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
||||
|
||||
/**
|
||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||
* and additionally accessible thru {@link LogBufferManager}.
|
||||
*
|
||||
* All methods are thread safe, and are safe to call even
|
||||
* if {@link Utils#getContext()} is not available.
|
||||
*/
|
||||
public class Logger {
|
||||
|
||||
/**
|
||||
* Log messages using lambdas.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LogMessage {
|
||||
/**
|
||||
* @return Logger string message. This method is only called if logging is enabled.
|
||||
*/
|
||||
@NonNull
|
||||
String buildMessageString();
|
||||
}
|
||||
|
||||
private enum LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Log tag prefix. Only used for system logging.
|
||||
*/
|
||||
private static final String REVANCED_LOG_TAG_PREFIX = "revanced: ";
|
||||
|
||||
private static final String LOGGER_CLASS_NAME = Logger.class.getName();
|
||||
|
||||
/**
|
||||
* @return For outer classes, this returns {@link Class#getSimpleName()}.
|
||||
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
|
||||
* <br>
|
||||
* For example, each of these classes returns 'SomethingView':
|
||||
* <code>
|
||||
* com.company.SomethingView
|
||||
* com.company.SomethingView$StaticClass
|
||||
* com.company.SomethingView$1
|
||||
* </code>
|
||||
*/
|
||||
private static String getOuterClassSimpleName(Object obj) {
|
||||
Class<?> logClass = obj.getClass();
|
||||
String fullClassName = logClass.getName();
|
||||
final int dollarSignIndex = fullClassName.indexOf('$');
|
||||
if (dollarSignIndex < 0) {
|
||||
return logClass.getSimpleName(); // Already an outer class.
|
||||
}
|
||||
|
||||
// Class is inner, static, or anonymous.
|
||||
// Parse the simple name full name.
|
||||
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
|
||||
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
|
||||
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to handle logging to Android Log and {@link LogBufferManager}.
|
||||
* Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer
|
||||
* with class name but without 'revanced:' prefix.
|
||||
*
|
||||
* @param logLevel The log level.
|
||||
* @param message Log message object.
|
||||
* @param ex Optional exception.
|
||||
* @param includeStackTrace If the current stack should be included.
|
||||
* @param showToast If a toast is to be shown.
|
||||
*/
|
||||
private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex,
|
||||
boolean includeStackTrace, boolean showToast) {
|
||||
// It's very important that no Settings are used in this method,
|
||||
// as this code is used when a context is not set and thus referencing
|
||||
// a setting will crash the app.
|
||||
String messageString = message.buildMessageString();
|
||||
String className = getOuterClassSimpleName(message);
|
||||
|
||||
String logText = messageString;
|
||||
|
||||
// Append exception message if present.
|
||||
if (ex != null) {
|
||||
var exceptionMessage = ex.getMessage();
|
||||
if (exceptionMessage != null) {
|
||||
logText += "\nException: " + exceptionMessage;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeStackTrace) {
|
||||
var sw = new StringWriter();
|
||||
new Throwable().printStackTrace(new PrintWriter(sw));
|
||||
String stackTrace = sw.toString();
|
||||
// Remove the stacktrace elements of this class.
|
||||
final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME);
|
||||
final int loggerBegins = stackTrace.indexOf('\n', loggerIndex);
|
||||
logText += stackTrace.substring(loggerBegins);
|
||||
}
|
||||
|
||||
// Do not include "revanced:" prefix in clipboard logs.
|
||||
String managerToastString = className + ": " + logText;
|
||||
LogBufferManager.appendToLogBuffer(managerToastString);
|
||||
|
||||
String logTag = REVANCED_LOG_TAG_PREFIX + className;
|
||||
switch (logLevel) {
|
||||
case DEBUG:
|
||||
if (ex == null) Log.d(logTag, logText);
|
||||
else Log.d(logTag, logText, ex);
|
||||
break;
|
||||
case INFO:
|
||||
if (ex == null) Log.i(logTag, logText);
|
||||
else Log.i(logTag, logText, ex);
|
||||
break;
|
||||
case ERROR:
|
||||
if (ex == null) Log.e(logTag, logText);
|
||||
else Log.e(logTag, logText, ex);
|
||||
break;
|
||||
}
|
||||
|
||||
if (showToast) {
|
||||
Utils.showToastLong(managerToastString);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldLogDebug() {
|
||||
// If the app is still starting up and the context is not yet set,
|
||||
// then allow debug logging regardless what the debug setting actually is.
|
||||
return Utils.context == null || DEBUG.get();
|
||||
}
|
||||
|
||||
private static boolean shouldShowErrorToast() {
|
||||
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
|
||||
}
|
||||
|
||||
private static boolean includeStackTrace() {
|
||||
return Utils.context != null && DEBUG_STACKTRACE.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug messages under the outer class name of the code calling this method.
|
||||
* <p>
|
||||
* Whenever possible, the log string should be constructed entirely inside
|
||||
* {@link LogMessage#buildMessageString()} so the performance cost of
|
||||
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||
*/
|
||||
public static void printDebug(LogMessage message) {
|
||||
printDebug(message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug messages under the outer class name of the code calling this method.
|
||||
* <p>
|
||||
* Whenever possible, the log string should be constructed entirely inside
|
||||
* {@link LogMessage#buildMessageString()} so the performance cost of
|
||||
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||
*/
|
||||
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
||||
if (shouldLogDebug()) {
|
||||
logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs information messages using the outer class name of the code calling this method.
|
||||
*/
|
||||
public static void printInfo(LogMessage message) {
|
||||
printInfo(message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs information messages using the outer class name of the code calling this method.
|
||||
*/
|
||||
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
||||
logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs exceptions under the outer class name of the code calling this method.
|
||||
* Appends the log message, exception (if present), and toast message (if enabled) to logBuffer.
|
||||
*/
|
||||
public static void printException(LogMessage message) {
|
||||
printException(message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs exceptions under the outer class name of the code calling this method.
|
||||
* <p>
|
||||
* If the calling code is showing it's own error toast,
|
||||
* instead use {@link #printInfo(LogMessage, Exception)}
|
||||
*
|
||||
* @param message log message
|
||||
* @param ex exception (optional)
|
||||
*/
|
||||
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
||||
logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StringRef {
|
||||
private static Resources resources;
|
||||
private static String packageName;
|
||||
|
||||
// must use a thread safe map, as this class is used both on and off the main thread
|
||||
private static final Map<String, StringRef> strings = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/**
|
||||
* Returns a cached instance.
|
||||
* Should be used if the same String could be loaded more than once.
|
||||
*
|
||||
* @param id string resource name/id
|
||||
* @see #sf(String)
|
||||
*/
|
||||
@NonNull
|
||||
public static StringRef sfc(@NonNull String id) {
|
||||
StringRef ref = strings.get(id);
|
||||
if (ref == null) {
|
||||
ref = new StringRef(id);
|
||||
strings.put(id, ref);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance, but does not cache the value.
|
||||
* Should be used for Strings that are loaded exactly once.
|
||||
*
|
||||
* @param id string resource name/id
|
||||
* @see #sfc(String)
|
||||
*/
|
||||
@NonNull
|
||||
public static StringRef sf(@NonNull String id) {
|
||||
return new StringRef(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets string value by string id, shorthand for <code>sfc(id).toString()</code>
|
||||
*
|
||||
* @param id string resource name/id
|
||||
* @return String value from string.xml
|
||||
*/
|
||||
@NonNull
|
||||
public static String str(@NonNull String id) {
|
||||
return sfc(id).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets string value by string id, shorthand for <code>sfc(id).toString()</code> and formats the string
|
||||
* with given args.
|
||||
*
|
||||
* @param id string resource name/id
|
||||
* @param args the args to format the string with
|
||||
* @return String value from string.xml formatted with given args
|
||||
*/
|
||||
@NonNull
|
||||
public static String str(@NonNull String id, Object... args) {
|
||||
return String.format(str(id), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a StringRef object that'll not change it's value
|
||||
*
|
||||
* @param value value which toString() method returns when invoked on returned object
|
||||
* @return Unique StringRef instance, its value will never change
|
||||
*/
|
||||
@NonNull
|
||||
public static StringRef constant(@NonNull String value) {
|
||||
final StringRef ref = new StringRef(value);
|
||||
ref.resolved = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for <code>constant("")</code>
|
||||
* Its value always resolves to empty string
|
||||
*/
|
||||
@NonNull
|
||||
public static final StringRef empty = constant("");
|
||||
|
||||
@NonNull
|
||||
private String value;
|
||||
private boolean resolved;
|
||||
|
||||
public StringRef(@NonNull String resName) {
|
||||
this.value = resName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
if (!resolved) {
|
||||
if (resources == null || packageName == null) {
|
||||
Context context = Utils.getContext();
|
||||
resources = context.getResources();
|
||||
packageName = context.getPackageName();
|
||||
}
|
||||
resolved = true;
|
||||
if (resources != null) {
|
||||
final int identifier = resources.getIdentifier(value, "string", packageName);
|
||||
if (identifier == 0)
|
||||
Logger.printException(() -> "Resource not found: " + value);
|
||||
else
|
||||
value = resources.getString(identifier);
|
||||
} else {
|
||||
Logger.printException(() -> "Could not resolve resources!");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,212 +0,0 @@
|
||||
package app.revanced.extension.shared.checks;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.text.Html;
|
||||
import android.util.Pair;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
abstract class Check {
|
||||
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
|
||||
|
||||
private static final int SECONDS_BEFORE_SHOWING_IGNORE_BUTTON = 15;
|
||||
private static final int SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON = 10;
|
||||
|
||||
private static final Uri GOOD_SOURCE = Uri.parse("https://revanced.app");
|
||||
|
||||
/**
|
||||
* @return If the check conclusively passed or failed. A null value indicates it neither passed nor failed.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Boolean check();
|
||||
|
||||
protected abstract String failureReason();
|
||||
|
||||
/**
|
||||
* Specifies a sorting order for displaying the checks that failed.
|
||||
* A lower value indicates to show first before other checks.
|
||||
*/
|
||||
public abstract int uiSortingValue();
|
||||
|
||||
/**
|
||||
* For debugging and development only.
|
||||
* Forces all checks to be performed and the check failed dialog to be shown.
|
||||
* Can be enabled by importing settings text with {@link BaseSettings#CHECK_ENVIRONMENT_WARNINGS_ISSUED}
|
||||
* set to -1.
|
||||
*/
|
||||
static boolean debugAlwaysShowWarning() {
|
||||
final boolean alwaysShowWarning = BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get() < 0;
|
||||
if (alwaysShowWarning) {
|
||||
Logger.printInfo(() -> "Debug forcing environment check warning to show");
|
||||
}
|
||||
|
||||
return alwaysShowWarning;
|
||||
}
|
||||
|
||||
static boolean shouldRun() {
|
||||
return BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()
|
||||
< NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING;
|
||||
}
|
||||
|
||||
static void disableForever() {
|
||||
Logger.printInfo(() -> "Environment checks disabled forever");
|
||||
|
||||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
static void issueWarning(Activity activity, Collection<Check> failedChecks) {
|
||||
final var reasons = new StringBuilder();
|
||||
|
||||
reasons.append("<ul>");
|
||||
for (var check : failedChecks) {
|
||||
// Add a non breaking space to fix bullet points spacing issue.
|
||||
reasons.append("<li> ").append(check.failureReason());
|
||||
}
|
||||
reasons.append("</ul>");
|
||||
|
||||
var message = Html.fromHtml(
|
||||
str("revanced_check_environment_failed_message", reasons.toString()),
|
||||
FROM_HTML_MODE_COMPACT
|
||||
);
|
||||
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
// Create the custom dialog.
|
||||
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||
activity,
|
||||
str("revanced_check_environment_failed_title"), // Title.
|
||||
message, // Message.
|
||||
null, // No EditText.
|
||||
str("revanced_check_environment_dialog_open_official_source_button"), // OK button text.
|
||||
() -> {
|
||||
// Action for the OK (website) button.
|
||||
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(intent);
|
||||
|
||||
// Shutdown to prevent the user from navigating back to this app,
|
||||
// which is no longer showing a warning dialog.
|
||||
activity.finishAffinity();
|
||||
System.exit(0);
|
||||
},
|
||||
null, // No cancel button.
|
||||
str("revanced_check_environment_dialog_ignore_button"), // Neutral button text.
|
||||
() -> {
|
||||
// Neutral button action.
|
||||
// Cleanup data if the user incorrectly imported a huge negative number.
|
||||
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
||||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
|
||||
},
|
||||
true // Dismiss dialog when onNeutralClick.
|
||||
);
|
||||
|
||||
// Get the dialog and main layout.
|
||||
Dialog dialog = dialogPair.first;
|
||||
LinearLayout mainLayout = dialogPair.second;
|
||||
|
||||
// Add icon to the dialog.
|
||||
ImageView iconView = new ImageView(activity);
|
||||
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
|
||||
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
|
||||
iconView.setPadding(0, 0, 0, 0);
|
||||
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
iconParams.gravity = Gravity.CENTER;
|
||||
mainLayout.addView(iconView, 0); // Add icon at the top.
|
||||
|
||||
dialog.setCancelable(false);
|
||||
|
||||
// Show the dialog.
|
||||
Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() {
|
||||
boolean hasRun;
|
||||
@Override
|
||||
public void onStart(Dialog dialog) {
|
||||
// Only run this once, otherwise if the user changes to a different app
|
||||
// then changes back, this handler will run again and disable the buttons.
|
||||
if (hasRun) {
|
||||
return;
|
||||
}
|
||||
hasRun = true;
|
||||
|
||||
// Get the button container to access buttons.
|
||||
LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1);
|
||||
|
||||
Button openWebsiteButton;
|
||||
Button ignoreButton;
|
||||
|
||||
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
|
||||
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
|
||||
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
|
||||
// Neutral button is the first child (index 0).
|
||||
ignoreButton = (Button) rowContainer.getChildAt(0);
|
||||
// OK button is the last child.
|
||||
openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1);
|
||||
} else {
|
||||
// Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral.
|
||||
LinearLayout okContainer =
|
||||
(LinearLayout) buttonContainer.getChildAt(0); // OK is first.
|
||||
openWebsiteButton = (Button) okContainer.getChildAt(0);
|
||||
LinearLayout neutralContainer =
|
||||
(LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last.
|
||||
ignoreButton = (Button) neutralContainer.getChildAt(0);
|
||||
}
|
||||
|
||||
// Initially set buttons to INVISIBLE and disabled.
|
||||
openWebsiteButton.setVisibility(View.INVISIBLE);
|
||||
openWebsiteButton.setEnabled(false);
|
||||
ignoreButton.setVisibility(View.INVISIBLE);
|
||||
ignoreButton.setEnabled(false);
|
||||
|
||||
// Start the countdown for showing and enabling buttons.
|
||||
getCountdownRunnable(ignoreButton, openWebsiteButton).run();
|
||||
}
|
||||
});
|
||||
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
|
||||
}
|
||||
|
||||
private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) {
|
||||
return new Runnable() {
|
||||
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
if (secondsRemaining > 0) {
|
||||
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) {
|
||||
openWebsiteButton.setVisibility(View.VISIBLE);
|
||||
openWebsiteButton.setEnabled(true);
|
||||
}
|
||||
secondsRemaining--;
|
||||
Utils.runOnMainThreadDelayed(this, 1000);
|
||||
} else {
|
||||
ignoreButton.setVisibility(View.VISIBLE);
|
||||
ignoreButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
package app.revanced.extension.shared.checks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Base64;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.checks.Check.debugAlwaysShowWarning;
|
||||
import static app.revanced.extension.shared.checks.PatchInfo.Build.*;
|
||||
|
||||
/**
|
||||
* This class is used to check if the app was patched by the user
|
||||
* and not downloaded pre-patched, because pre-patched apps are difficult to trust.
|
||||
* <br>
|
||||
* Various indicators help to detect if the app was patched by the user.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class CheckEnvironmentPatch {
|
||||
private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning();
|
||||
|
||||
private enum InstallationType {
|
||||
/**
|
||||
* CLI patching, manual installation of a previously patched using adb,
|
||||
* or root installation if stock app is first installed using adb.
|
||||
*/
|
||||
ADB((String) null),
|
||||
ROOT_MOUNT_ON_APP_STORE("com.android.vending"),
|
||||
MANAGER("app.revanced.manager.flutter",
|
||||
"app.revanced.manager",
|
||||
"app.revanced.manager.debug");
|
||||
|
||||
@Nullable
|
||||
static InstallationType installTypeFromPackageName(@Nullable String packageName) {
|
||||
for (InstallationType type : values()) {
|
||||
for (String installPackageName : type.packageNames) {
|
||||
if (Objects.equals(installPackageName, packageName)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array elements can be null.
|
||||
*/
|
||||
final String[] packageNames;
|
||||
|
||||
InstallationType(String... packageNames) {
|
||||
this.packageNames = packageNames;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app is installed by the manager, the app store, or through adb/CLI.
|
||||
* <br>
|
||||
* Does not conclusively
|
||||
* If the app is installed by the manager or the app store, it is likely, the app was patched using the manager,
|
||||
* or installed manually via ADB (in the case of ReVanced CLI for example).
|
||||
* <br>
|
||||
* If the app is not installed by the manager or the app store, then the app was likely downloaded pre-patched
|
||||
* and installed by the browser or another unknown app.
|
||||
*/
|
||||
private static class CheckExpectedInstaller extends Check {
|
||||
@Nullable
|
||||
InstallationType installerFound;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Boolean check() {
|
||||
final var context = Utils.getContext();
|
||||
|
||||
final var installerPackageName =
|
||||
context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
||||
|
||||
Logger.printInfo(() -> "Installed by: " + installerPackageName);
|
||||
|
||||
installerFound = InstallationType.installTypeFromPackageName(installerPackageName);
|
||||
final boolean passed = (installerFound != null);
|
||||
|
||||
Logger.printInfo(() -> passed
|
||||
? "Apk was not installed from an unknown source"
|
||||
: "Apk was installed from an unknown source");
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String failureReason() {
|
||||
return str("revanced_check_environment_manager_not_expected_installer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uiSortingValue() {
|
||||
return -100; // Show first.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the build properties are the same as during the patch.
|
||||
* <br>
|
||||
* If the build properties are the same as during the patch, it is likely, the app was patched on the same device.
|
||||
* <br>
|
||||
* If the build properties are different, the app was likely downloaded pre-patched or patched on another device.
|
||||
*/
|
||||
private static class CheckWasPatchedOnSameDevice extends Check {
|
||||
@SuppressLint({"NewApi", "HardwareIds"})
|
||||
@Override
|
||||
protected Boolean check() {
|
||||
if (PATCH_BOARD.isEmpty()) {
|
||||
// Did not patch with Manager, and cannot conclusively say where this was from.
|
||||
Logger.printInfo(() -> "APK does not contain a hardware signature and cannot compare to current device");
|
||||
return null;
|
||||
}
|
||||
|
||||
//noinspection deprecation
|
||||
final var passed = buildFieldEqualsHash("BOARD", Build.BOARD, PATCH_BOARD) &
|
||||
buildFieldEqualsHash("BOOTLOADER", Build.BOOTLOADER, PATCH_BOOTLOADER) &
|
||||
buildFieldEqualsHash("BRAND", Build.BRAND, PATCH_BRAND) &
|
||||
buildFieldEqualsHash("CPU_ABI", Build.CPU_ABI, PATCH_CPU_ABI) &
|
||||
buildFieldEqualsHash("CPU_ABI2", Build.CPU_ABI2, PATCH_CPU_ABI2) &
|
||||
buildFieldEqualsHash("DEVICE", Build.DEVICE, PATCH_DEVICE) &
|
||||
buildFieldEqualsHash("DISPLAY", Build.DISPLAY, PATCH_DISPLAY) &
|
||||
buildFieldEqualsHash("FINGERPRINT", Build.FINGERPRINT, PATCH_FINGERPRINT) &
|
||||
buildFieldEqualsHash("HARDWARE", Build.HARDWARE, PATCH_HARDWARE) &
|
||||
buildFieldEqualsHash("HOST", Build.HOST, PATCH_HOST) &
|
||||
buildFieldEqualsHash("ID", Build.ID, PATCH_ID) &
|
||||
buildFieldEqualsHash("MANUFACTURER", Build.MANUFACTURER, PATCH_MANUFACTURER) &
|
||||
buildFieldEqualsHash("MODEL", Build.MODEL, PATCH_MODEL) &
|
||||
buildFieldEqualsHash("PRODUCT", Build.PRODUCT, PATCH_PRODUCT) &
|
||||
buildFieldEqualsHash("RADIO", Build.RADIO, PATCH_RADIO) &
|
||||
buildFieldEqualsHash("TAGS", Build.TAGS, PATCH_TAGS) &
|
||||
buildFieldEqualsHash("TYPE", Build.TYPE, PATCH_TYPE) &
|
||||
buildFieldEqualsHash("USER", Build.USER, PATCH_USER);
|
||||
|
||||
Logger.printInfo(() -> passed
|
||||
? "Device hardware signature matches current device"
|
||||
: "Device hardware signature does not match current device");
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String failureReason() {
|
||||
return str("revanced_check_environment_not_same_patching_device");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uiSortingValue() {
|
||||
return 0; // Show in the middle.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app was installed within the last 30 minutes after being patched.
|
||||
* <br>
|
||||
* If the app was installed within the last 30 minutes, it is likely, the app was patched by the user.
|
||||
* <br>
|
||||
* If the app was installed much later than the patch time, it is likely the app was
|
||||
* downloaded pre-patched or the user waited too long to install the app.
|
||||
*/
|
||||
private static class CheckIsNearPatchTime extends Check {
|
||||
/**
|
||||
* How soon after patching the app must be installed to pass.
|
||||
*/
|
||||
static final int INSTALL_AFTER_PATCHING_DURATION_THRESHOLD = 30 * 60 * 1000; // 30 minutes.
|
||||
|
||||
/**
|
||||
* Milliseconds between the time the app was patched, and when it was installed/updated.
|
||||
*/
|
||||
long durationBetweenPatchingAndInstallation;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Boolean check() {
|
||||
try {
|
||||
Context context = Utils.getContext();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
|
||||
// Duration since initial install or last update, which ever is sooner.
|
||||
durationBetweenPatchingAndInstallation = packageInfo.lastUpdateTime - PatchInfo.PATCH_TIME;
|
||||
Logger.printInfo(() -> "App was installed/updated: "
|
||||
+ (durationBetweenPatchingAndInstallation / (60 * 1000) + " minutes after patching"));
|
||||
|
||||
if (durationBetweenPatchingAndInstallation < 0) {
|
||||
// Patch time is in the future and clearly wrong.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (durationBetweenPatchingAndInstallation < INSTALL_AFTER_PATCHING_DURATION_THRESHOLD) {
|
||||
return true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
Logger.printException(() -> "Package name not found exception", ex); // Will never happen.
|
||||
}
|
||||
|
||||
// User installed more than 30 minutes after patching.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String failureReason() {
|
||||
if (durationBetweenPatchingAndInstallation < 0) {
|
||||
// Could happen if the user has their device clock incorrectly set in the past,
|
||||
// but assume that isn't the case and the apk was patched on a device with the wrong system time.
|
||||
return str("revanced_check_environment_not_near_patch_time_invalid");
|
||||
}
|
||||
|
||||
// If patched over 1 day ago, show how old this pre-patched apk is.
|
||||
// Showing the age can help convey it's better to patch yourself and know it's the latest.
|
||||
final long oneDay = 24 * 60 * 60 * 1000;
|
||||
final long daysSincePatching = durationBetweenPatchingAndInstallation / oneDay;
|
||||
if (daysSincePatching > 1) { // Use over 1 day to avoid singular vs plural strings.
|
||||
return str("revanced_check_environment_not_near_patch_time_days", daysSincePatching);
|
||||
}
|
||||
|
||||
return str("revanced_check_environment_not_near_patch_time");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uiSortingValue() {
|
||||
return 100; // Show last.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void check(Activity context) {
|
||||
// If the warning was already issued twice, or if the check was successful in the past,
|
||||
// do not run the checks again.
|
||||
if (!Check.shouldRun() && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
Logger.printDebug(() -> "Environment checks are disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
Logger.printInfo(() -> "Running environment checks");
|
||||
List<Check> failedChecks = new ArrayList<>();
|
||||
|
||||
CheckWasPatchedOnSameDevice sameHardware = new CheckWasPatchedOnSameDevice();
|
||||
Boolean hardwareCheckPassed = sameHardware.check();
|
||||
if (hardwareCheckPassed != null) {
|
||||
if (hardwareCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
// Patched on the same device using Manager,
|
||||
// and no further checks are needed.
|
||||
Check.disableForever();
|
||||
return;
|
||||
}
|
||||
|
||||
failedChecks.add(sameHardware);
|
||||
}
|
||||
|
||||
CheckExpectedInstaller installerCheck = new CheckExpectedInstaller();
|
||||
if (installerCheck.check() && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
// If the installer package is Manager but this code is reached,
|
||||
// that means it must not be the right Manager otherwise the hardware hash
|
||||
// signatures would be present and this check would not have run.
|
||||
if (installerCheck.installerFound == InstallationType.MANAGER) {
|
||||
failedChecks.add(installerCheck);
|
||||
// Also could not have been patched on this device.
|
||||
failedChecks.add(sameHardware);
|
||||
} else if (failedChecks.isEmpty()) {
|
||||
// ADB install of CLI build. Allow even if patched a long time ago.
|
||||
Check.disableForever();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
failedChecks.add(installerCheck);
|
||||
}
|
||||
|
||||
CheckIsNearPatchTime nearPatchTime = new CheckIsNearPatchTime();
|
||||
Boolean timeCheckPassed = nearPatchTime.check();
|
||||
if (timeCheckPassed && !DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
// Allow installing recently patched apks,
|
||||
// even if the install source is not Manager or ADB.
|
||||
Check.disableForever();
|
||||
return;
|
||||
} else {
|
||||
failedChecks.add(nearPatchTime);
|
||||
}
|
||||
|
||||
if (DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG) {
|
||||
// Show all failures for debugging layout.
|
||||
failedChecks = Arrays.asList(
|
||||
sameHardware,
|
||||
nearPatchTime,
|
||||
installerCheck
|
||||
);
|
||||
}
|
||||
|
||||
//noinspection ComparatorCombinators
|
||||
Collections.sort(failedChecks, (o1, o2) -> o1.uiSortingValue() - o2.uiSortingValue());
|
||||
|
||||
Check.issueWarning(
|
||||
context,
|
||||
failedChecks
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "check failure", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean buildFieldEqualsHash(String buildFieldName, String buildFieldValue, @Nullable String hash) {
|
||||
try {
|
||||
final var sha1 = MessageDigest.getInstance("SHA-1")
|
||||
.digest(buildFieldValue.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Must be careful to use same base64 encoding Kotlin uses.
|
||||
String runtimeHash = new String(Base64.encode(sha1, Base64.NO_WRAP), StandardCharsets.ISO_8859_1);
|
||||
final boolean equals = runtimeHash.equals(hash);
|
||||
if (!equals) {
|
||||
Logger.printInfo(() -> "Hashes do not match. " + buildFieldName + ": '" + buildFieldValue
|
||||
+ "' runtimeHash: '" + runtimeHash + "' patchTimeHash: '" + hash + "'");
|
||||
}
|
||||
|
||||
return equals;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Logger.printException(() -> "buildFieldEqualsHash failure", ex); // Will never happen.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user