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.os.Bundle
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.isVisible
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.LICENSE_EVENT_BUS import com.simplemobiletools.commons.helpers.LICENSE_EVENT_BUS
import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA
@ -183,7 +182,7 @@ class MainActivity : SimpleActivity() {
private fun setupCameraImpl() { private fun setupCameraImpl() {
mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener { mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener {
override fun onTorchEnabled(isEnabled: Boolean) { override fun onTorchEnabled(isEnabled: Boolean) {
if (mCameraImpl?.supportsBrightnessControl() == true) { if (mCameraImpl!!.supportsBrightnessControl()) {
brightness_bar.beVisibleIf(isEnabled) 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 import org.greenrobot.eventbus.EventBus
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
internal class PostMarshmallowCamera( internal class MarshmallowPlusCameraFlash(
private val context: Context, 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 val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
private var cameraId: String? = null private var cameraId: String? = null
@ -35,12 +35,13 @@ internal class PostMarshmallowCamera(
} }
} }
fun toggleMarshmallowFlashlight(enable: Boolean) { override fun toggleFlashlight(enable: Boolean) {
try { try {
manager.setTorchMode(cameraId!!, enable) if (supportsBrightnessControl() && enable) {
if (enable) {
val brightnessLevel = getCurrentBrightnessLevel() val brightnessLevel = getCurrentBrightnessLevel()
changeTorchBrightness(brightnessLevel) changeTorchBrightness(brightnessLevel)
} else {
manager.setTorchMode(cameraId!!, enable)
} }
} catch (e: Exception) { } catch (e: Exception) {
context.showErrorToast(e) context.showErrorToast(e)
@ -51,13 +52,13 @@ internal class PostMarshmallowCamera(
} }
} }
fun changeTorchBrightness(level: Int) { override fun changeTorchBrightness(level: Int) {
if (isTiramisuPlus()) { if (isTiramisuPlus()) {
manager.turnOnTorchWithStrengthLevel(cameraId!!, level) manager.turnOnTorchWithStrengthLevel(cameraId!!, level)
} }
} }
fun getMaximumBrightnessLevel(): Int { override fun getMaximumBrightnessLevel(): Int {
return if (isTiramisuPlus()) { return if (isTiramisuPlus()) {
val characteristics = manager.getCameraCharacteristics(cameraId!!) val characteristics = manager.getCameraCharacteristics(cameraId!!)
characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: MIN_BRIGHTNESS_LEVEL 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() val maxBrightnessLevel = getMaximumBrightnessLevel()
return maxBrightnessLevel > MIN_BRIGHTNESS_LEVEL return maxBrightnessLevel > MIN_BRIGHTNESS_LEVEL
} }
fun getCurrentBrightnessLevel(): Int { override fun getCurrentBrightnessLevel(): Int {
var brightnessLevel = context.config.brightnessLevel var brightnessLevel = context.config.brightnessLevel
if (brightnessLevel == DEFAULT_BRIGHTNESS_LEVEL) { if (brightnessLevel == DEFAULT_BRIGHTNESS_LEVEL) {
brightnessLevel = getMaximumBrightnessLevel() brightnessLevel = getMaximumBrightnessLevel()
@ -79,11 +80,15 @@ internal class PostMarshmallowCamera(
return brightnessLevel return brightnessLevel
} }
fun initialize() { override fun initialize() {
manager.registerTorchCallback(torchCallback, Handler(context.mainLooper)) manager.registerTorchCallback(torchCallback, Handler(context.mainLooper))
} }
fun cleanUp() { override fun unregisterListeners() {
manager.unregisterTorchCallback(torchCallback) manager.unregisterTorchCallback(torchCallback)
} }
override fun release() {
cameraTorchListener = null
}
} }

View File

@ -1,36 +1,29 @@
package com.simplemobiletools.flashlight.helpers package com.simplemobiletools.flashlight.helpers
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.Camera
import android.os.Handler import android.os.Handler
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.isMarshmallowPlus import com.simplemobiletools.commons.helpers.isMarshmallowPlus
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.flashlight.R import com.simplemobiletools.flashlight.R
import com.simplemobiletools.flashlight.extensions.config import com.simplemobiletools.flashlight.extensions.config
import com.simplemobiletools.flashlight.extensions.updateWidgets import com.simplemobiletools.flashlight.extensions.updateWidgets
import com.simplemobiletools.flashlight.models.Events import com.simplemobiletools.flashlight.models.Events
import org.greenrobot.eventbus.EventBus 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 var stroboFrequency = 1000L
companion object { companion object {
var isFlashlightOn = false 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 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 shouldEnableFlashlight = false
private var shouldEnableStroboscope = false private var shouldEnableStroboscope = false
private var shouldEnableSOS = false private var shouldEnableSOS = false
private var isStroboSOS = false // are we sending SOS, or casual stroboscope? private var isStroboSOS = false // are we sending SOS, or casual stroboscope?
private var marshmallowCamera: PostMarshmallowCamera? = null private var cameraFlash: CameraFlash? = null
@Volatile @Volatile
private var shouldStroboscopeStop = false private var shouldStroboscopeStop = false
@ -45,7 +38,6 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
init { init {
isMarshmallow = isMarshmallowPlus()
handleCameraSetup() handleCameraSetup()
stroboFrequency = context.config.stroboscopeFrequency stroboFrequency = context.config.stroboscopeFrequency
} }
@ -56,6 +48,9 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
fun toggleStroboscope(): Boolean { fun toggleStroboscope(): Boolean {
handleCameraSetup()
cameraFlash!!.unregisterListeners()
if (isSOSRunning) { if (isSOSRunning) {
stopSOS() stopSOS()
shouldEnableStroboscope = true shouldEnableStroboscope = true
@ -86,6 +81,9 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
fun toggleSOS(): Boolean { fun toggleSOS(): Boolean {
handleCameraSetup()
cameraFlash!!.unregisterListeners()
if (isStroboscopeRunning) { if (isStroboscopeRunning) {
stopStroboscope() stopStroboscope()
shouldEnableSOS = true shouldEnableSOS = true
@ -120,59 +118,26 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
private fun tryInitCamera(): Boolean { private fun tryInitCamera(): Boolean {
if (!isNougatPlus()) { handleCameraSetup()
if (camera == null) { if (cameraFlash == null) {
initCamera() context.toast(R.string.camera_error)
} return false
if (camera == null) {
context.toast(R.string.camera_error)
return false
}
} }
return true return true
} }
fun handleCameraSetup() { 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 { try {
camera = Camera.open() if (cameraFlash == null) {
params = camera!!.parameters cameraFlash = if (isMarshmallowPlus()) MarshmallowPlusCameraFlash(context, cameraTorchListener) else LollipopCameraFlash()
params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF }
camera!!.parameters = params
} catch (e: Exception) { } catch (e: Exception) {
EventBus.getDefault().post(Events.CameraUnavailable()) EventBus.getDefault().post(Events.CameraUnavailable())
} }
} }
private fun checkFlashlight() { private fun checkFlashlight() {
if (camera == null) { handleCameraSetup()
handleCameraSetup()
}
if (isFlashlightOn) { if (isFlashlightOn) {
enableFlashlight() enableFlashlight()
@ -188,30 +153,16 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
return return
} }
if (isMarshmallow) { try {
toggleMarshmallowFlashlight(true) cameraFlash!!.initialize()
} else { cameraFlash!!.toggleFlashlight(true)
try { } catch (e: Exception) {
if (camera == null || params == null || camera!!.parameters == null) { context.showErrorToast(e)
return disableFlashlight()
}
} catch (e: Exception) {
return
}
params!!.flashMode = Camera.Parameters.FLASH_MODE_TORCH
camera!!.parameters = params
try {
camera!!.startPreview()
} catch (e: Exception) {
context.showErrorToast(e)
disableFlashlight()
}
} }
val mainRunnable = Runnable { stateChanged(true) } val mainRunnable = Runnable { stateChanged(true) }
Handler(context.mainLooper).post(mainRunnable) Handler(context.mainLooper).post(mainRunnable)
marshmallowCamera?.initialize()
} }
private fun disableFlashlight() { private fun disableFlashlight() {
@ -219,22 +170,13 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
return return
} }
if (isMarshmallow) { try {
toggleMarshmallowFlashlight(false) cameraFlash!!.toggleFlashlight(false)
} else { } catch (e: Exception) {
try { context.showErrorToast(e)
if (camera == null || params == null || camera!!.parameters == null) { disableFlashlight()
return
}
} catch (e: Exception) {
return
}
params!!.flashMode = Camera.Parameters.FLASH_MODE_OFF
camera!!.parameters = params
} }
stateChanged(false) stateChanged(false)
releaseCamera()
} }
private fun stateChanged(isEnabled: Boolean) { private fun stateChanged(isEnabled: Boolean) {
@ -243,21 +185,19 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
context.updateWidgets(isEnabled) context.updateWidgets(isEnabled)
} }
private fun toggleMarshmallowFlashlight(enable: Boolean) {
marshmallowCamera!!.toggleMarshmallowFlashlight(enable)
}
fun releaseCamera() { fun releaseCamera() {
cameraFlash?.unregisterListeners()
if (isFlashlightOn) { if (isFlashlightOn) {
disableFlashlight() disableFlashlight()
} }
camera?.release() cameraFlash?.release()
camera = null cameraFlash = null
cameraTorchListener = null
isFlashlightOn = false isFlashlightOn = false
shouldStroboscopeStop = true shouldStroboscopeStop = true
marshmallowCamera?.cleanUp()
} }
private val stroboscope = Runnable { private val stroboscope = Runnable {
@ -273,56 +213,28 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
var sosIndex = 0 var sosIndex = 0
if (isNougatPlus()) { handleCameraSetup()
while (!shouldStroboscopeStop) { 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()
}
try { try {
val torchOn = camera!!.parameters ?: return@Runnable cameraFlash!!.toggleFlashlight(true)
val torchOff = camera!!.parameters val onDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency
torchOn.flashMode = Camera.Parameters.FLASH_MODE_TORCH Thread.sleep(onDuration)
torchOff.flashMode = Camera.Parameters.FLASH_MODE_OFF cameraFlash!!.toggleFlashlight(false)
val offDuration = if (isStroboSOS) SOS[sosIndex++ % SOS.size] else stroboFrequency
val dummy = SurfaceTexture(1) Thread.sleep(offDuration)
camera!!.setPreviewTexture(dummy) } catch (e: Exception) {
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 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 shouldStroboscopeStop = false
if (isStroboSOS) { if (isStroboSOS) {
isSOSRunning = false isSOSRunning = false
@ -347,26 +259,18 @@ class MyCameraImpl private constructor(val context: Context, private val cameraT
} }
fun getMaximumBrightnessLevel(): Int { fun getMaximumBrightnessLevel(): Int {
return if (isMarshmallow) { return cameraFlash!!.getMaximumBrightnessLevel()
marshmallowCamera?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL
} else MIN_BRIGHTNESS_LEVEL
} }
fun getCurrentBrightnessLevel(): Int { fun getCurrentBrightnessLevel(): Int {
return if (isMarshmallow) { return cameraFlash!!.getCurrentBrightnessLevel()
marshmallowCamera?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL
} else MIN_BRIGHTNESS_LEVEL
} }
fun supportsBrightnessControl(): Boolean { fun supportsBrightnessControl(): Boolean {
return if (isMarshmallow) { return cameraFlash!!.supportsBrightnessControl()
marshmallowCamera?.supportsBrightnessControl() ?: false
} else false
} }
fun updateBrightnessLevel(level: Int) { fun updateBrightnessLevel(level: Int) {
if (isMarshmallow) { cameraFlash!!.changeTorchBrightness(level)
marshmallowCamera?.changeTorchBrightness(level)
}
} }
} }