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
This commit is contained in:
darthpaul 2022-11-14 19:17:00 +00:00
parent 87b678e4bb
commit 98c0f0578e
5 changed files with 120 additions and 163 deletions

View File

@ -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)
}
}

View File

@ -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(){}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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) {
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()
}
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()
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
}
cameraFlash!!.toggleFlashlight(false)
} catch (e: Exception) {
return
}
params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF
camera!!.parameters = params
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,54 +213,26 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
}
var sosIndex = 0
if (isNougatPlus()) {
handleCameraSetup()
while (!shouldStroboscopeStop) {
try {
marshmallowCamera!!.toggleMarshmallowFlashlight(true)
cameraFlash!!.toggleFlashlight(true)
val onDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency
Thread.sleep(onDuration)
marshmallowCamera!!.toggleMarshmallowFlashlight(false)
cameraFlash!!.toggleFlashlight(false)
val offDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency
Thread.sleep(offDuration)
} catch (e: Exception) {
shouldStroboscopeStop = true
}
}
} else {
if (camera == null) {
initCamera()
}
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) {
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
@ -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)
}
}