From cea57e9db00e10900687430773e135ead3b9b20b Mon Sep 17 00:00:00 2001 From: darthpaul Date: Sun, 23 Oct 2022 09:03:16 +0100 Subject: [PATCH 1/5] Add torch brightness level support for Android 13+ --- .../flashlight/activities/MainActivity.kt | 19 ++++++++++++- .../flashlight/helpers/Config.kt | 4 +++ .../flashlight/helpers/Constants.kt | 2 ++ .../flashlight/helpers/MyCameraImpl.kt | 22 +++++++++++++-- ...llowCamera.kt => PostMarshmallowCamera.kt} | 28 ++++++++++++++++++- app/src/main/res/layout/activity_main.xml | 14 ++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) rename app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/{MarshmallowCamera.kt => PostMarshmallowCamera.kt} (51%) diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt index dc60046..96ec87b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -4,12 +4,12 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.ShortcutInfo -import android.content.res.ColorStateList import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.view.WindowManager import android.widget.ImageView +import androidx.core.view.isVisible import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.LICENSE_EVENT_BUS import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA @@ -19,6 +19,7 @@ import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.flashlight.BuildConfig import com.simplemobiletools.flashlight.R import com.simplemobiletools.flashlight.extensions.config +import com.simplemobiletools.flashlight.helpers.DEFAULT_BRIGHTNESS_LEVEL import com.simplemobiletools.flashlight.helpers.MyCameraImpl import com.simplemobiletools.flashlight.models.Events import kotlinx.android.synthetic.main.activity_main.* @@ -53,6 +54,10 @@ class MainActivity : SimpleActivity() { flashlight_btn.setOnClickListener { mCameraImpl!!.toggleFlashlight() + if (mCameraImpl?.supportsBrightnessControl() == true) { + brightness_bar.beInvisibleIf(brightness_bar.isVisible) + stroboscope_bar.beInvisible() + } } sos_btn.setOnClickListener { @@ -183,6 +188,7 @@ class MainActivity : SimpleActivity() { if (config.turnFlashlightOn) { mCameraImpl!!.enableFlashlight() } + setupBrightness() } private fun setupStroboscope() { @@ -211,12 +217,23 @@ class MainActivity : SimpleActivity() { } } + private fun setupBrightness() { + brightness_bar.max = mCameraImpl?.getMaximumBrightnessLevel() ?: DEFAULT_BRIGHTNESS_LEVEL + brightness_bar.progress = config.brightnessLevel + brightness_bar.onSeekBarChangeListener { level -> + val newLevel = level.coerceAtLeast(DEFAULT_BRIGHTNESS_LEVEL) + mCameraImpl?.updateBrightnessLevel(newLevel) + config.brightnessLevel = newLevel + } + } + private fun cameraPermissionGranted(isSOS: Boolean) { if (isSOS) { val isSOSRunning = mCameraImpl!!.toggleSOS() sos_btn.setTextColor(if (isSOSRunning) getProperPrimaryColor() else getContrastColor()) } else if (mCameraImpl!!.toggleStroboscope()) { stroboscope_bar.beInvisibleIf(stroboscope_bar.isVisible()) + brightness_bar.beInvisible() changeIconColor(if (stroboscope_bar.isVisible()) getProperPrimaryColor() else getContrastColor(), stroboscope_btn) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt index e52fcd0..4e24b45 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt @@ -40,4 +40,8 @@ class Config(context: Context) : BaseConfig(context) { var forcePortraitMode: Boolean get() = prefs.getBoolean(FORCE_PORTRAIT_MODE, true) set(forcePortraitMode) = prefs.edit().putBoolean(FORCE_PORTRAIT_MODE, forcePortraitMode).apply() + + var brightnessLevel: Int + get() = prefs.getInt(BRIGHTNESS_LEVEL, DEFAULT_BRIGHTNESS_LEVEL) + set(brightnessLevel) = prefs.edit().putInt(BRIGHTNESS_LEVEL, brightnessLevel).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt index e954a73..ce944bd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt @@ -11,3 +11,5 @@ const val STROBOSCOPE_FREQUENCY = "stroboscope_frequency" const val STROBOSCOPE_PROGRESS = "stroboscope_progress" const val FORCE_PORTRAIT_MODE = "force_portrait_mode" const val SOS = "sos" +const val BRIGHTNESS_LEVEL = "brightness_level" +const val DEFAULT_BRIGHTNESS_LEVEL = 1 diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt index 6ee4288..f619a3a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -29,7 +29,7 @@ class MyCameraImpl(val context: Context) { private var shouldEnableSOS = false private var isStroboSOS = false // are we sending SOS, or casual stroboscope? - private var marshmallowCamera: MarshmallowCamera? = null + private var marshmallowCamera: PostMarshmallowCamera? = null @Volatile private var shouldStroboscopeStop = false @@ -142,7 +142,7 @@ class MyCameraImpl(val context: Context) { private fun setupMarshmallowCamera() { if (marshmallowCamera == null) { - marshmallowCamera = MarshmallowCamera(context) + marshmallowCamera = PostMarshmallowCamera(context) } } @@ -341,4 +341,22 @@ class MyCameraImpl(val context: Context) { } } } + + fun getMaximumBrightnessLevel(): Int { + return if (isMarshmallow) { + marshmallowCamera?.getMaximumBrightnessLevel() ?: DEFAULT_BRIGHTNESS_LEVEL + } else DEFAULT_BRIGHTNESS_LEVEL + } + + fun supportsBrightnessControl(): Boolean { + return if (isMarshmallow) { + marshmallowCamera?.supportsBrightnessControl() ?: false + } else false + } + + fun updateBrightnessLevel(level: Int) { + if (isMarshmallow) { + marshmallowCamera?.changeTorchBrightness(level) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowCamera.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt similarity index 51% rename from app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowCamera.kt rename to app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt index 1ab3d2e..2d02572 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowCamera.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt @@ -2,14 +2,17 @@ package com.simplemobiletools.flashlight.helpers import android.annotation.TargetApi import android.content.Context +import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.os.Build import android.os.Handler import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.helpers.isTiramisuPlus +import com.simplemobiletools.flashlight.extensions.config import com.simplemobiletools.flashlight.models.Events import org.greenrobot.eventbus.EventBus -internal class MarshmallowCamera constructor(val context: Context) { +internal class PostMarshmallowCamera constructor(val context: Context) { private val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager private var cameraId: String? = null @@ -26,6 +29,9 @@ internal class MarshmallowCamera constructor(val context: Context) { fun toggleMarshmallowFlashlight(enable: Boolean) { try { manager.setTorchMode(cameraId!!, enable) + if (enable) { + changeTorchBrightness(context.config.brightnessLevel) + } } catch (e: Exception) { context.showErrorToast(e) val mainRunnable = Runnable { @@ -34,4 +40,24 @@ internal class MarshmallowCamera constructor(val context: Context) { Handler(context.mainLooper).post(mainRunnable) } } + + fun changeTorchBrightness(level: Int) { + if (isTiramisuPlus()) { + manager.turnOnTorchWithStrengthLevel(cameraId!!, level) + } + } + + fun getMaximumBrightnessLevel(): Int { + return if (isTiramisuPlus()) { + val characteristics = manager.getCameraCharacteristics(cameraId!!) + characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: DEFAULT_BRIGHTNESS_LEVEL + } else { + DEFAULT_BRIGHTNESS_LEVEL + } + } + + fun supportsBrightnessControl(): Boolean { + val maxBrightnessLevel = getMaximumBrightnessLevel() + return maxBrightnessLevel > DEFAULT_BRIGHTNESS_LEVEL + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 76ed2e0..263080f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -105,6 +105,20 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/stroboscope_btn" /> + + From 75eca9e3f0d7f53a84613072db6605bef2941f03 Mon Sep 17 00:00:00 2001 From: darthpaul Date: Wed, 26 Oct 2022 14:16:27 +0100 Subject: [PATCH 2/5] set brightness level to maximum by default --- .../flashlight/activities/MainActivity.kt | 8 ++++---- .../flashlight/helpers/Constants.kt | 3 ++- .../flashlight/helpers/MyCameraImpl.kt | 10 ++++++++-- .../flashlight/helpers/PostMarshmallowCamera.kt | 17 +++++++++++++---- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt index 96ec87b..8c23fdf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -19,7 +19,7 @@ import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.flashlight.BuildConfig import com.simplemobiletools.flashlight.R import com.simplemobiletools.flashlight.extensions.config -import com.simplemobiletools.flashlight.helpers.DEFAULT_BRIGHTNESS_LEVEL +import com.simplemobiletools.flashlight.helpers.MIN_BRIGHTNESS_LEVEL import com.simplemobiletools.flashlight.helpers.MyCameraImpl import com.simplemobiletools.flashlight.models.Events import kotlinx.android.synthetic.main.activity_main.* @@ -218,10 +218,10 @@ class MainActivity : SimpleActivity() { } private fun setupBrightness() { - brightness_bar.max = mCameraImpl?.getMaximumBrightnessLevel() ?: DEFAULT_BRIGHTNESS_LEVEL - brightness_bar.progress = config.brightnessLevel + brightness_bar.max = mCameraImpl?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL + brightness_bar.progress = mCameraImpl?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL brightness_bar.onSeekBarChangeListener { level -> - val newLevel = level.coerceAtLeast(DEFAULT_BRIGHTNESS_LEVEL) + val newLevel = level.coerceAtLeast(MIN_BRIGHTNESS_LEVEL) mCameraImpl?.updateBrightnessLevel(newLevel) config.brightnessLevel = newLevel } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt index ce944bd..1517a9c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt @@ -12,4 +12,5 @@ const val STROBOSCOPE_PROGRESS = "stroboscope_progress" const val FORCE_PORTRAIT_MODE = "force_portrait_mode" const val SOS = "sos" const val BRIGHTNESS_LEVEL = "brightness_level" -const val DEFAULT_BRIGHTNESS_LEVEL = 1 +const val MIN_BRIGHTNESS_LEVEL = 1 +const val DEFAULT_BRIGHTNESS_LEVEL = -1 diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt index f619a3a..a3a54a2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -344,8 +344,14 @@ class MyCameraImpl(val context: Context) { fun getMaximumBrightnessLevel(): Int { return if (isMarshmallow) { - marshmallowCamera?.getMaximumBrightnessLevel() ?: DEFAULT_BRIGHTNESS_LEVEL - } else DEFAULT_BRIGHTNESS_LEVEL + marshmallowCamera?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL + } else MIN_BRIGHTNESS_LEVEL + } + + fun getCurrentBrightnessLevel(): Int { + return if (isMarshmallow) { + marshmallowCamera?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL + } else MIN_BRIGHTNESS_LEVEL } fun supportsBrightnessControl(): Boolean { diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt index 2d02572..e63a0ba 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt @@ -30,7 +30,8 @@ internal class PostMarshmallowCamera constructor(val context: Context) { try { manager.setTorchMode(cameraId!!, enable) if (enable) { - changeTorchBrightness(context.config.brightnessLevel) + val brightnessLevel = getCurrentBrightnessLevel() + changeTorchBrightness(brightnessLevel) } } catch (e: Exception) { context.showErrorToast(e) @@ -50,14 +51,22 @@ internal class PostMarshmallowCamera constructor(val context: Context) { fun getMaximumBrightnessLevel(): Int { return if (isTiramisuPlus()) { val characteristics = manager.getCameraCharacteristics(cameraId!!) - characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: DEFAULT_BRIGHTNESS_LEVEL + characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: MIN_BRIGHTNESS_LEVEL } else { - DEFAULT_BRIGHTNESS_LEVEL + MIN_BRIGHTNESS_LEVEL } } fun supportsBrightnessControl(): Boolean { val maxBrightnessLevel = getMaximumBrightnessLevel() - return maxBrightnessLevel > DEFAULT_BRIGHTNESS_LEVEL + return maxBrightnessLevel > MIN_BRIGHTNESS_LEVEL + } + + fun getCurrentBrightnessLevel(): Int { + var brightnessLevel = context.config.brightnessLevel + if (brightnessLevel == DEFAULT_BRIGHTNESS_LEVEL) { + brightnessLevel = getMaximumBrightnessLevel() + } + return brightnessLevel } } From 87b678e4bbff0cacb77700fbd68a84eab723046e Mon Sep 17 00:00:00 2001 From: darthpaul Date: Wed, 9 Nov 2022 09:36:08 +0000 Subject: [PATCH 3/5] show/hide brightness seekbar from camera callback --- .../flashlight/activities/MainActivity.kt | 14 ++++++----- .../flashlight/helpers/CameraTorchListener.kt | 5 ++++ .../flashlight/helpers/MyCameraImpl.kt | 10 +++++--- .../helpers/PostMarshmallowCamera.kt | 23 ++++++++++++++++--- 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraTorchListener.kt diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt index 8c23fdf..8d3c9ff 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -19,6 +19,7 @@ import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.flashlight.BuildConfig import com.simplemobiletools.flashlight.R import com.simplemobiletools.flashlight.extensions.config +import com.simplemobiletools.flashlight.helpers.CameraTorchListener import com.simplemobiletools.flashlight.helpers.MIN_BRIGHTNESS_LEVEL import com.simplemobiletools.flashlight.helpers.MyCameraImpl import com.simplemobiletools.flashlight.models.Events @@ -54,10 +55,6 @@ class MainActivity : SimpleActivity() { flashlight_btn.setOnClickListener { mCameraImpl!!.toggleFlashlight() - if (mCameraImpl?.supportsBrightnessControl() == true) { - brightness_bar.beInvisibleIf(brightness_bar.isVisible) - stroboscope_bar.beInvisible() - } } sos_btn.setOnClickListener { @@ -184,7 +181,13 @@ class MainActivity : SimpleActivity() { } private fun setupCameraImpl() { - mCameraImpl = MyCameraImpl.newInstance(this) + mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener { + override fun onTorchEnabled(isEnabled: Boolean) { + if (mCameraImpl?.supportsBrightnessControl() == true) { + brightness_bar.beVisibleIf(isEnabled) + } + } + }) if (config.turnFlashlightOn) { mCameraImpl!!.enableFlashlight() } @@ -233,7 +236,6 @@ class MainActivity : SimpleActivity() { sos_btn.setTextColor(if (isSOSRunning) getProperPrimaryColor() else getContrastColor()) } else if (mCameraImpl!!.toggleStroboscope()) { stroboscope_bar.beInvisibleIf(stroboscope_bar.isVisible()) - brightness_bar.beInvisible() changeIconColor(if (stroboscope_bar.isVisible()) getProperPrimaryColor() else getContrastColor(), stroboscope_btn) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraTorchListener.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraTorchListener.kt new file mode 100644 index 0000000..481db07 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraTorchListener.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.flashlight.helpers + +interface CameraTorchListener { + fun onTorchEnabled(isEnabled:Boolean) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt index a3a54a2..d9b5e52 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -1,5 +1,6 @@ package com.simplemobiletools.flashlight.helpers +import android.annotation.SuppressLint import android.content.Context import android.graphics.SurfaceTexture import android.hardware.Camera @@ -14,7 +15,7 @@ import com.simplemobiletools.flashlight.extensions.updateWidgets import com.simplemobiletools.flashlight.models.Events import org.greenrobot.eventbus.EventBus -class MyCameraImpl(val context: Context) { +class MyCameraImpl private constructor(val context: Context, private val cameraTorchListener: CameraTorchListener?) { var stroboFrequency = 1000L companion object { @@ -40,7 +41,7 @@ class MyCameraImpl(val context: Context) { @Volatile private var isSOSRunning = false - fun newInstance(context: Context) = MyCameraImpl(context) + fun newInstance(context: Context, cameraTorchListener: CameraTorchListener? = null) = MyCameraImpl(context, cameraTorchListener) } init { @@ -140,9 +141,10 @@ class MyCameraImpl(val context: Context) { } } + @SuppressLint("NewApi") private fun setupMarshmallowCamera() { if (marshmallowCamera == null) { - marshmallowCamera = PostMarshmallowCamera(context) + marshmallowCamera = PostMarshmallowCamera(context, cameraTorchListener) } } @@ -209,6 +211,7 @@ class MyCameraImpl(val context: Context) { val mainRunnable = Runnable { stateChanged(true) } Handler(context.mainLooper).post(mainRunnable) + marshmallowCamera?.initialize() } private fun disableFlashlight() { @@ -254,6 +257,7 @@ class MyCameraImpl(val context: Context) { isFlashlightOn = false shouldStroboscopeStop = true + marshmallowCamera?.cleanUp() } private val stroboscope = Runnable { diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt index e63a0ba..8ec920c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt @@ -1,22 +1,32 @@ package com.simplemobiletools.flashlight.helpers -import android.annotation.TargetApi import android.content.Context import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.os.Build import android.os.Handler +import androidx.annotation.RequiresApi import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.helpers.isTiramisuPlus import com.simplemobiletools.flashlight.extensions.config import com.simplemobiletools.flashlight.models.Events import org.greenrobot.eventbus.EventBus -internal class PostMarshmallowCamera constructor(val context: Context) { +@RequiresApi(Build.VERSION_CODES.M) +internal class PostMarshmallowCamera( + private val context: Context, + private val cameraTorchListener: CameraTorchListener? = null, +) { private val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager private var cameraId: String? = null + private val torchCallback = object : CameraManager.TorchCallback() { + override fun onTorchModeChanged(cameraId: String, enabled: Boolean) { + cameraTorchListener?.onTorchEnabled(enabled) + } + } + init { try { cameraId = manager.cameraIdList[0] ?: "0" @@ -25,7 +35,6 @@ internal class PostMarshmallowCamera constructor(val context: Context) { } } - @TargetApi(Build.VERSION_CODES.M) fun toggleMarshmallowFlashlight(enable: Boolean) { try { manager.setTorchMode(cameraId!!, enable) @@ -69,4 +78,12 @@ internal class PostMarshmallowCamera constructor(val context: Context) { } return brightnessLevel } + + fun initialize() { + manager.registerTorchCallback(torchCallback, Handler(context.mainLooper)) + } + + fun cleanUp() { + manager.unregisterTorchCallback(torchCallback) + } } From 98c0f0578e56e5b9160b6634238882c149f4dded Mon Sep 17 00:00:00 2001 From: darthpaul Date: Mon, 14 Nov 2022 19:17:00 +0000 Subject: [PATCH 4/5] cleanup flash implementation - fix issues with brightness control - add separate classes for Android 5 and Android 6+ for simplicity - properly cleanup camera in onDestory and also when stroboscope/flashlight mode are disabled --- .../flashlight/activities/MainActivity.kt | 3 +- .../flashlight/helpers/CameraFlash.kt | 12 ++ .../flashlight/helpers/LollipopCameraFlash.kt | 37 ++++ ...amera.kt => MarshmallowPlusCameraFlash.kt} | 29 +-- .../flashlight/helpers/MyCameraImpl.kt | 202 +++++------------- 5 files changed, 120 insertions(+), 163 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraFlash.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/LollipopCameraFlash.kt rename app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/{PostMarshmallowCamera.kt => MarshmallowPlusCameraFlash.kt} (78%) diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt index 8d3c9ff..77a189e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -9,7 +9,6 @@ import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.view.WindowManager import android.widget.ImageView -import androidx.core.view.isVisible import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.LICENSE_EVENT_BUS import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA @@ -183,7 +182,7 @@ class MainActivity : SimpleActivity() { private fun setupCameraImpl() { mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener { override fun onTorchEnabled(isEnabled: Boolean) { - if (mCameraImpl?.supportsBrightnessControl() == true) { + if (mCameraImpl!!.supportsBrightnessControl()) { brightness_bar.beVisibleIf(isEnabled) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraFlash.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraFlash.kt new file mode 100644 index 0000000..7aa40b4 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/CameraFlash.kt @@ -0,0 +1,12 @@ +package com.simplemobiletools.flashlight.helpers + +interface CameraFlash { + fun initialize() + fun toggleFlashlight(enable: Boolean) + fun changeTorchBrightness(level: Int) {} + fun getMaximumBrightnessLevel(): Int = DEFAULT_BRIGHTNESS_LEVEL + fun supportsBrightnessControl(): Boolean = false + fun getCurrentBrightnessLevel(): Int = DEFAULT_BRIGHTNESS_LEVEL + fun unregisterListeners(){} + fun release(){} +} diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/LollipopCameraFlash.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/LollipopCameraFlash.kt new file mode 100644 index 0000000..b4a763f --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/LollipopCameraFlash.kt @@ -0,0 +1,37 @@ +@file:Suppress("DEPRECATION") + +package com.simplemobiletools.flashlight.helpers + +import android.graphics.SurfaceTexture +import android.hardware.Camera + +class LollipopCameraFlash : CameraFlash { + private var camera: Camera? = null + private var params: Camera.Parameters? = null + + override fun toggleFlashlight(enable: Boolean) { + if (camera == null || params == null || camera!!.parameters == null) { + return + } + val flashMode = if (enable) Camera.Parameters.FLASH_MODE_ON else Camera.Parameters.FLASH_MODE_OFF + params!!.flashMode = flashMode + camera!!.parameters = params + if (enable) { + val dummy = SurfaceTexture(1) + camera!!.setPreviewTexture(dummy) + camera!!.startPreview() + } + } + + override fun initialize() { + camera = Camera.open() + params = camera!!.parameters + params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF + camera!!.parameters = params + } + + override fun release() { + camera!!.release() + camera = null + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt similarity index 78% rename from app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt rename to app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt index 8ec920c..cb4ea61 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/PostMarshmallowCamera.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt @@ -13,10 +13,10 @@ import com.simplemobiletools.flashlight.models.Events import org.greenrobot.eventbus.EventBus @RequiresApi(Build.VERSION_CODES.M) -internal class PostMarshmallowCamera( +internal class MarshmallowPlusCameraFlash( private val context: Context, - private val cameraTorchListener: CameraTorchListener? = null, -) { + private var cameraTorchListener: CameraTorchListener? = null, +) : CameraFlash { private val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager private var cameraId: String? = null @@ -35,12 +35,13 @@ internal class PostMarshmallowCamera( } } - fun toggleMarshmallowFlashlight(enable: Boolean) { + override fun toggleFlashlight(enable: Boolean) { try { - manager.setTorchMode(cameraId!!, enable) - if (enable) { + if (supportsBrightnessControl() && enable) { val brightnessLevel = getCurrentBrightnessLevel() changeTorchBrightness(brightnessLevel) + } else { + manager.setTorchMode(cameraId!!, enable) } } catch (e: Exception) { context.showErrorToast(e) @@ -51,13 +52,13 @@ internal class PostMarshmallowCamera( } } - fun changeTorchBrightness(level: Int) { + override fun changeTorchBrightness(level: Int) { if (isTiramisuPlus()) { manager.turnOnTorchWithStrengthLevel(cameraId!!, level) } } - fun getMaximumBrightnessLevel(): Int { + override fun getMaximumBrightnessLevel(): Int { return if (isTiramisuPlus()) { val characteristics = manager.getCameraCharacteristics(cameraId!!) characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: MIN_BRIGHTNESS_LEVEL @@ -66,12 +67,12 @@ internal class PostMarshmallowCamera( } } - fun supportsBrightnessControl(): Boolean { + override fun supportsBrightnessControl(): Boolean { val maxBrightnessLevel = getMaximumBrightnessLevel() return maxBrightnessLevel > MIN_BRIGHTNESS_LEVEL } - fun getCurrentBrightnessLevel(): Int { + override fun getCurrentBrightnessLevel(): Int { var brightnessLevel = context.config.brightnessLevel if (brightnessLevel == DEFAULT_BRIGHTNESS_LEVEL) { brightnessLevel = getMaximumBrightnessLevel() @@ -79,11 +80,15 @@ internal class PostMarshmallowCamera( return brightnessLevel } - fun initialize() { + override fun initialize() { manager.registerTorchCallback(torchCallback, Handler(context.mainLooper)) } - fun cleanUp() { + override fun unregisterListeners() { manager.unregisterTorchCallback(torchCallback) } + + override fun release() { + cameraTorchListener = null + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt index d9b5e52..8189a30 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -1,36 +1,29 @@ package com.simplemobiletools.flashlight.helpers -import android.annotation.SuppressLint import android.content.Context -import android.graphics.SurfaceTexture -import android.hardware.Camera import android.os.Handler import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.helpers.isMarshmallowPlus -import com.simplemobiletools.commons.helpers.isNougatPlus import com.simplemobiletools.flashlight.R import com.simplemobiletools.flashlight.extensions.config import com.simplemobiletools.flashlight.extensions.updateWidgets import com.simplemobiletools.flashlight.models.Events import org.greenrobot.eventbus.EventBus -class MyCameraImpl private constructor(val context: Context, private val cameraTorchListener: CameraTorchListener?) { +class MyCameraImpl private constructor(val context: Context, private var cameraTorchListener: CameraTorchListener? = null) { var stroboFrequency = 1000L companion object { var isFlashlightOn = false private val SOS = arrayListOf(250L, 250L, 250L, 250L, 250L, 250L, 500L, 250L, 500L, 250L, 500L, 250L, 250L, 250L, 250L, 250L, 250L, 1000L) - private var camera: Camera? = null - private var params: Camera.Parameters? = null - private var isMarshmallow = false private var shouldEnableFlashlight = false private var shouldEnableStroboscope = false private var shouldEnableSOS = false private var isStroboSOS = false // are we sending SOS, or casual stroboscope? - private var marshmallowCamera: PostMarshmallowCamera? = null + private var cameraFlash: CameraFlash? = null @Volatile private var shouldStroboscopeStop = false @@ -45,7 +38,6 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } init { - isMarshmallow = isMarshmallowPlus() handleCameraSetup() stroboFrequency = context.config.stroboscopeFrequency } @@ -56,6 +48,9 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } fun toggleStroboscope(): Boolean { + handleCameraSetup() + cameraFlash!!.unregisterListeners() + if (isSOSRunning) { stopSOS() shouldEnableStroboscope = true @@ -86,6 +81,9 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } fun toggleSOS(): Boolean { + handleCameraSetup() + cameraFlash!!.unregisterListeners() + if (isStroboscopeRunning) { stopStroboscope() shouldEnableSOS = true @@ -120,59 +118,26 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } private fun tryInitCamera(): Boolean { - if (!isNougatPlus()) { - if (camera == null) { - initCamera() - } - - if (camera == null) { - context.toast(R.string.camera_error) - return false - } + handleCameraSetup() + if (cameraFlash == null) { + context.toast(R.string.camera_error) + return false } return true } fun handleCameraSetup() { - if (isMarshmallow) { - setupMarshmallowCamera() - } else { - setupCamera() - } - } - - @SuppressLint("NewApi") - private fun setupMarshmallowCamera() { - if (marshmallowCamera == null) { - marshmallowCamera = PostMarshmallowCamera(context, cameraTorchListener) - } - } - - private fun setupCamera() { - if (isMarshmallow) { - return - } - - if (camera == null) { - initCamera() - } - } - - private fun initCamera() { try { - camera = Camera.open() - params = camera!!.parameters - params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF - camera!!.parameters = params + if (cameraFlash == null) { + cameraFlash = if (isMarshmallowPlus()) MarshmallowPlusCameraFlash(context, cameraTorchListener) else LollipopCameraFlash() + } } catch (e: Exception) { EventBus.getDefault().post(Events.CameraUnavailable()) } } private fun checkFlashlight() { - if (camera == null) { - handleCameraSetup() - } + handleCameraSetup() if (isFlashlightOn) { enableFlashlight() @@ -188,30 +153,16 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT return } - if (isMarshmallow) { - toggleMarshmallowFlashlight(true) - } else { - try { - if (camera == null || params == null || camera!!.parameters == null) { - return - } - } catch (e: Exception) { - return - } - - params!!.flashMode = Camera.Parameters.FLASH_MODE_TORCH - camera!!.parameters = params - try { - camera!!.startPreview() - } catch (e: Exception) { - context.showErrorToast(e) - disableFlashlight() - } + try { + cameraFlash!!.initialize() + cameraFlash!!.toggleFlashlight(true) + } catch (e: Exception) { + context.showErrorToast(e) + disableFlashlight() } val mainRunnable = Runnable { stateChanged(true) } Handler(context.mainLooper).post(mainRunnable) - marshmallowCamera?.initialize() } private fun disableFlashlight() { @@ -219,22 +170,13 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT return } - if (isMarshmallow) { - toggleMarshmallowFlashlight(false) - } else { - try { - if (camera == null || params == null || camera!!.parameters == null) { - return - } - } catch (e: Exception) { - return - } - - params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF - camera!!.parameters = params + try { + cameraFlash!!.toggleFlashlight(false) + } catch (e: Exception) { + context.showErrorToast(e) + disableFlashlight() } stateChanged(false) - releaseCamera() } private fun stateChanged(isEnabled: Boolean) { @@ -243,21 +185,19 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT context.updateWidgets(isEnabled) } - private fun toggleMarshmallowFlashlight(enable: Boolean) { - marshmallowCamera!!.toggleMarshmallowFlashlight(enable) - } - fun releaseCamera() { + cameraFlash?.unregisterListeners() + if (isFlashlightOn) { disableFlashlight() } - camera?.release() - camera = null + cameraFlash?.release() + cameraFlash = null + cameraTorchListener = null isFlashlightOn = false shouldStroboscopeStop = true - marshmallowCamera?.cleanUp() } private val stroboscope = Runnable { @@ -273,56 +213,28 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } var sosIndex = 0 - if (isNougatPlus()) { - while (!shouldStroboscopeStop) { - try { - marshmallowCamera!!.toggleMarshmallowFlashlight(true) - val onDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency - Thread.sleep(onDuration) - marshmallowCamera!!.toggleMarshmallowFlashlight(false) - val offDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency - Thread.sleep(offDuration) - } catch (e: Exception) { - shouldStroboscopeStop = true - } - } - } else { - if (camera == null) { - initCamera() - } - + handleCameraSetup() + while (!shouldStroboscopeStop) { try { - val torchOn = camera!!.parameters ?: return@Runnable - val torchOff = camera!!.parameters - torchOn.flashMode = Camera.Parameters.FLASH_MODE_TORCH - torchOff.flashMode = Camera.Parameters.FLASH_MODE_OFF - - val dummy = SurfaceTexture(1) - camera!!.setPreviewTexture(dummy) - - camera!!.startPreview() - - while (!shouldStroboscopeStop) { - camera!!.parameters = torchOn - val onDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency - Thread.sleep(onDuration) - camera!!.parameters = torchOff - val offDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency - Thread.sleep(offDuration) - } - - if (camera != null) { - camera!!.parameters = torchOff - if (!shouldEnableFlashlight || isMarshmallow) { - camera!!.release() - camera = null - } - } - } catch (e: RuntimeException) { + cameraFlash!!.toggleFlashlight(true) + val onDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency + Thread.sleep(onDuration) + cameraFlash!!.toggleFlashlight(false) + val offDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency + Thread.sleep(offDuration) + } catch (e: Exception) { shouldStroboscopeStop = true } } + // disable flash immediately if stroboscope is stopped and normal flash mode is disabled + if (shouldStroboscopeStop && !shouldEnableFlashlight) { + handleCameraSetup() + cameraFlash!!.toggleFlashlight(false) + cameraFlash!!.release() + cameraFlash = null + } + shouldStroboscopeStop = false if (isStroboSOS) { isSOSRunning = false @@ -347,26 +259,18 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT } fun getMaximumBrightnessLevel(): Int { - return if (isMarshmallow) { - marshmallowCamera?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL - } else MIN_BRIGHTNESS_LEVEL + return cameraFlash!!.getMaximumBrightnessLevel() } fun getCurrentBrightnessLevel(): Int { - return if (isMarshmallow) { - marshmallowCamera?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL - } else MIN_BRIGHTNESS_LEVEL + return cameraFlash!!.getCurrentBrightnessLevel() } fun supportsBrightnessControl(): Boolean { - return if (isMarshmallow) { - marshmallowCamera?.supportsBrightnessControl() ?: false - } else false + return cameraFlash!!.supportsBrightnessControl() } fun updateBrightnessLevel(level: Int) { - if (isMarshmallow) { - marshmallowCamera?.changeTorchBrightness(level) - } + cameraFlash!!.changeTorchBrightness(level) } } From c975ab1e82feb713a6b677442824cd234721addd Mon Sep 17 00:00:00 2001 From: darthpaul Date: Tue, 15 Nov 2022 14:28:35 +0000 Subject: [PATCH 5/5] clear listeners after disabling flash for SOS and stroboscope - this prevents the two sliders for brightness and stroboscope from showing up at the same time --- .../simplemobiletools/flashlight/helpers/MyCameraImpl.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt index 8189a30..034a324 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -49,7 +49,6 @@ class MyCameraImpl private constructor(val context: Context, private var cameraT fun toggleStroboscope(): Boolean { handleCameraSetup() - cameraFlash!!.unregisterListeners() if (isSOSRunning) { stopSOS() @@ -62,6 +61,8 @@ class MyCameraImpl private constructor(val context: Context, private var cameraT disableFlashlight() } + cameraFlash!!.unregisterListeners() + if (!tryInitCamera()) { return false } @@ -82,7 +83,6 @@ class MyCameraImpl private constructor(val context: Context, private var cameraT fun toggleSOS(): Boolean { handleCameraSetup() - cameraFlash!!.unregisterListeners() if (isStroboscopeRunning) { stopStroboscope() @@ -103,6 +103,8 @@ class MyCameraImpl private constructor(val context: Context, private var cameraT disableFlashlight() } + cameraFlash!!.unregisterListeners() + return if (isSOSRunning) { stopSOS() false