Android, Kotlinでカメラ(camera2)を表示するための最小実装

[今更カメラ制御の基礎（STEP1:とりあえずプレビュー表示）](https://qiita.com/zaburo/items/d9d07eb4d87d21308124) の記事のように最小限のカメラ実装の情報で、**Kotlin**かつ**android.hardware.camera2**のものが無かったのでまとめます。

## やりたいこと
とりあえずプレビュー表示する

## カメラ制御の主な流れ

- 対応APIを確認
- Manifestにuses-permissionを追加
- プレビュー表示用のTextureViewを配置
- TextureView.SurfaceTextureListener実装
- openCamera実装
- requestCameraPermission実装
- CameraDevice.StateCallback実装
- createCaptureSession実装

### 対応APIを確認

https://developer.android.com/reference/android/hardware/camera2/package-summary にある通り、API level 21が必要。　ってことで以下のように`build.gradle(Module: app)` を書く

```gradle:build.gradle
android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "net.kboy.mlkitsample"
        minSdkVersion 23
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
```

### Manifestにuses-permissionを追加

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.kboy.mlkitsample">
    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.hardware.camera.autofocus" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="com.google.firebase.ml.vision.DEPENDENCIES"
            android:value="face" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```

### プレビュー表示用のTextureViewを配置

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <TextureView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/mySurfaceView" />
</FrameLayout>
```

### TextureView.SurfaceTextureListener実装

```kotlin:MainActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textureView = findViewById(R.id.mySurfaceView)
        textureView.surfaceTextureListener = surfaceTextureListener
        startBackgroundThread()
    }

    private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {

        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
            imageReader = ImageReader.newInstance(width, height,
                    ImageFormat.JPEG, /*maxImages*/ 2)

            openCamera()
        }

        override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture?, p1: Int, p2: Int) {

        }

        override fun onSurfaceTextureUpdated(p0: SurfaceTexture?) {

        }

        override fun onSurfaceTextureDestroyed(p0: SurfaceTexture?): Boolean {
            return false
        }
    }
```

### openCamera実装

```kotlin:MainActivity.kt
    fun openCamera() {
        var manager: CameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager

        try {
            var camerId: String = manager.getCameraIdList()[0]
            val permission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)

            if (permission != PackageManager.PERMISSION_GRANTED) {
                requestCameraPermission()
                return
            }
            manager.openCamera(camerId, stateCallback, null)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
```

### requestCameraPermission実装

これないと落ちるし、カメラが使えない。

```kotlin
private fun requestCameraPermission() {
    requestPermissions(arrayOf(Manifest.permission.CAMERA), 200)
}
```

また、onRequestPermissionsResultでダイアログ許可後をハンドリングできる
https://developer.android.com/training/permissions/requesting?hl=ja

### CameraDevice.StateCallback実装

```kotlin:MainActivity.kt
    private val stateCallback = object : CameraDevice.StateCallback() {

        override fun onOpened(cameraDevice: CameraDevice) {
            this@MainActivity.cameraDevice = cameraDevice
            createCameraPreviewSession()
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            cameraDevice.close()
            this@MainActivity.cameraDevice = null
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            onDisconnected(cameraDevice)
            finish()
        }

    }
```


### createCameraPreviewSession実装

```kotlin:MainActivity.kt
    private fun createCameraPreviewSession() {
        try {
            val texture = textureView.surfaceTexture
            texture.setDefaultBufferSize(previewSize.width, previewSize.height)
            val surface = Surface(texture)
            previewRequestBuilder = cameraDevice!!.createCaptureRequest(
                    CameraDevice.TEMPLATE_PREVIEW
            )
            previewRequestBuilder.addTarget(surface)
            cameraDevice?.createCaptureSession(Arrays.asList(surface, imageReader?.surface),
                    object : CameraCaptureSession.StateCallback() {

                        override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {

                            if (cameraDevice == null) return
                            captureSession = cameraCaptureSession
                            try {
                                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
                                previewRequest = previewRequestBuilder.build()
                                captureSession?.setRepeatingRequest(previewRequest,
                                        null, Handler(backgroundThread?.looper))
                            } catch (e: CameraAccessException) {
                                Log.e("erfs", e.toString())
                            }

                        }

                        override fun onConfigureFailed(session: CameraCaptureSession) {
                            //Tools.makeToast(baseContext, "Failed")
                        }
                    }, null)
        } catch (e: CameraAccessException) {
            Log.e("erf", e.toString())
        }

    }
```


## 完成

<img src="https://qiita-image-store.s3.amazonaws.com/0/111366/50e0343b-db44-50e1-e020-2828e92978aa.png" width=200>

## 本記事のGithubコード
https://github.com/kboy-silvergym/MLKitSample/blob/6efa8acced3cf2d56bf98e65505a1873c0ccb37f/Android/app/src/main/java/net/kboy/mlkitsample/MainActivity.kt

## 参考
- https://qiita.com/zaburo/items/d9d07eb4d87d21308124
- https://developer.android.com/reference/android/hardware/camera2/package-summary
- https://woshidan.hatenablog.com/entry/2017/09/06/083000