Skip to content

Commit

Permalink
First release
Browse files Browse the repository at this point in the history
  • Loading branch information
Massi-X committed Sep 26, 2023
0 parents commit 1b82669
Show file tree
Hide file tree
Showing 32 changed files with 1,376 additions and 0 deletions.
46 changes: 46 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Log/OS Files
*.log

# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
release/

# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml

# Keystore files
*.jks
*.keystore

# Google Services (e.g. APIs or Firebase)
google-services.json

# Android Profiling
*.hprof

# Common credential files
**/credentials.json
**/client_secrets.json
**/client_secret.json
*creds*
*.dat
*password*
*.httr-oauth*

# Mac/OSX
.DS_Store
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<table>
<tr>
<td width="200" style="border: none;">
<img src="app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" alt="Module icon"/>
</td>
<td style="border: none;">
<h2>Cast Enabler</h2>
</td>
</tr>
</table>

## About this
Simple module to enable cast volume control with the rocker or in the input switcher when disabled by the manufacturer.

The purpose of the app is to only enable cast volume control when this was mistakenly disabled by the manufacturer and by no mean was meant to break any law.

Find the latest release [here](https://github.com/Massi-X/castenabler/releases/latest).

## Development
Pull the main repo, make the appropriate changes, then compile it using [Android Studio](https://developer.android.com/studio). Should work out of the box.

## Donation
If you like to support me, you can donate. Any help is greatly appreciated. Thank you!

<a target="_blank" href="https://paypal.me/firemetris"><img src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" alt="paypal"/></a>

**Bitcoin:** 1Pig6XJxXGBj1v3r6uLrFUSHwyX8GPopRs

**Monero:** 89qdmpDsMm9MUvpsG69hbRMcGWeG2D26BdATw1iXXZwi8DqSEstJGcWNkenrXtThCAAJTpjkUNtZuQvxK1N5xSyb18eXzPD

## License
`SPDX-License-Identifier: AGPL-3.0`<br>
This work is licensed under <a target="_blank" href="https://spdx.org/licenses/AGPL-3.0.html">GNU Affero General Public License v3.0</a><br>
By using this project you agree to the terms.
31 changes: 31 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id 'com.android.application'
}

android {
compileSdk 31

defaultConfig {
applicationId "com.metris.xposed.castenabler"
minSdk 31
targetSdk 31
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
compileOnly 'de.robv.android.xposed:api:82'
}
24 changes: 24 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep,allowobfuscation class com.metris.xposed.castenabler.XHooks
-keepnames class com.metris.xposed.castenabler.XHooks
-keepattributes EnclosingMethod
41 changes: 41 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.metris.xposed.castenabler">

<!-- Don't declare any permission to let the app be hidden -->

<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.CastEnabler">
<activity android:name=".MainActivity" />

<activity-alias
android:name=".MainAlias"
android:exported="true"
android:targetActivity=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>

<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="@string/xposed_description" />
<meta-data
android:name="xposedminversion"
android:value="82" />

<!-- LSPosed specific -->
<meta-data
android:name="xposedscope"
android:resource="@array/module_scope" />
</application>

</manifest>
1 change: 1 addition & 0 deletions app/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.metris.xposed.castenabler.XHooks
60 changes: 60 additions & 0 deletions app/src/main/java/com/metris/xposed/castenabler/MainActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.metris.xposed.castenabler;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import java.util.Objects;

public class MainActivity extends Activity {
private AlertDialog alert;
private boolean dismissForConfigChange = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//hide all the graphics because we use the dialog only
setTitle("");
overridePendingTransition(0, 0);

AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_Dialog)
.setTitle(R.string.app_name)
.setMessage(R.string.info_text)
.setNeutralButton(R.string.feedback, (dialog, which) ->
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.feedback_url)))))
.setNegativeButton(R.string.hide_app, (dialog, which) -> {
Toast.makeText(this, R.string.goodbye, Toast.LENGTH_LONG).show();
//hide in the standard Android way (supported on Android 10+ because no permissions)
MainActivity.this.getPackageManager().setComponentEnabledSetting(
new ComponentName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".MainAlias"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
})
.setPositiveButton(R.string.support_me, (dialog, which) ->
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_store)))))
.setOnDismissListener((dialog) -> {
if (!dismissForConfigChange)
new Handler(Objects.requireNonNull(Looper.myLooper())).postDelayed(MainActivity.this::finish, 250);
dismissForConfigChange = false;
});

alert = builder.create();
alert.show();
}

@Override
protected void onDestroy() {
super.onDestroy();
if (alert != null && alert.isShowing()) {
dismissForConfigChange = true;
alert.dismiss();
}
}
}
85 changes: 85 additions & 0 deletions app/src/main/java/com/metris/xposed/castenabler/XHooks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.metris.xposed.castenabler;

import java.lang.reflect.Constructor;
import java.util.Arrays;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class XHooks implements IXposedHookLoadPackage {
private static final String APP_TAG = "[CASTENABLER] ";

@SuppressWarnings("RedundantThrows")
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
//before -> https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.0.0_r25/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java#1152
//after -> https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.0.0_r27/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java#1159
String systemUIpkg = "com.android.systemui";

if (lpparam.packageName.equals(systemUIpkg)) {
XposedBridge.log(APP_TAG + "hooking systemui");

try {
Class<?> mediaSessionsCallbacks = XposedHelpers.findClass(
systemUIpkg + ".volume.VolumeDialogControllerImpl$MediaSessionsCallbacks", lpparam.classLoader);
Constructor<?>[] constructorList = mediaSessionsCallbacks.getDeclaredConstructors();
XposedBridge.log(APP_TAG + "found " + constructorList.length + " constructors");

for (Constructor<?> currentConstructor : mediaSessionsCallbacks.getDeclaredConstructors()) {
Class<?>[] parameterClass = currentConstructor.getParameterTypes();
Object[] hookObject = new Object[parameterClass.length + 1];
if (parameterClass.length >= 0)
System.arraycopy(parameterClass, 0, hookObject, 0, parameterClass.length);

hookObject[hookObject.length - 1] = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
try {
XposedHelpers.setBooleanField(param.thisObject, "mShowRemoteSessions", true);
} catch (NoSuchFieldError e) {
try {
XposedHelpers.setBooleanField(param.thisObject, "mVolumeAdjustmentForRemoteGroupSessions", true);
} catch (NoSuchFieldError e1) {
XposedBridge.log(APP_TAG + "unable to hook VolumeDialogControllerImpl (no mShowRemoteSessions or mVolumeAdjustmentForRemoteGroupSessions)!");
}
}
}
};

XposedBridge.log(APP_TAG + "built hook with " + Arrays.toString(hookObject));
XposedHelpers.findAndHookConstructor(mediaSessionsCallbacks, hookObject);
}
} catch (XposedHelpers.ClassNotFoundError e) {
XposedBridge.log(APP_TAG + "cannot find MediaSessionsCallbacks class in systemui!");
} catch (NoSuchMethodError e) {
XposedBridge.log(APP_TAG + "cannot find MediaSessionsCallbacks constructor in systemui!");
}
}

//-----------------------------------
//before -> https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.0.0_r25/services/core/java/com/android/server/media/MediaSessionRecord.java#397
//after -> https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.0.0_r27/services/core/java/com/android/server/media/MediaSessionRecord.java#465
//but no need to hook canHandleVolumeKey(), isPlaybackTypeLocal is always called first

if ("android".equals(lpparam.packageName)) {
XposedBridge.log(APP_TAG + "hooking framework");

try {
XposedHelpers.findAndHookMethod("com.android.server.media.MediaSessionRecord", lpparam.classLoader,
"isPlaybackTypeLocal", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
} catch (XposedHelpers.ClassNotFoundError e) {
XposedBridge.log(APP_TAG + "cannot find MediaSessionRecord class in framework!");
} catch (NoSuchMethodError e) {
XposedBridge.log(APP_TAG + "cannot find method isPlaybackTypeLocal in framework!");
}
}
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file added app/src/main/res/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/res/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/res/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions app/src/main/res/values-night/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background">#424242</color>
<color name="text">#fafafa</color>
</resources>
7 changes: 7 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="module_scope">
<item>android</item>
<item>com.android.systemui</item>
</string-array>
</resources>
7 changes: 7 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background">#f1f8e9</color>
<color name="text">#424242</color>
<color name="buttons">@color/text</color>
<color name="ic_launcher_background">#9CCC65</color>
</resources>
20 changes: 20 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<resources>
<string name="app_name" translatable="false">Cast Enabler</string>
<string name="url_store" translatable="false">https://play.google.com/store/apps/dev?id=5916168791200129933</string>

<string name="xposed_description">Simple module to enable cast volume control when disabled by the manufacturer.</string>
<string name="info_text">Cast Enabler is an app that allow you to bypass manufacturer restrictions on cast volume control.
\nThe purpose of the app is to only enable cast volume control when this was mistakenly disabled by the manufacturer and by no mean was meant to break any law.
\n
\nRemember that the module is only compatible with Android 12 and that this is a Beta version, if you have any problem please
send me a feedback through the thread by clicking the button below.
\n
\nIf you would like to support my work you can buy one of my other apps, Thank you!
\nIf you like instead to stop me worrying you with this you can hide the icon 😭
</string>
<string name="feedback">Feedback</string>
<string name="feedback_url">https://forum.xda-developers.com/t/cast-enabler-for-android-12-xposed.4394713/</string>
<string name="support_me">Support me!</string>
<string name="hide_app">Hide icon</string>
<string name="goodbye">Goodbye my lover…</string>
</resources>
Loading

0 comments on commit 1b82669

Please sign in to comment.