From 94a66e97c63b5adcf7c431856316926dcb6c88fa Mon Sep 17 00:00:00 2001 From: ks1000 Date: Mon, 24 Jun 2024 18:16:18 -0700 Subject: [PATCH] Added the ability to adjust the brightness level using the volume buttons after using the double-volume button shortcut to activate the flashlight. The interval is user-adjustable in the UI. --- .../kotlin/com/cyb3rko/flashdim/Camera.kt | 7 ++ .../flashdim/activities/SettingsActivity.kt | 77 +++++++++++++++++++ .../flashdim/service/VolumeButtonService.kt | 52 +++++++++++-- .../kotlin/com/cyb3rko/flashdim/utils/Safe.kt | 11 +++ app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/preferences.xml | 6 ++ gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 8 files changed, 154 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/cyb3rko/flashdim/Camera.kt b/app/src/main/kotlin/com/cyb3rko/flashdim/Camera.kt index 33d52f6..3ac2a9d 100644 --- a/app/src/main/kotlin/com/cyb3rko/flashdim/Camera.kt +++ b/app/src/main/kotlin/com/cyb3rko/flashdim/Camera.kt @@ -94,5 +94,12 @@ internal class Camera(activity: AppCompatActivity) { handleFlashlightException(e) } } + + fun getLightLevel(context: Context): Int { + val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) + as CameraManager + val cameraId = cameraManager.cameraIdList[0] + return cameraManager.getTorchStrengthLevel(cameraId) + } } } diff --git a/app/src/main/kotlin/com/cyb3rko/flashdim/activities/SettingsActivity.kt b/app/src/main/kotlin/com/cyb3rko/flashdim/activities/SettingsActivity.kt index 4b3d82f..a111a7a 100644 --- a/app/src/main/kotlin/com/cyb3rko/flashdim/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/cyb3rko/flashdim/activities/SettingsActivity.kt @@ -152,6 +152,63 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL true } } + + findPreference(Safe.TIMEOUT_DURATION)?.apply { + Safe.initialize(myContext) + if (!Safe.getBoolean(Safe.MULTILEVEL, false)) { + isEnabled = false + return@apply + } + + val timeoutDuration = Safe.getFloat(Safe.TIMEOUT_DURATION, 2.toFloat()) + val summaryString = getString(R.string.preference_item_timeout_duration_summary) + summary = "$summaryString: $timeoutDuration" + + setOnPreferenceClickListener { preference -> + val currentTimeoutDuration = Safe.getFloat(Safe.TIMEOUT_DURATION, 2.toFloat()) + val withVibration = Safe.getBoolean(Safe.BUTTON_VIBRATION, true) + val content = LinearLayout(myContext).apply { + orientation = LinearLayout.VERTICAL + setPadding(75, 0, 75, 0) + } + val levelView = TextView(myContext).apply { + textSize = 18f + setPadding(24, 24, 24, 50) + gravity = Gravity.CENTER_HORIZONTAL + text = String.format( + getString(R.string.preference_item_timeout_duration_dialog_message), + currentTimeoutDuration, + currentTimeoutDuration + ) + } + content.addView(levelView) + val slider = Slider(myContext).apply { + valueFrom = 1F // 1 second min + valueTo = 5F // 10 seconds max + value = currentTimeoutDuration.toFloat() + stepSize = .5F + addOnChangeListener { _, value, _ -> + if (withVibration) Vibrator.vibrateTick() + levelView.text = String.format( + getString(R.string.preference_item_timeout_duration_dialog_message), + value, + currentTimeoutDuration + ) + } + } + content.addView(slider) + showTimeoutDurationDialog(content) { + val value = slider.value + Safe.writeFloat(Safe.TIMEOUT_DURATION, value) // make a Safe.writeFloat() function/ + + val summary = getString(R.string.preference_item_timeout_duration_summary) + preference.summary = "$summary: $value" + } + true + } + } + + findPreference("volume_buttons")?.setOnPreferenceClickListener { AccessibilityInfoDialog.show(myContext) true @@ -175,5 +232,25 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL ) .show() } + + private fun showTimeoutDurationDialog(content: View, onSave: () -> Unit) { + MaterialAlertDialogBuilder( + requireContext(), + MaterialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered + ) + .setIcon(R.drawable.ic_level) + .setTitle(getString(R.string.preference_item_timeout_duration_dialog_title)) + .setView(content) + .setPositiveButton(android.R.string.ok) { _, _ -> + onSave() + } + .setNegativeButton( + getString(R.string.preference_item_initial_level_dialog_negative_button), + null + ) + .show() + } + // make my own function for the volume dimmer duration view + } } diff --git a/app/src/main/kotlin/com/cyb3rko/flashdim/service/VolumeButtonService.kt b/app/src/main/kotlin/com/cyb3rko/flashdim/service/VolumeButtonService.kt index bc5fa88..4f8c0a1 100644 --- a/app/src/main/kotlin/com/cyb3rko/flashdim/service/VolumeButtonService.kt +++ b/app/src/main/kotlin/com/cyb3rko/flashdim/service/VolumeButtonService.kt @@ -26,10 +26,14 @@ import android.view.KeyEvent import android.view.accessibility.AccessibilityEvent import com.cyb3rko.flashdim.Camera import com.cyb3rko.flashdim.utils.Safe +import kotlin.math.max +import kotlin.math.min class VolumeButtonService : AccessibilityService() { private var volumeUpPressed = false private var volumeDownPressed = false + private var flashlightOnTime = 0 // Timestamp of flashlight on time + private val DIM_INCREMENT = 10 // How much each click +/- the brightness override fun onServiceConnected() { Safe.initialize(applicationContext) @@ -46,6 +50,8 @@ class VolumeButtonService : AccessibilityService() { override fun onKeyEvent(event: KeyEvent?): Boolean { if (event == null) return false + var shouldEatMessage = false // Used to determine if the message should be consumed + if ((event.keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) ) { @@ -58,21 +64,57 @@ class VolumeButtonService : AccessibilityService() { if (volumeUpPressed && volumeDownPressed) { Log.i("FlashDim Service", "Both volume buttons pressed") val flashActive = Safe.getBoolean(Safe.FLASH_ACTIVE, false) + if (!flashActive) flashlightOnTime = System.currentTimeMillis().toInt() + val flashLevel = if (!flashActive) getFlashLevel() else 0 Camera.sendLightLevel(applicationContext, flashLevel, !flashActive) + shouldEatMessage = true + + } else if ((volumeUpPressed || volumeDownPressed) && + event.action == KeyEvent.ACTION_DOWN && + Safe.getBoolean(Safe.FLASH_ACTIVE, false)) { + val timeoutDuration = (Safe.getFloat(Safe.TIMEOUT_DURATION, 2F)) * 1000 + + // if the light is on, handle dimming w/ buttons + if ((System.currentTimeMillis().toInt() - flashlightOnTime) < timeoutDuration) { // Come back in 31 years to fix this + val flashLevel = Camera.getLightLevel(applicationContext) + + when (event.keyCode) { + KeyEvent.KEYCODE_VOLUME_UP -> { + Camera.sendLightLevel( + applicationContext, + min(flashLevel + DIM_INCREMENT, Safe.getInt(Safe.MAX_LEVEL, -1)), + true + ) + shouldEatMessage = true + flashlightOnTime = System.currentTimeMillis().toInt() // reset time + } + + KeyEvent.KEYCODE_VOLUME_DOWN -> { + Camera.sendLightLevel( + applicationContext, + max(1, flashLevel - DIM_INCREMENT), + true + ) + shouldEatMessage = true + flashlightOnTime = System.currentTimeMillis().toInt() // reset time + } + } + } } - } else { + } + else { volumeUpPressed = false volumeDownPressed = false } - return false + return shouldEatMessage } private fun getFlashLevel(): Int { - return if (Safe.getBoolean(Safe.VOLUME_BUTTONS_LINK, false)) { - Safe.getInt(Safe.INITIAL_LEVEL, -1) + if (Safe.getBoolean(Safe.VOLUME_BUTTONS_LINK, false)) { + return Safe.getInt(Safe.INITIAL_LEVEL, Safe.getInt(Safe.MAX_LEVEL, -1)) } else { - -1 + return -1 } } diff --git a/app/src/main/kotlin/com/cyb3rko/flashdim/utils/Safe.kt b/app/src/main/kotlin/com/cyb3rko/flashdim/utils/Safe.kt index 7c26b9e..169343a 100644 --- a/app/src/main/kotlin/com/cyb3rko/flashdim/utils/Safe.kt +++ b/app/src/main/kotlin/com/cyb3rko/flashdim/utils/Safe.kt @@ -36,6 +36,8 @@ internal object Safe { const val REPORT_DIALOG_SHOWN = "${BuildConfig.VERSION_CODE}-report_dialog" const val STARTUP_COUNTER = "startup_counter" const val VOLUME_BUTTONS_LINK = "volume_buttons_link" + const val TIMEOUT_DURATION = "timeout_duration" // default 2 seconds, 2000 ms // Make it so that the user can customize this later + private lateinit var sharedPreferences: SharedPreferences private lateinit var editor: SharedPreferences.Editor @@ -51,12 +53,21 @@ internal object Safe { fun getInt(label: String, default: Int) = sharedPreferences.getInt(label, default) + fun getFloat(label: String, default: Float) = sharedPreferences.getFloat(label, default) + + fun writeInt(label: String, value: Int) { try { editor.putInt(label, value).apply() } catch (_: Exception) {} } + fun writeFloat(label: String, value: Float) { + try { + editor.putFloat(label, value).apply() + } catch (_: Exception) {} + } + fun writeBoolean(label: String, value: Boolean) { try { editor.putBoolean(label, value).apply() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 998074d..6d66ffb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,9 +71,13 @@ Dim level for Initial Flash, optionally for quick settings tile and volume button press Volume Buttons Service Activate the service for toggling the flashlight when pressing both volume buttons. + Volume Button Dimming + After pressing both volume buttons, quickly adjust the flashlight\'s dim level within a customizable amount of time Set Initial Level New: %1$s\nCurrent: %2$s Cancel + Set Timeout Duration + New: %1$s seconds\nCurrent: %2$s seconds Vibration Tactile buttons Vibrate on button clicks and seekbar swipes diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index bedcd68..0b63b23 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -53,6 +53,12 @@ app:summary="@string/preference_item_volume_buttons_summary" app:icon="@drawable/ic_button_service" /> + +