Compare commits

..

2 Commits

Author SHA1 Message Date
semantic-release-bot
1041238632 chore(release): 2.200.1-dev.1 [skip ci]
## [2.200.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v2.200.0...v2.200.1-dev.1) (2023-11-23)

### Bug Fixes

* **YouTube - Enable tablet layout:** Respect the original device layout ([e88d6a8](e88d6a8bba))
2023-11-23 00:01:35 +00:00
oSumAtrIX
e88d6a8bba fix(YouTube - Enable tablet layout): Respect the original device layout 2023-11-23 00:59:32 +01:00
1843 changed files with 30341 additions and 164199 deletions

View File

@@ -1,3 +0,0 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled

View File

@@ -70,7 +70,7 @@ body:
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).
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-patches/labels/Bug%20report).
- **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
@@ -102,7 +102,7 @@ body:
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.
- label: This issue is not a duplicate of an existing bug report.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -70,8 +70,8 @@ body:
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 submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-patches/labels/Feature%20request).
- **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:
@@ -98,7 +98,7 @@ body:
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
- label: This issue is not a duplicate of an existing feature request.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -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

View File

@@ -1,35 +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
- 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

View File

@@ -20,12 +20,12 @@ jobs:
- 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 }}.
## Dependencies before merge
## Before merging this PR
- [ ] Pull translations from Crowdin
- [ ] https://github.com/revanced/revanced-integrations
pr_draft: true

View File

@@ -1,43 +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
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)"

View File

@@ -1,31 +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
- 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 }}

View File

@@ -6,59 +6,43 @@ 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
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# 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: Cache Node modules
uses: actions/cache@v3
with:
path: |
node_modules
key: npm-${{ hashFiles('package-lock.json') }}
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v3
uses: burrunan/gradle-cache-action@v1
- name: Build
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean
run: ./gradlew generateMeta clean
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'
- name: Install dependencies
- name: Setup semantic-release
run: npm install
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
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
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
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
run: npm exec semantic-release

View File

@@ -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
View File

@@ -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/

2
.idea/misc.xml generated
View File

@@ -4,5 +4,5 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
</project>

View File

@@ -21,10 +21,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 +33,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
}
]
]

File diff suppressed because it is too large Load Diff

View File

@@ -64,15 +64,15 @@ 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
* The [documentation](https://github.com/ReVanced/revanced-patches/tree/docs/docs) provides the fundamentals of patches
and everything necessary to create your own patch from scratch
* [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+).
[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.
@@ -81,11 +81,7 @@ Features can be requested by opening an issue using the
## 🐞 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).
[Bug report issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+).
## 🧑‍⚖️ Guidelines for requesting or contributing patches
@@ -111,18 +107,19 @@ are unaffected by this change.
* 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
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)
described in the [documentation](https://github.com/ReVanced/revanced-patches/tree/docs/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

View File

@@ -67,7 +67,7 @@ This repository contains a collection of ReVanced Patches.
## ❓ About
Patches are small modifications to Android apps that allow you to change the behavior of or add new features,
Patches are small modifications to Android apps that allow you to change the behaviour of or add new features,
block ads, customize the appearance, and much more.
## 💪 Features
@@ -77,11 +77,11 @@ Some of the features the patches provide are:
* 🚫 **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.
* ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions,
export activities, etc.
***And much more!**
For a complete list of all available patches, visit [revanced.app/patches](https://revanced.app/patches).
For a full list of all available patches, visit [revanced.app/patches](https://revanced.app/patches).
## 🚀 How to get started
@@ -93,13 +93,17 @@ You can use [ReVanced CLI](https://github.com/ReVanced/revanced-cli) or [ReVance
Thank you for considering contributing to ReVanced Patches. You can find the contribution guidelines [here](CONTRIBUTING.md).
### 📃 Documentation
The documentation provides the fundamentals of patches and everything necessary to create your own patch from scratch.
You can find it [here](https://github.com/ReVanced/revanced-patches/tree/docs/docs).
### 🛠️ Building
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
In order to build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 📜 Licence
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.
ReVanced Patches is licensed under the GPLv3 licence. Please see the [licence 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.

View File

@@ -1,8 +0,0 @@
{
"info": "This is verification file for ads.fund project",
"project": {
"name": "Revanced Patches",
"walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
"tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802"
}
}

View File

@@ -1,3 +1,113 @@
import org.gradle.kotlin.dsl.support.listFilesOrdered
plugins {
alias(libs.plugins.android.library) apply false
kotlin("jvm") version "1.9.10"
`maven-publish`
}
group = "app.revanced"
repositories {
mavenCentral()
mavenLocal()
google()
maven { url = uri("https://jitpack.io") }
// Required for FlexVer-Java
maven {
url = uri("https://repo.sleeping.town")
content {
includeGroup("com.unascribed")
}
}
}
dependencies {
implementation(libs.revanced.patcher)
implementation(libs.smali)
// TODO: Required because build fails without it. Find a way to remove this dependency.
implementation(libs.guava)
// Used in JsonGenerator.
implementation(libs.gson)
// A dependency to the Android library unfortunately fails the build, which is why this is required.
compileOnly(project("dummy"))
}
kotlin {
jvmToolchain(11)
}
tasks {
register<DefaultTask>("generateBundle") {
description = "Generate dex files from build and bundle them in the jar file"
dependsOn(build)
doLast {
val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools")
.listFilesOrdered().last().resolve("d8").absolutePath
val artifacts = configurations.archives.get().allArtifacts.files.files.first().absolutePath
val workingDirectory = layout.buildDirectory.dir("libs").get().asFile
exec {
workingDir = workingDirectory
commandLine = listOf(d8, artifacts)
}
exec {
workingDir = workingDirectory
commandLine = listOf("zip", "-u", artifacts, "classes.dex")
}
}
}
register<JavaExec>("generateMeta") {
description = "Generate metadata for this bundle"
dependsOn(build)
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("app.revanced.meta.PatchesFileGenerator")
}
// Required to run tasks because Gradle semantic-release plugin runs the publish task.
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
named("publish") {
dependsOn("generateBundle")
dependsOn("generateMeta")
}
}
publishing {
publications {
create<MavenPublication>("revanced-patches-publication") {
from(components["java"])
pom {
name = "ReVanced Patches"
description = "Patches for ReVanced."
url = "https://revanced.app"
licenses {
license {
name = "GNU General Public License v3.0"
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
}
}
developers {
developer {
id = "ReVanced"
name = "ReVanced"
email = "contact@revanced.app"
}
}
scm {
connection = "scm:git:git://github.com/revanced/revanced-patches.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-patches.git"
url = "https://github.com/revanced/revanced-patches"
}
}
}
}
}

View File

@@ -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

9
dummy/build.gradle.kts Normal file
View File

@@ -0,0 +1,9 @@
plugins {
id("java")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}

View File

@@ -0,0 +1,9 @@
package android.os;
import java.io.File;
public final class Environment {
public static File getExternalStorageDirectory() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -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)
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -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);
}
}

View File

@@ -1,12 +0,0 @@
android {
namespace = "app.revanced.extension"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

@@ -1,3 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>

View File

@@ -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));
}
}
}

View File

@@ -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)
}

View File

@@ -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);
}
}
}

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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)
}

View File

@@ -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);
}
}

View File

@@ -1,5 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,22 +0,0 @@
package app.revanced.extension.baconreader;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// BaconReader uses a static user agent for Redgifs API calls
return "BaconReader";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,6 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:boostforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,22 +0,0 @@
package app.revanced.extension.boostforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// Boost uses a static user agent for Redgifs API calls
return "Boost";
}
public static OkHttpClient createClient() {
return new OkHttpClient.Builder().addInterceptor(INSTANCE).build();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,6 +0,0 @@
package com.rubenmayayo.reddit.ui.activities;
import android.app.Activity;
public class WebViewActivity extends Activity {
}

View File

@@ -1,4 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:cricbuzz:stub"))
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,28 +0,0 @@
package app.revanced.extension.cricbuzz.ads;
import com.cricbuzz.android.data.rest.model.BottomBar;
import java.util.List;
import java.util.Iterator;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class HideAdsPatch {
/**
* Injection point.
*/
public static void filterCb11(List<BottomBar> list) {
try {
Iterator<BottomBar> iterator = list.iterator();
while (iterator.hasNext()) {
BottomBar bar = iterator.next();
if (bar.getName().equals("Cricbuzz11")) {
Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar);
iterator.remove();
}
}
} catch (Exception ex) {
Logger.printException(() -> "filterCb11 failure", ex);
}
}
}

View File

@@ -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
}
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,5 +0,0 @@
package com.cricbuzz.android.data.rest.model;
public final class BottomBar {
public final String getName() { throw new UnsupportedOperationException(); }
}

View File

@@ -1,3 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -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;
}
}

View File

@@ -1,11 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 26
}
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,14 +0,0 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideCategoryBarPatch {
/**
* Injection point
*/
public static boolean hideCategoryBar() {
return Settings.HIDE_CATEGORY_BAR.get();
}
}

View File

@@ -1,14 +0,0 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideGetPremiumPatch {
/**
* Injection point
*/
public static boolean hideGetPremiumLabel() {
return Settings.HIDE_GET_PREMIUM_LABEL.get();
}
}

View File

@@ -1,14 +0,0 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideUpgradeButtonPatch {
/**
* Injection point
*/
public static boolean hideUpgradeButton() {
return Settings.HIDE_UPGRADE_BUTTON.get();
}
}

View File

@@ -1,17 +0,0 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideVideoAdsPatch {
/**
* Injection point
*/
public static boolean showVideoAds(boolean original) {
if (Settings.HIDE_VIDEO_ADS.get()) {
return false;
}
return original;
}
}

View File

@@ -1,14 +0,0 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class PermanentRepeatPatch {
/**
* Injection point
*/
public static boolean permanentRepeat() {
return Settings.PERMANENT_REPEAT.get();
}
}

View File

@@ -1,28 +0,0 @@
package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
import java.util.List;
import app.revanced.extension.shared.spoof.ClientType;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32,
ANDROID_VR_1_61_48,
VISIONOS
);
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
availableClients, SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
}
}

View File

@@ -1,87 +0,0 @@
package app.revanced.extension.music.settings;
import android.app.Activity;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.view.View;
import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
/**
* Hooks GoogleApiActivity to inject a custom ReVancedPreferenceFragment with a toolbar.
*/
public class GoogleApiActivityHook extends BaseActivityHook {
/**
* Injection point
* <p>
* Creates an instance of GoogleApiActivityHook for use in static initialization.
*/
@SuppressWarnings("unused")
public static GoogleApiActivityHook createInstance() {
// Must touch the Music settings to ensure the class is loaded and
// the values can be found when setting the UI preferences.
// Logging anything under non debug ensures this is set.
Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get());
// YT Music always uses dark mode.
Utils.setIsDarkModeEnabled(true);
return new GoogleApiActivityHook();
}
/**
* Sets the fixed theme for the activity.
*/
@Override
protected void customizeActivityTheme(Activity activity) {
// Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifier("Theme.ReVanced.YouTubeMusic.Settings", "style"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return Utils.getResourceIdentifier("revanced_music_settings_with_toolbar", "layout");
}
/**
* Returns the fixed background color for the toolbar.
*/
@Override
protected int getToolbarBackgroundColor() {
return Utils.getResourceColor("ytm_color_black");
}
/**
* Returns the navigation icon with a color filter applied.
*/
@Override
protected Drawable getNavigationIcon() {
Drawable navigationIcon = ReVancedPreferenceFragment.getBackButtonDrawable();
navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
return navigationIcon;
}
/**
* Returns the click listener that finishes the activity when the navigation icon is clicked.
*/
@Override
protected View.OnClickListener getNavigationClickListener(Activity activity) {
return view -> activity.finish();
}
/**
* Creates a new ReVancedPreferenceFragment for the activity.
*/
@Override
protected PreferenceFragment createPreferenceFragment() {
return new ReVancedPreferenceFragment();
}
}

View File

@@ -1,29 +0,0 @@
package app.revanced.extension.music.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends BaseSettings {
// Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
public static final BooleanSetting HIDE_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_upgrade_button", TRUE, true);
// General
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
// Player
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
// Miscellaneous
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -1,38 +0,0 @@
package app.revanced.extension.music.settings.preference;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.GoogleApiActivityHook;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Preference fragment for ReVanced settings.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
/**
* Initializes the preference fragment.
*/
@Override
protected void initialize() {
super.initialize();
try {
Utils.sortPreferenceGroups(getPreferenceScreen());
setPreferenceScreenToolbar(getPreferenceScreen());
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
@Override
protected void customizeToolbar(Toolbar toolbar) {
GoogleApiActivityHook.setToolbarLayoutParams(toolbar);
}
}

View File

@@ -1,4 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:nunl:stub"))
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,5 +0,0 @@
package nl.nu.performance.api.client.interfaces;
public class Block {
}

View File

@@ -1,7 +0,0 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class DividerBlock extends Block {
}

View File

@@ -1,7 +0,0 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class DpgBannerBlock extends Block {
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -1,7 +0,0 @@
package nl.nu.performance.api.client.objects;
public class StyledText {
public final String getText() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -1,4 +0,0 @@
package nl.nu.performance.api.client.unions;
public interface LinkFlavor {
}

View File

@@ -1,7 +0,0 @@
package nl.nu.performance.api.client.unions;
public class SmallArticleLinkFlavor implements LinkFlavor {
public final Boolean isPartner() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -1,4 +0,0 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:primevideo:stub"))
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -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);
}
}
}

View File

@@ -1,207 +0,0 @@
package app.revanced.extension.primevideo.videoplayer;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import java.util.Arrays;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import com.amazon.video.sdk.player.Player;
public class PlaybackSpeedPatch {
private static Player player;
private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f};
private static final String SPEED_BUTTON_TAG = "speed_overlay";
public static void setPlayer(Player playerInstance) {
player = playerInstance;
if (player != null) {
// Reset playback rate when switching between episodes to ensure correct display.
player.setPlaybackRate(1.0f);
}
}
public static void initializeSpeedOverlay(View userControlsView) {
try {
LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop");
// If the speed overlay exists we should return early.
if (Utils.getChildView(buttonContainer, false, child ->
child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) {
return;
}
ImageView speedButton = createSpeedButton(userControlsView.getContext());
speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton));
buttonContainer.addView(speedButton, 0);
} catch (IllegalArgumentException e) {
Logger.printException(() -> "initializeSpeedOverlay, no button container found", e);
} catch (Exception e) {
Logger.printException(() -> "initializeSpeedOverlay failure", e);
}
}
private static ImageView createSpeedButton(Context context) {
ImageView speedButton = new ImageView(context);
speedButton.setContentDescription("Playback Speed");
speedButton.setTag(SPEED_BUTTON_TAG);
speedButton.setClickable(true);
speedButton.setFocusable(true);
speedButton.setScaleType(ImageView.ScaleType.CENTER);
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
speedButton.setImageDrawable(speedIcon);
int buttonSize = Utils.dipToPixels(48);
speedButton.setMinimumWidth(buttonSize);
speedButton.setMinimumHeight(buttonSize);
return speedButton;
}
private static String[] getSpeedOptions() {
String[] options = new String[SPEED_VALUES.length];
for (int i = 0; i < SPEED_VALUES.length; i++) {
options[i] = SPEED_VALUES[i] + "x";
}
return options;
}
private static void changePlaybackSpeed(ImageView imageView) {
if (player == null) {
Logger.printException(() -> "Player not available");
return;
}
try {
player.pause();
AlertDialog dialog = createSpeedPlaybackDialog(imageView);
dialog.setOnDismissListener(dialogInterface -> player.play());
dialog.show();
} catch (Exception e) {
Logger.printException(() -> "changePlaybackSpeed", e);
}
}
private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) {
Context context = imageView.getContext();
int currentSelection = getCurrentSpeedSelection();
return new AlertDialog.Builder(context)
.setTitle("Select Playback Speed")
.setSingleChoiceItems(getSpeedOptions(), currentSelection,
PlaybackSpeedPatch::handleSpeedSelection)
.create();
}
private static int getCurrentSpeedSelection() {
try {
float currentRate = player.getPlaybackRate();
int index = Arrays.binarySearch(SPEED_VALUES, currentRate);
return Math.max(index, 0); // Use slowest speed if not found.
} catch (Exception e) {
Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e);
return 0;
}
}
private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) {
try {
float selectedSpeed = SPEED_VALUES[selectedIndex];
player.setPlaybackRate(selectedSpeed);
player.play();
} catch (Exception e) {
Logger.printException(() -> "handleSpeedSelection error setting playback speed", e);
} finally {
dialog.dismiss();
}
}
}
class SpeedIconDrawable extends Drawable {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
public void draw(Canvas canvas) {
int w = getBounds().width();
int h = getBounds().height();
float centerX = w / 2f;
// Position gauge in lower portion.
float centerY = h * 0.7f;
float radius = Math.min(w, h) / 2f * 0.8f;
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(radius * 0.1f);
// Draw semicircle.
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawArc(oval, 180, 180, false, paint);
// Draw three tick marks.
paint.setStrokeWidth(radius * 0.06f);
for (int i = 0; i < 3; i++) {
float angle = 180 + (i * 45); // 180°, 225°, 270°.
float angleRad = (float) Math.toRadians(angle);
float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad);
float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad);
float endX = centerX + radius * (float) Math.cos(angleRad);
float endY = centerY + radius * (float) Math.sin(angleRad);
canvas.drawLine(startX, startY, endX, endY, paint);
}
// Draw needle.
paint.setStrokeWidth(radius * 0.08f);
float needleAngle = 200; // Slightly right of center.
float needleAngleRad = (float) Math.toRadians(needleAngle);
float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad);
float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad);
canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint);
// Center dot.
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centerX, centerY, radius * 0.06f, paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return Utils.dipToPixels(32);
}
@Override
public int getIntrinsicHeight() {
return Utils.dipToPixels(32);
}
}

View File

@@ -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
}
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,6 +0,0 @@
package com.amazon.avod.fsm;
public final class SimpleTrigger<T> implements Trigger<T> {
public SimpleTrigger(T triggerType) {
}
}

View File

@@ -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) {
}
}

View File

@@ -1,4 +0,0 @@
package com.amazon.avod.fsm;
public interface Trigger<T> {
}

View File

@@ -1,7 +0,0 @@
package com.amazon.avod.media;
public final class TimeSpan {
public long getTotalMilliseconds() {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,7 +0,0 @@
package com.amazon.avod.media.ads;
import com.amazon.avod.media.TimeSpan;
public interface AdBreak {
TimeSpan getDurationExcludingAux();
}

View File

@@ -1,4 +0,0 @@
package com.amazon.avod.media.ads.internal.state;
public abstract class AdBreakState extends AdEnabledPlaybackState {
}

View File

@@ -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();
}
}

View File

@@ -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> {
}

View File

@@ -1,5 +0,0 @@
package com.amazon.avod.media.ads.internal.state;
public enum AdEnabledPlayerTriggerType {
NO_MORE_ADS_SKIP_TRANSITION
}

View File

@@ -1,4 +0,0 @@
package com.amazon.avod.media.ads.internal.state;
public class ServerInsertedAdBreakState extends AdBreakState {
}

View File

@@ -1,13 +0,0 @@
package com.amazon.avod.media.playback;
public interface VideoPlayer {
long getCurrentPosition();
void seekTo(long positionMs);
void pause();
void play();
boolean isPlaying();
}

View File

@@ -1,4 +0,0 @@
package com.amazon.avod.media.playback.state;
public interface PlayerStateType {
}

View File

@@ -1,4 +0,0 @@
package com.amazon.avod.media.playback.state.trigger;
public interface PlayerTriggerType {
}

View File

@@ -1,11 +0,0 @@
package com.amazon.video.sdk.player;
public interface Player {
float getPlaybackRate();
void setPlaybackRate(float rate);
void play();
void pause();
}

Some files were not shown because too many files have changed in this diff Show More