Skip to content
Draft
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
2 changes: 0 additions & 2 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions DisableSounds/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# DisableSounds

Disable various system sounds.

- Disable (regionally) forced camera sound
- Disable shutter sound
- Disable screenshot sound

## 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.
26 changes: 26 additions & 0 deletions DisableSounds/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
alias(libs.plugins.buildlogic.android.application)
alias(libs.plugins.buildlogic.kotlin.android)
}

android {
namespace = "com.programminghoch10.DisableSounds"

defaultConfig {
minSdk = 17
targetSdk = 36
}

lint {
disable += "ObsoleteSdkInt"
}
}

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:exported="true"
android:excludeFromRecents="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.DisableForcedCameraSoundHook
com.programminghoch10.DisableSounds.DisableScreenshotSoundHook
com.programminghoch10.DisableSounds.DisableShutterSoundsHook
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.programminghoch10.DisableSounds

import android.content.ContentResolver
import android.provider.Settings
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,29 @@
package com.programminghoch10.DisableSounds

import android.content.res.XResources
import android.media.MediaActionSound
import android.os.Build
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 DisableForcedCameraSoundHook : IXposedHookLoadPackage, IXposedHookZygoteInit {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
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 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 DisableScreenshotSoundHook : 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,27 @@
package com.programminghoch10.DisableSounds

import android.media.MediaActionSound
import android.os.Build
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,42 @@
package com.programminghoch10.DisableSounds

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceGroup
import androidx.preference.children

val SHARED_PREFERENCES_NAME = "disable_sounds"

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() {
@SuppressLint("WorldReadableFiles")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE
setPreferencesFromResource(R.xml.root_preferences, rootKey)
preferenceScreen.setIconSpaceReservedRecursive(false)
}

fun Preference.setIconSpaceReservedRecursive(iconSpaceReserved: Boolean) {
this.isIconSpaceReserved = iconSpaceReserved
if (this is PreferenceGroup) children.forEach { it.setIconSpaceReservedRecursive(iconSpaceReserved) }
}
}
}
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>
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_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. On Android 13 and older the screenshot sound might be muted too.</string>
<string name="disable_screenshot_sound_title">Disable screenshot sound</string>
<string name="disable_screenshot_sound_description">@null</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