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..77a189e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -4,7 +4,6 @@ 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 @@ -19,6 +18,8 @@ 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 import kotlinx.android.synthetic.main.activity_main.* @@ -179,10 +180,17 @@ class MainActivity : SimpleActivity() { } private fun setupCameraImpl() { - mCameraImpl = MyCameraImpl.newInstance(this) + mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener { + override fun onTorchEnabled(isEnabled: Boolean) { + if (mCameraImpl!!.supportsBrightnessControl()) { + brightness_bar.beVisibleIf(isEnabled) + } + } + }) if (config.turnFlashlightOn) { mCameraImpl!!.enableFlashlight() } + setupBrightness() } private fun setupStroboscope() { @@ -211,6 +219,16 @@ class MainActivity : SimpleActivity() { } } + private fun setupBrightness() { + brightness_bar.max = mCameraImpl?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL + brightness_bar.progress = mCameraImpl?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL + brightness_bar.onSeekBarChangeListener { level -> + val newLevel = level.coerceAtLeast(MIN_BRIGHTNESS_LEVEL) + mCameraImpl?.updateBrightnessLevel(newLevel) + config.brightnessLevel = newLevel + } + } + private fun cameraPermissionGranted(isSOS: Boolean) { if (isSOS) { val isSOSRunning = mCameraImpl!!.toggleSOS() 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/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/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..1517a9c 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,6 @@ 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 MIN_BRIGHTNESS_LEVEL = 1 +const val DEFAULT_BRIGHTNESS_LEVEL = -1 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/MarshmallowCamera.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowCamera.kt deleted file mode 100644 index 1ab3d2e..0000000 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowCamera.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.simplemobiletools.flashlight.helpers - -import android.annotation.TargetApi -import android.content.Context -import android.hardware.camera2.CameraManager -import android.os.Build -import android.os.Handler -import com.simplemobiletools.commons.extensions.showErrorToast -import com.simplemobiletools.flashlight.models.Events -import org.greenrobot.eventbus.EventBus - -internal class MarshmallowCamera constructor(val context: Context) { - - private val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager - private var cameraId: String? = null - - init { - try { - cameraId = manager.cameraIdList[0] ?: "0" - } catch (e: Exception) { - context.showErrorToast(e) - } - } - - @TargetApi(Build.VERSION_CODES.M) - fun toggleMarshmallowFlashlight(enable: Boolean) { - try { - manager.setTorchMode(cameraId!!, enable) - } catch (e: Exception) { - context.showErrorToast(e) - val mainRunnable = Runnable { - EventBus.getDefault().post(Events.CameraUnavailable()) - } - Handler(context.mainLooper).post(mainRunnable) - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt new file mode 100644 index 0000000..cb4ea61 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MarshmallowPlusCameraFlash.kt @@ -0,0 +1,94 @@ +package com.simplemobiletools.flashlight.helpers + +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 + +@RequiresApi(Build.VERSION_CODES.M) +internal class MarshmallowPlusCameraFlash( + private val context: Context, + private var cameraTorchListener: CameraTorchListener? = null, +) : CameraFlash { + + 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" + } catch (e: Exception) { + context.showErrorToast(e) + } + } + + override fun toggleFlashlight(enable: Boolean) { + try { + if (supportsBrightnessControl() && enable) { + val brightnessLevel = getCurrentBrightnessLevel() + changeTorchBrightness(brightnessLevel) + } else { + manager.setTorchMode(cameraId!!, enable) + } + } catch (e: Exception) { + context.showErrorToast(e) + val mainRunnable = Runnable { + EventBus.getDefault().post(Events.CameraUnavailable()) + } + Handler(context.mainLooper).post(mainRunnable) + } + } + + override fun changeTorchBrightness(level: Int) { + if (isTiramisuPlus()) { + manager.turnOnTorchWithStrengthLevel(cameraId!!, level) + } + } + + override fun getMaximumBrightnessLevel(): Int { + return if (isTiramisuPlus()) { + val characteristics = manager.getCameraCharacteristics(cameraId!!) + characteristics.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) ?: MIN_BRIGHTNESS_LEVEL + } else { + MIN_BRIGHTNESS_LEVEL + } + } + + override fun supportsBrightnessControl(): Boolean { + val maxBrightnessLevel = getMaximumBrightnessLevel() + return maxBrightnessLevel > MIN_BRIGHTNESS_LEVEL + } + + override fun getCurrentBrightnessLevel(): Int { + var brightnessLevel = context.config.brightnessLevel + if (brightnessLevel == DEFAULT_BRIGHTNESS_LEVEL) { + brightnessLevel = getMaximumBrightnessLevel() + } + return brightnessLevel + } + + override fun initialize() { + manager.registerTorchCallback(torchCallback, Handler(context.mainLooper)) + } + + 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 ee77902..5182266 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/MyCameraImpl.kt @@ -1,20 +1,17 @@ package com.simplemobiletools.flashlight.helpers 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(val context: Context) { +class MyCameraImpl private constructor(val context: Context, private var cameraTorchListener: CameraTorchListener? = null) { var stroboFrequency = 1000L companion object { @@ -23,15 +20,12 @@ class MyCameraImpl(val context: Context) { private var u = 200L // The length of one dit (Time unit) private val SOS = arrayListOf(u, u, u, u, u, u * 3, u * 3, u, u * 3, u, u * 3, u * 3, u, u, u, u, u, u * 7) - 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: MarshmallowCamera? = null + private var cameraFlash: CameraFlash? = null @Volatile private var shouldStroboscopeStop = false @@ -42,11 +36,10 @@ 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 { - isMarshmallow = isMarshmallowPlus() handleCameraSetup() stroboFrequency = context.config.stroboscopeFrequency } @@ -57,6 +50,8 @@ class MyCameraImpl(val context: Context) { } fun toggleStroboscope(): Boolean { + handleCameraSetup() + if (isSOSRunning) { stopSOS() shouldEnableStroboscope = true @@ -68,6 +63,8 @@ class MyCameraImpl(val context: Context) { disableFlashlight() } + cameraFlash!!.unregisterListeners() + if (!tryInitCamera()) { return false } @@ -87,6 +84,8 @@ class MyCameraImpl(val context: Context) { } fun toggleSOS(): Boolean { + handleCameraSetup() + if (isStroboscopeRunning) { stopStroboscope() shouldEnableSOS = true @@ -106,6 +105,8 @@ class MyCameraImpl(val context: Context) { disableFlashlight() } + cameraFlash!!.unregisterListeners() + return if (isSOSRunning) { stopSOS() false @@ -121,58 +122,26 @@ class MyCameraImpl(val context: Context) { } 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() - } - } - - private fun setupMarshmallowCamera() { - if (marshmallowCamera == null) { - marshmallowCamera = MarshmallowCamera(context) - } - } - - 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,25 +157,12 @@ class MyCameraImpl(val context: Context) { 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) } @@ -218,22 +174,13 @@ class MyCameraImpl(val context: Context) { 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) { @@ -242,17 +189,16 @@ class MyCameraImpl(val context: Context) { 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 @@ -271,56 +217,28 @@ class MyCameraImpl(val context: Context) { } 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 @@ -343,4 +261,20 @@ class MyCameraImpl(val context: Context) { } } } + + fun getMaximumBrightnessLevel(): Int { + return cameraFlash!!.getMaximumBrightnessLevel() + } + + fun getCurrentBrightnessLevel(): Int { + return cameraFlash!!.getCurrentBrightnessLevel() + } + + fun supportsBrightnessControl(): Boolean { + return cameraFlash!!.supportsBrightnessControl() + } + + fun updateBrightnessLevel(level: Int) { + cameraFlash!!.changeTorchBrightness(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" /> + +