diff --git a/DisableSounds/Readme.md b/DisableSounds/Readme.md new file mode 100644 index 0000000..27ad570 --- /dev/null +++ b/DisableSounds/Readme.md @@ -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. diff --git a/DisableSounds/build.gradle.kts b/DisableSounds/build.gradle.kts new file mode 100644 index 0000000..7541d88 --- /dev/null +++ b/DisableSounds/build.gradle.kts @@ -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) +} diff --git a/DisableSounds/src/main/AndroidManifest.xml b/DisableSounds/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b3547a0 --- /dev/null +++ b/DisableSounds/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/DisableSounds/src/main/assets/xposed_init b/DisableSounds/src/main/assets/xposed_init new file mode 100644 index 0000000..0f991eb --- /dev/null +++ b/DisableSounds/src/main/assets/xposed_init @@ -0,0 +1,4 @@ +com.programminghoch10.DisableSounds.DisableChargingSoundsHook +com.programminghoch10.DisableSounds.DisableForcedCameraSoundsHook +com.programminghoch10.DisableSounds.DisableScreenshotSoundsHook +com.programminghoch10.DisableSounds.DisableShutterSoundsHook diff --git a/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableChargingSoundsHook.kt b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableChargingSoundsHook.kt new file mode 100644 index 0000000..ad89228 --- /dev/null +++ b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableChargingSoundsHook.kt @@ -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), + ) + } + } +} diff --git a/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableForcedCameraSoundsHook.kt b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableForcedCameraSoundsHook.kt new file mode 100644 index 0000000..e55c27d --- /dev/null +++ b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableForcedCameraSoundsHook.kt @@ -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) + } +} diff --git a/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableScreenshotSoundsHook.kt b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableScreenshotSoundsHook.kt new file mode 100644 index 0000000..860cbbd --- /dev/null +++ b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableScreenshotSoundsHook.kt @@ -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() {} + } +} diff --git a/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableShutterSoundsHook.kt b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableShutterSoundsHook.kt new file mode 100644 index 0000000..3348e8a --- /dev/null +++ b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/DisableShutterSoundsHook.kt @@ -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 + } +} diff --git a/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/SettingsActivity.kt b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/SettingsActivity.kt new file mode 100644 index 0000000..e77dd84 --- /dev/null +++ b/DisableSounds/src/main/java/com/programminghoch10/DisableSounds/SettingsActivity.kt @@ -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) + } + } +} diff --git a/DisableSounds/src/main/res/layout/settings_activity.xml b/DisableSounds/src/main/res/layout/settings_activity.xml new file mode 100644 index 0000000..d4662df --- /dev/null +++ b/DisableSounds/src/main/res/layout/settings_activity.xml @@ -0,0 +1,13 @@ + + + + diff --git a/DisableSounds/src/main/res/values-v21/themes.xml b/DisableSounds/src/main/res/values-v21/themes.xml new file mode 100644 index 0000000..ee02adb --- /dev/null +++ b/DisableSounds/src/main/res/values-v21/themes.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DisableSounds/src/main/res/xml/root_preferences.xml b/DisableSounds/src/main/res/xml/root_preferences.xml new file mode 100644 index 0000000..8c43548 --- /dev/null +++ b/DisableSounds/src/main/res/xml/root_preferences.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14890dc..39b3988 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ githubApi = "1.330" hiddenapibypass = "6.1" jebrainsAnnotations = "26.0.2-1" kotlin = "2.2.20" +kotlinxCoroutinesGuava = "1.10.2" libsu = "6.0.0" preference = "1.2.1" xposed = "82" @@ -30,6 +31,7 @@ jebtrains-annotations = { module = "org.jetbrains:annotations", version.ref = "j kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } +kotlinx-coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxCoroutinesGuava" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposed" } diff --git a/metadata/com.programminghoch10.DisableSounds/en-US/full_description.txt b/metadata/com.programminghoch10.DisableSounds/en-US/full_description.txt new file mode 100644 index 0000000..0fbf9e2 --- /dev/null +++ b/metadata/com.programminghoch10.DisableSounds/en-US/full_description.txt @@ -0,0 +1,6 @@ +Disable various system sounds. + +- Disable (regionally) forced camera sound +- Disable shutter sound +- Disable screenshot sound +- Disable charging sound and vibration diff --git a/metadata/com.programminghoch10.DisableSounds/en-US/short_description.txt b/metadata/com.programminghoch10.DisableSounds/en-US/short_description.txt new file mode 100644 index 0000000..e65c164 --- /dev/null +++ b/metadata/com.programminghoch10.DisableSounds/en-US/short_description.txt @@ -0,0 +1 @@ +Disable various system sounds. \ No newline at end of file diff --git a/metadata/com.programminghoch10.DisableSounds/en-US/title.txt b/metadata/com.programminghoch10.DisableSounds/en-US/title.txt new file mode 100644 index 0000000..eb487d0 --- /dev/null +++ b/metadata/com.programminghoch10.DisableSounds/en-US/title.txt @@ -0,0 +1 @@ +DisableSounds \ No newline at end of file diff --git a/modules.gradle.kts b/modules.gradle.kts index a3d053f..c0f785e 100644 --- a/modules.gradle.kts +++ b/modules.gradle.kts @@ -7,6 +7,7 @@ include(":BetterBluetoothDeviceSort") include(":BetterVerboseWiFiLogging") include(":ClassHunter") include(":CodecMod") +include(":DisableSounds") include(":DontResetIfBootedAndConnected") include(":EnableCallRecording") include(":FreeNotifications")