zcash-android-wallet-zcon1/zcash-android-wallet-app/qrecycler/src/main/java/cash/z/android/cameraview/CameraView.kt

511 lines
16 KiB
Kotlin

/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cash.z.android.cameraview
import cash.z.android.qrecycler.R
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.IntDef
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import androidx.core.os.ParcelableCompat
import androidx.core.os.ParcelableCompatCreatorCallbacks
import androidx.core.view.ViewCompat
import cash.z.android.cameraview.api21.Camera2
import cash.z.android.cameraview.base.AspectRatio
import cash.z.android.cameraview.base.CameraViewImpl
import cash.z.android.cameraview.base.Constants
import cash.z.android.cameraview.base.PreviewImpl
import com.google.android.cameraview.Camera2Api23
import com.google.android.cameraview.TextureViewPreview
import java.lang.IllegalStateException
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.util.*
open class CameraView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
FrameLayout(context, attrs, defStyleAttr) {
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : this(context, attrs, 0)
internal lateinit var mImpl: CameraViewImpl
private var mCallbacks: CallbackBridge?
private var mAdjustViewBounds: Boolean = false
private var mDisplayOrientationDetector: DisplayOrientationDetector?
/**
* @return `true` if the camera is opened.
*/
val isCameraOpened: Boolean
get() = mImpl.isCameraOpened
/**
* @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of
* camera.
* @see .setAdjustViewBounds
*/
/**
* @param adjustViewBounds `true` if you want the CameraView to adjust its bounds to
* preserve the aspect ratio of camera.
* @see .getAdjustViewBounds
*/
var adjustViewBounds: Boolean
get() = mAdjustViewBounds
set(adjustViewBounds) {
if (mAdjustViewBounds != adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds
requestLayout()
}
}
/**
* Gets the direction that the current camera faces.
*
* @return The camera facing.
*/
/**
* Chooses camera by the direction it faces.
*
* @param facing The camera facing. Must be either [.FACING_BACK] or
* [.FACING_FRONT].
*/
var facing: Int
@Facing
get() = mImpl.facing
set(@Facing facing) {
mImpl.facing = facing
}
/**
* Gets all the aspect ratios supported by the current camera.
*/
val supportedAspectRatios: Set<AspectRatio>
get() = mImpl.supportedAspectRatios
/**
* Gets the current aspect ratio of camera.
*
* @return The current [AspectRatio]. Can be `null` if no camera is opened yet.
*/
/**
* Sets the aspect ratio of camera.
*
* @param ratio The [AspectRatio] to be set.
*/
var aspectRatio: AspectRatio?
@Nullable
get() = mImpl.aspectRatio
set(@NonNull ratio) {
if (mImpl.setAspectRatio(ratio)) {
requestLayout()
}
}
/**
* Returns whether the continuous auto-focus mode is enabled.
*
* @return `true` if the continuous auto-focus mode is enabled. `false` if it is
* disabled, or if it is not supported by the current camera.
*/
/**
* Enables or disables the continuous auto-focus mode. When the current camera doesn't support
* auto-focus, calling this method will be ignored.
*
* @param autoFocus `true` to enable continuous auto-focus mode. `false` to
* disable it.
*/
var autoFocus: Boolean
get() = mImpl.autoFocus
set(autoFocus) {
mImpl.autoFocus = autoFocus
}
/**
* Gets the current flash mode.
*
* @return The current flash mode.
*/
/**
* Sets the flash mode.
*
* @param flash The desired flash mode.
*/
var flash: Int
@Flash
get() = mImpl.flash
set(@Flash flash) {
mImpl.flash = flash
}
/** Direction the camera faces relative to device screen. */
@IntDef(FACING_BACK, FACING_FRONT)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class Facing
/** The mode for for the camera device's flash control */
@IntDef(FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE)
annotation class Flash
init {
if (isInEditMode) {
mCallbacks = null
mDisplayOrientationDetector = null
} else {
// Internal setup
val preview = createPreviewImpl(context)
mCallbacks = CallbackBridge()
if (Build.VERSION.SDK_INT < 23) {
mImpl = Camera2(mCallbacks!!, preview, context)
} else {
mImpl = Camera2Api23(mCallbacks!!, preview, context)
}
// Attributes
val a = context.obtainStyledAttributes(
attrs, R.styleable.CameraView, defStyleAttr,
R.style.Widget_CameraView
)
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false)
facing = a.getInt(R.styleable.CameraView_facing, FACING_BACK)
var aspectRatioString = a.getString(R.styleable.CameraView_aspectRatio)
if (aspectRatioString != null) {
aspectRatio = AspectRatio.parse(aspectRatioString)
} else {
aspectRatio = Constants.DEFAULT_ASPECT_RATIO
}
autoFocus = a.getBoolean(R.styleable.CameraView_autoFocus, true)
flash = a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO)
a.recycle()
// Display orientation detector
mDisplayOrientationDetector = object : DisplayOrientationDetector(context) {
override fun onDisplayOrientationChanged(displayOrientation: Int) {
mImpl.setDisplayOrientation(displayOrientation)
}
}
}
}
@NonNull
private fun createPreviewImpl(context: Context): PreviewImpl {
return TextureViewPreview(context, this)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!isInEditMode) {
mDisplayOrientationDetector!!.enable(ViewCompat.getDisplay(this)!!)
}
}
override fun onDetachedFromWindow() {
if (!isInEditMode) {
mDisplayOrientationDetector!!.disable()
}
super.onDetachedFromWindow()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (isInEditMode) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
// Handle android:adjustViewBounds
if (mAdjustViewBounds) {
if (!isCameraOpened) {
mCallbacks!!.reserveRequestLayoutOnOpen()
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
if (widthMode == View.MeasureSpec.EXACTLY && heightMode != View.MeasureSpec.EXACTLY) {
val ratio = aspectRatio!!
var height = (View.MeasureSpec.getSize(widthMeasureSpec) * ratio!!.toFloat()) as Int
if (heightMode == View.MeasureSpec.AT_MOST) {
height = Math.min(height, View.MeasureSpec.getSize(heightMeasureSpec))
}
super.onMeasure(
widthMeasureSpec,
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
)
} else if (widthMode != View.MeasureSpec.EXACTLY && heightMode == View.MeasureSpec.EXACTLY) {
val ratio = aspectRatio!!
var width = (View.MeasureSpec.getSize(heightMeasureSpec) * ratio!!.toFloat()) as Int
if (widthMode == View.MeasureSpec.AT_MOST) {
width = Math.min(width, View.MeasureSpec.getSize(widthMeasureSpec))
}
super.onMeasure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
heightMeasureSpec
)
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
// Measure the TextureView
val width = measuredWidth
val height = measuredHeight
var ratio = aspectRatio
if (mDisplayOrientationDetector!!.lastKnownDisplayOrientation % 180 == 0) {
ratio = ratio!!.inverse()
}
assert(ratio != null)
if (height < width * ratio!!.y / ratio!!.x) {
mImpl.view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(
width * ratio!!.y / ratio!!.x,
View.MeasureSpec.EXACTLY
)
)
} else {
mImpl.view.measure(
View.MeasureSpec.makeMeasureSpec(
height * ratio!!.x / ratio!!.y,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
)
}
}
override fun onSaveInstanceState(): Parcelable? {
val state = SavedState(super.onSaveInstanceState())
state.facing = facing
state.ratio = aspectRatio
state.autoFocus = autoFocus
state.flash = flash
return state
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state !is SavedState) {
super.onRestoreInstanceState(state)
return
}
val ss = state as SavedState?
super.onRestoreInstanceState(ss!!.getSuperState())
facing = ss.facing
aspectRatio = ss.ratio
autoFocus = ss.autoFocus
flash = ss.flash
}
/**
* Open a camera device and start showing camera preview. This is typically called from
* [Activity.onResume].
*/
fun start() {
if (!mImpl.start()) {
throw IllegalStateException("failed to start even though we're on API 21+")
// //store the state ,and restore this state after fall back o Camera1
// val state = onSaveInstanceState()
// // Camera2 uses legacy hardware layer; fall back to Camera1
// mImpl = Camera1(mCallbacks, createPreviewImpl(context))
// onRestoreInstanceState(state)
// mImpl.start()
}
}
/**
* Stop camera preview and close the device. This is typically called from
* [Activity.onPause].
*/
fun stop() {
mImpl.stop()
}
/**
* Add a new callback.
*
* @param callback The [Callback] to add.
* @see .removeCallback
*/
fun addCallback(@NonNull callback: Callback) {
mCallbacks!!.add(callback)
}
/**
* Remove a callback.
*
* @param callback The [Callback] to remove.
* @see .addCallback
*/
fun removeCallback(@NonNull callback: Callback) {
mCallbacks!!.remove(callback)
}
/**
* Take a picture. The result will be returned to
* [Callback.onPictureTaken].
*/
fun takePicture() {
mImpl.takePicture()
}
private inner class CallbackBridge internal constructor() : CameraViewImpl.Callback {
private val mCallbacks = ArrayList<Callback>()
private var mRequestLayoutOnOpen: Boolean = false
fun add(callback: Callback) {
mCallbacks.add(callback)
}
fun remove(callback: Callback) {
mCallbacks.remove(callback)
}
override fun onCameraOpened() {
if (mRequestLayoutOnOpen) {
mRequestLayoutOnOpen = false
requestLayout()
}
for (callback in mCallbacks) {
callback.onCameraOpened(this@CameraView)
}
}
override fun onCameraClosed() {
for (callback in mCallbacks) {
callback.onCameraClosed(this@CameraView)
}
}
override fun onPictureTaken(data: ByteArray) {
for (callback in mCallbacks) {
callback.onPictureTaken(this@CameraView, data)
}
}
fun reserveRequestLayoutOnOpen() {
mRequestLayoutOnOpen = true
}
}
protected class SavedState : View.BaseSavedState {
@Facing
internal var facing: Int = 0
internal var ratio: AspectRatio? = null
internal var autoFocus: Boolean = false
@Flash
internal var flash: Int = 0
constructor(source: Parcel) : this(source, AspectRatio::class.java.classLoader!!)
constructor(source: Parcel, loader: ClassLoader) : super(source) {
facing = source.readInt()
ratio = source.readParcelable(loader)
autoFocus = source.readByte().toInt() != 0
flash = source.readInt()
}
constructor(parcelable: Parcelable) : super(parcelable)
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(facing)
out.writeParcelable(ratio, 0)
out.writeByte((if (autoFocus) 1 else 0).toByte())
out.writeInt(flash)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<SavedState> {
override fun createFromParcel(parcel: Parcel): SavedState {
return SavedState(parcel)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
/**
* Callback for monitoring events about [CameraView].
*/
abstract class Callback {
/**
* Called when camera is opened.
*
* @param cameraView The associated [CameraView].
*/
fun onCameraOpened(cameraView: CameraView) {}
/**
* Called when camera is closed.
*
* @param cameraView The associated [CameraView].
*/
fun onCameraClosed(cameraView: CameraView) {}
/**
* Called when a picture is taken.
*
* @param cameraView The associated [CameraView].
* @param data JPEG data.
*/
fun onPictureTaken(cameraView: CameraView, data: ByteArray) {}
}
companion object {
/** The camera device faces the opposite direction as the device's screen. */
const val FACING_BACK = Constants.FACING_BACK
/** The camera device faces the same direction as the device's screen. */
const val FACING_FRONT = Constants.FACING_FRONT
/** Flash will not be fired. */
const val FLASH_OFF = Constants.FLASH_OFF
/** Flash will always be fired during snapshot. */
const val FLASH_ON = Constants.FLASH_ON
/** Constant emission of light during preview, auto-focus and snapshot. */
const val FLASH_TORCH = Constants.FLASH_TORCH
/** Flash will be fired automatically when required. */
const val FLASH_AUTO = Constants.FLASH_AUTO
/** Flash will be fired in red-eye reduction mode. */
const val FLASH_RED_EYE = Constants.FLASH_RED_EYE
}
}