Aperture: Implement shutter sound
Change-Id: I3716739c616bc49901f96922cd0e8000b2460747
diff --git a/app/src/main/java/org/lineageos/aperture/MainActivity.kt b/app/src/main/java/org/lineageos/aperture/MainActivity.kt
index 1536f30..43fa9e6 100644
--- a/app/src/main/java/org/lineageos/aperture/MainActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/MainActivity.kt
@@ -61,6 +61,7 @@
import org.lineageos.aperture.ui.GridView
import org.lineageos.aperture.utils.CameraFacing
import org.lineageos.aperture.utils.CameraMode
+import org.lineageos.aperture.utils.CameraSoundsUtils
import org.lineageos.aperture.utils.GridMode
import org.lineageos.aperture.utils.PhysicalCamera
import org.lineageos.aperture.utils.StorageUtils
@@ -126,6 +127,8 @@
PreferenceManager.getDefaultSharedPreferences(this)
}
+ private lateinit var cameraSoundsUtils: CameraSoundsUtils
+
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
@@ -168,6 +171,9 @@
// Get vendor extensions manager
extensionsManager = ExtensionsManager.getInstanceAsync(this, cameraProvider).get()
+ // Initialize sounds utils
+ cameraSoundsUtils = CameraSoundsUtils(sharedPreferences)
+
// Bind camera controller to lifecycle
cameraController.bindToLifecycle(this)
@@ -258,7 +264,7 @@
startTimerAndRun {
when (cameraMode) {
CameraMode.PHOTO -> takePhoto()
- CameraMode.VIDEO -> captureVideo()
+ CameraMode.VIDEO -> lifecycleScope.launch { captureVideo() }
else -> {}
}
}
@@ -332,6 +338,7 @@
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
+ cameraSoundsUtils.playShutterClick()
viewFinder.foreground = ColorDrawable(Color.BLACK)
ValueAnimator.ofInt(0, 255, 0).apply {
addUpdateListener { anim ->
@@ -351,7 +358,7 @@
}
@androidx.camera.view.video.ExperimentalVideo
- private fun captureVideo() {
+ private suspend fun captureVideo() {
if (cameraController.isRecording) {
// Stop the current recording session.
cameraController.stopRecording()
@@ -361,12 +368,19 @@
// Create output options object which contains file + metadata
val outputOptions = StorageUtils.getVideoMediaStoreOutputOptions(contentResolver)
+ // Play shutter sound
+ if (cameraSoundsUtils.playStartVideoRecording()) {
+ // Delay startRecording() by 500ms to avoid recording shutter sound
+ delay(500)
+ }
+
// Start recording
cameraController.startRecording(
outputOptions,
cameraExecutor,
object : OnVideoSavedCallback {
override fun onVideoSaved(output: OutputFileResults) {
+ cameraSoundsUtils.playStopVideoRecording()
stopRecordingTimer()
val msg = "Video capture succeeded: ${output.savedUri}"
sharedPreferences.lastSavedUri = output.savedUri
@@ -376,6 +390,7 @@
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
+ cameraSoundsUtils.playStopVideoRecording()
stopRecordingTimer()
Log.e(LOG_TAG, "Video capture ends with error: $message")
}
diff --git a/app/src/main/java/org/lineageos/aperture/SettingsActivity.kt b/app/src/main/java/org/lineageos/aperture/SettingsActivity.kt
index 5e4ecdd..437b40e 100644
--- a/app/src/main/java/org/lineageos/aperture/SettingsActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/SettingsActivity.kt
@@ -10,6 +10,8 @@
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreference
+import org.lineageos.aperture.utils.CameraSoundsUtils
class SettingsActivity : AppCompatActivity() {
@@ -36,8 +38,11 @@
}
class SettingsFragment : PreferenceFragmentCompat() {
+ private val shutterSound by lazy { findPreference<SwitchPreference>("shutter_sound") }
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
+ shutterSound?.isEnabled = !CameraSoundsUtils.mustPlaySounds
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt b/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
index 3cb445c..1b090a8 100644
--- a/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
+++ b/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
@@ -271,6 +271,20 @@
}
}
+// Shutter sound
+private const val SHUTTER_SOUND_KEY = "shutter_sound"
+private const val SHUTTER_SOUND_DEFAULT = true
+
+internal var SharedPreferences.shutterSound: Boolean
+ get() {
+ return getBoolean(SHUTTER_SOUND_KEY, SHUTTER_SOUND_DEFAULT)
+ }
+ set(value) {
+ edit {
+ putBoolean(SHUTTER_SOUND_KEY, value)
+ }
+ }
+
// Last saved URI
private const val LAST_SAVED_URI_KEY = "saved_uri"
diff --git a/app/src/main/java/org/lineageos/aperture/utils/CameraSoundsUtils.kt b/app/src/main/java/org/lineageos/aperture/utils/CameraSoundsUtils.kt
new file mode 100644
index 0000000..d6d896e
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/utils/CameraSoundsUtils.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The LineageOS Project
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.utils
+
+import android.content.res.Resources
+import android.content.SharedPreferences
+import android.media.MediaActionSound
+import org.lineageos.aperture.shutterSound
+
+class CameraSoundsUtils(private val sharedPreferences: SharedPreferences) {
+ private val mediaActionSound = MediaActionSound().apply {
+ // Preload all sounds to reduce latency
+ load(MediaActionSound.SHUTTER_CLICK)
+ load(MediaActionSound.START_VIDEO_RECORDING)
+ load(MediaActionSound.STOP_VIDEO_RECORDING)
+ }
+
+ fun playShutterClick() {
+ if (sharedPreferences.shutterSound || mustPlaySounds) {
+ mediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
+ }
+ }
+
+ fun playStartVideoRecording(): Boolean {
+ if (sharedPreferences.shutterSound || mustPlaySounds) {
+ mediaActionSound.play(MediaActionSound.START_VIDEO_RECORDING)
+ return true
+ }
+ return false
+ }
+
+ fun playStopVideoRecording() {
+ if (sharedPreferences.shutterSound || mustPlaySounds) {
+ mediaActionSound.play(MediaActionSound.STOP_VIDEO_RECORDING)
+ }
+ }
+
+ companion object {
+ val mustPlaySounds: Boolean
+ get() {
+ val resources = Resources.getSystem()
+ val id = resources.getIdentifier("config_camera_sound_forced", "bool", "android")
+ return id > 0 && resources.getBoolean(id)
+ }
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4606547..dcb3283 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -27,9 +27,14 @@
<string name="title_activity_settings">Settings</string>
<!-- Preference Titles -->
+ <string name="general_header">General</string>
<string name="photos_header">Photos</string>
<string name="videos_header">Videos</string>
+ <!-- General Preferences -->
+ <string name="shutter_sound_title">Shutter sound</string>
+ <string name="shutter_sound_summary">Note: In some countries this setting will be ignored</string>
+
<!-- Photo Preferences -->
<string name="photo_capture_mode_title">Capture mode</string>
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index 5d88563..c2611f4 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -1,5 +1,15 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+ <PreferenceCategory app:title="@string/general_header">
+
+ <SwitchPreference
+ app:defaultValue="true"
+ app:key="shutter_sound"
+ app:title="@string/shutter_sound_title"
+ app:summary="@string/shutter_sound_summary" />
+
+ </PreferenceCategory>
+
<PreferenceCategory app:title="@string/photos_header">
<ListPreference