Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions DisableSounds/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# DisableSounds

Disable various system sounds.

- Disable (regionally) forced camera sound
- Disable shutter sound
- Disable screenshot sound
- Disable charging sound and vibration

## Forced camera sound

In some regions the camera sounds
for shutter, start/stop recording and focus
are enforced by law.
This module tells the system that disabling the sounds
is allowed.
The system and apps will then show/enable their options
for disabling camera/shutter sounds.
This hook is always enabled, since allowing the option to appear,
does not change the option by default.
Be aware that it might be illegal to disable camera sounds in your country.
23 changes: 23 additions & 0 deletions DisableSounds/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
alias(libs.plugins.buildlogic.android.application)
alias(libs.plugins.buildlogic.kotlin.android)
}

android {
namespace = "com.programminghoch10.DisableSounds"

defaultConfig {
minSdk = 17
targetSdk = 36
buildConfigField("String", "SHARED_PREFERENCES_NAME", "\"disable_sounds\"")
}
}

dependencies {
// fragment-ktx is included as transitive dependency through preference-ktx
// the transitive dependency is a lower version though, which allows minSdk 17,
// while explicit mention with the latest version forced minSdk 21
//implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.preference.ktx)
implementation(libs.kotlinx.coroutines.guava)
}
37 changes: 37 additions & 0 deletions DisableSounds/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<application android:label="@string/app_name">
<activity
android:name=".SettingsActivity"
android:excludeFromRecents="true"
android:exported="true"
android:label="@string/title_activity_settings"
android:theme="@style/AppTheme"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
</intent-filter>
</activity>

<meta-data
android:name="xposedmodule"
android:value="true"
/>
<meta-data
android:name="xposeddescription"
android:value="@string/description"
/>
<meta-data
android:name="xposedminversion"
android:value="93"
/>
<meta-data
android:name="xposedscope"
android:resource="@array/scope"
/>
</application>

</manifest>
4 changes: 4 additions & 0 deletions DisableSounds/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
com.programminghoch10.DisableSounds.DisableChargingSoundsHook
com.programminghoch10.DisableSounds.DisableForcedCameraSoundsHook
com.programminghoch10.DisableSounds.DisableScreenshotSoundsHook
com.programminghoch10.DisableSounds.DisableShutterSoundsHook
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.programminghoch10.DisableSounds

import android.content.ContentResolver
import android.provider.Settings
import com.programminghoch10.DisableSounds.BuildConfig.SHARED_PREFERENCES_NAME
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

class DisableChargingSoundsHook : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
if (lpparam.packageName != "android") return
val sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
if (!sharedPreferences.getBoolean("charging", false)) return
val disableChargingFeedback = sharedPreferences.getBoolean("chargingFeedback", false)

val CHARGING_STARTED_SOUND = XposedHelpers.getStaticObjectField(Settings::class.java, "CHARGING_STARTED_SOUND") as String
val WIRELESS_CHARGING_STARTED_SOUND = XposedHelpers.getStaticObjectField(Settings::class.java, "WIRELESS_CHARGING_STARTED_SOUND") as String
val CHARGING_VIBRATION_ENABLED = XposedHelpers.getStaticObjectField(Settings.Secure::class.java, "CHARGING_VIBRATION_ENABLED") as String

XposedHelpers.findAndHookMethod(
Settings.Global::class.java,
"getString",
ContentResolver::class.java,
String::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val string = param.args[1] as String
if (string == CHARGING_STARTED_SOUND || string == WIRELESS_CHARGING_STARTED_SOUND) param.result = null
}
},
)

if (disableChargingFeedback) {
XposedHelpers.findAndHookMethod(
Settings.Secure::class.java,
"getIntForUser",
ContentResolver::class.java,
String::class.java,
Int::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val string = param.args[1] as String
if (string == CHARGING_VIBRATION_ENABLED) param.result = 0
}
},
)

val NotifierClass = XposedHelpers.findClass("com.android.server.power.Notifier", lpparam.classLoader)
XposedHelpers.findAndHookMethod(
NotifierClass,
"isChargingFeedbackEnabled",
Int::class.java,
XC_MethodReplacement.returnConstant(false),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.programminghoch10.DisableSounds

import android.content.res.XResources
import android.media.MediaActionSound
import android.os.Build
import android.util.Log
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

class DisableForcedCameraSoundsHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
Log.d(this::class.java.simpleName, "handleLoadPackage: loaded ${this::class.java.simpleName} with package ${lpparam.packageName}")
if (lpparam.packageName == "android") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val AudioServiceClass = XposedHelpers.findClass("com.android.server.audio.AudioService", lpparam.classLoader)
XposedHelpers.findAndHookMethod(AudioServiceClass, "isCameraSoundForced", XC_MethodReplacement.returnConstant(false))
XposedHelpers.findAndHookMethod(AudioServiceClass, "readCameraSoundForced", XC_MethodReplacement.returnConstant(false))
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
XposedHelpers.findAndHookMethod(MediaActionSound::class.java, "mustPlayShutterSound", XC_MethodReplacement.returnConstant(false))
}
}

override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
XResources.setSystemWideReplacement("android", "bool", "config_camera_sound_forced", false)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.programminghoch10.DisableSounds

import android.content.Context
import android.media.MediaActionSound
import android.os.Build
import com.google.common.util.concurrent.Futures
import com.programminghoch10.DisableSounds.BuildConfig.SHARED_PREFERENCES_NAME
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

class DisableScreenshotSoundsHook : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
if (lpparam.packageName != "com.android.systemui") return
val sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
if (!sharedPreferences.getBoolean("screenshot", false)) return

when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> XposedHelpers.findAndHookMethod(
"com.android.systemui.screenshot.ScreenshotSoundControllerImpl",
lpparam.classLoader,
"playScreenshotSoundAsync",
XC_MethodReplacement.DO_NOTHING,
)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
// TODO: check if inlined by r8 on 33
XposedHelpers.findAndHookMethod(
"com.android.systemui.screenshot.ScreenshotController",
lpparam.classLoader,
"playCameraSound",
XC_MethodReplacement.DO_NOTHING,
)

}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
val ScreenshotControllerClass = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
XposedHelpers.findClass("com.android.systemui.screenshot.ScreenshotController", lpparam.classLoader)
} else {
XposedHelpers.findClass("com.android.systemui.screenshot.GlobalScreenshot", lpparam.classLoader)
}

var replacementDummy: Any = MediaActionSoundDummy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) replacementDummy = Futures.immediateFuture(replacementDummy)

XposedHelpers.findAndHookConstructor(
ScreenshotControllerClass, Context::class.java, object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam) {
XposedHelpers.setObjectField(
param.thisObject, "mCameraSound", replacementDummy
)
}
})
}
}

class MediaActionSoundDummy : MediaActionSound() {
override fun load(soundName: Int) {}
override fun play(soundName: Int) {}
override fun release() {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.programminghoch10.DisableSounds

import android.media.MediaActionSound
import android.os.Build
import com.programminghoch10.DisableSounds.BuildConfig.SHARED_PREFERENCES_NAME
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XSharedPreferences
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage

class DisableShutterSoundsHook : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
val sharedPreferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
if (!sharedPreferences.getBoolean("shutter", false)) return

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
for (methodName in listOf("load", "play")) {
XposedHelpers.findAndHookMethod(
MediaActionSound::class.java,
methodName,
Int::class.java,
XC_MethodReplacement.DO_NOTHING,
)
}
}

// TODO: need a native hook for old Camera API methods
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/av/services/camera/libcameraservice/CameraService.cpp;l=4097?q=camera_click.ogg
// then the "might not work" warning can be removed from @string/disable_shutter_sounds_description_on
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.programminghoch10.DisableSounds

import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceFragmentCompat
import com.programminghoch10.DisableSounds.BuildConfig.SHARED_PREFERENCES_NAME

class SettingsActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit()
}
actionBar?.setDisplayHomeAsUpEnabled(true)
}

override fun onNavigateUp(): Boolean {
finish()
return true
}

class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}
13 changes: 13 additions & 0 deletions DisableSounds/src/main/res/layout/settings_activity.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme.Edge2EdgeFix"
>

<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
5 changes: 5 additions & 0 deletions DisableSounds/src/main/res/values-v21/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Settings" />
</resources>
4 changes: 4 additions & 0 deletions DisableSounds/src/main/res/values-v34/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="disable_shutter_sounds_description_on">Some apps using the old Camera API might not be muted.</string>
</resources>
8 changes: 8 additions & 0 deletions DisableSounds/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="scope">
<item>android</item>
<item>com.android.systemui</item>
<item>org.lineageos.aperture</item>
</string-array>
</resources>
14 changes: 14 additions & 0 deletions DisableSounds/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_activity_settings">DisableSounds Configuration</string>
<string name="app_name">DisableSounds</string>
<string name="description">Disable various system sounds</string>
<string name="disable_screenshot_sounds_title">Disable screenshot sounds</string>
<string name="disable_screenshot_sounds_description">Disable screenshot and screen recording sounds.</string>
<string name="disable_shutter_sounds_title">Disable shutter sounds</string>
<string name="disable_shutter_sounds_description">Disable shutter, focus and video recording sounds.</string>
<string name="disable_shutter_sounds_description_on">Some apps using the old Camera API might not be muted. The screenshot sound might be muted too.</string>
<string name="disable_charging_sounds">Disable charging sounds</string>
<string name="disable_charging_feedback">Disable charging feedback</string>
<string name="disable_charging_feedback_summary">Also disable charging vibration.</string>
</resources>
9 changes: 9 additions & 0 deletions DisableSounds/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme" parent="@android:style/Theme.DeviceDefault" />

<style name="AppTheme.Edge2EdgeFix">
<item name="android:fitsSystemWindows">true</item>
</style>
</resources>
Loading