【Kotlin研修13日目】暗黙的インテントを利用したカメラ機能の実装

# カメラ機能の実装
`カメラ機能`を実装する方法は、以下の2通り。

> 1. `暗黙的インテント`によるOS標準の`「カメラ」アプリ`の利用
> 2. `android.hardware.camera2`APIによる`カメラ機能`の作成

# 暗黙的インテントを利用したカメラ機能の実装
[参考: 研修11日目](https://qiita.com/b150005/items/a0053b66b28e16967270)
`暗黙的インテント`を用いて`カメラ機能`を実装する手順は、以下の通り。

> 1. `マニフェストファイル`に端末の`ストレージ`を利用するための`パーミッション`を付与
> 2. 撮影した`画像ファイル名`が**一意**となるよう、`SimpleDateFormat`を用いて`日時フォーマッタ`を作成
> 3. `ストレージ`に格納する`画像ファイル名`・`ファイル形式`を指定
> 4. `ContentResolver`を用いてデータの`格納先URI`を作成
> 5. `暗黙的インテント`を用いてOS標準の`「カメラ」アプリ`を起動
> 6. `遷移元アクティビティ`に戻った際に実行される処理を記述

# マニフェストファイルへのパーミッションの記述
[参考: 研修12日目](https://qiita.com/b150005/items/2c38f3f8925de74a0ff4#%E3%83%9E%E3%83%8B%E3%83%95%E3%82%A7%E3%82%B9%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%B8%E3%81%AE%E3%83%91%E3%83%BC%E3%83%9F%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%A8%98%E8%BF%B0)
`アプリケーション`が端末の`ストレージ`を利用できるよう、`マニフェストファイル`(=`AndroidManifest.xml`)に`<uses-permission>`タグを追記する。

なお、`ユーザ`に対して`パーミッションダイアログ`の表示が必要なのは、
`アプリケーション`に付与された`パーミッション`を用いて**外部(=インターネット)との通信が行われる場合**に限定されるため、端末内部でデータのやり取りを行う場合は`パーミッションダイアログ`の表示は不要である。

## サンプルコード

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission 
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    />
    ...
</manifest>
```

# 日時フォーマッタの作成
`ストレージ`に`ファイル`を格納する場合、ファイル名が同名となり自動で上書きされないよう、**一意**となるファイル名にする必要がある。
ファイル名を**一意**とするためには、`タイムスタンプ`を利用する。

`タイムスタンプ`の利用にあたって、`日時フォーマッタ`である`SimpleDateFormat`オブジェクト、`現在時刻`を保持する`Date`オブジェクトを利用する。

ここで、`Date`オブジェクトを`タイムスタンプ`として利用する場合、
`タイムスタンプ`は`String`型であるため、
`DateFormat`クラスの`format()`メソッドを用いて`Date`オブジェクトを`String`型に変換する必要がある。

## SimpleDateFormat
アルファベットを用いた`ユーザ定義`の`日時フォーマッタ`を生成するクラス。

## Date
`ミリ秒`単位で特定の「**瞬間**」を表すクラス。

## 定義

```kotlin
// 日時フォーマットを指定したSimpleDateFormatオブジェクトの生成
SimpleDateFormat(pattern: String)
// パラメータ
// pattern: 日時の形式

// Date型 -> String型 への変換
// <- SimpleDateFormatクラスはDateFormatクラスを継承しているため、
//    SimpleDateFormatクラスからも利用可能
DateFormat.format(date: Date): String
// パラメータ
// date: 日時を表すDateオブジェクト
```

### 日時形式の主な表現方法
[参考: SimpleDateFormat](https://developer.android.com/reference/java/text/SimpleDateFormat)

|文字|内容|例|
|:--|:--|:--|
|`yyyy`|年|`2021`|
|`MM`|月|`06`|
|`dd`|日|`21`|
|`EEE`|曜日|`Mon`|
|`HH`|時|`10`|
|`mm`|分|`56`|
|`ss`|秒|`48`|
|`SSS`|ミリ秒|`978`|

## サンプルコード

```kotlin:MainActivity.kt
// 日時形式を指定したSimpleDateFormatオブジェクト
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss")

// 現在の時刻を保持するDateオブジェクト
val now = Date()

// Date型 -> String型 への変換
val nowStr = dateFormat.format(now)

// タイムスタンプを利用したファイル名の指定
val fileName = "CameraIntentPhoto_${nowStr}.jpg"
```

# ContentValueオブジェクトの生成・定義
`ContentValue`オブジェクトを生成し、やり取りするデータの`ファイル名`と、データの`種類`(=`MIMEタイプ`)を定義する。

## ContentValue
`ContentResolver`が生成する`URI`に含まれる、`コンテンツプロバイダ`が取り扱う`データ情報`を`マップ`構造で保持するクラス。

## ContentResolver
`コンテンツプロバイダ`に渡す、`データの格納先URI`(=`Uri`オブジェクト)と`データ情報`(=`ContentValue`オブジェクト)が含まれる`URI`を生成するクラス。

## コンテンツプロバイダ(ContentProvider)
[参考: コンテンツプロバイダ](https://developer.android.com/guide/topics/providers/content-provider-basics)
端末の`ストレージ`や複数の`外部アプリケーション`と直接`データ`のやり取りを行う、`アプリケーション`の構成要素。

![コンテンツプロバイダ0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/e26170be-6a4c-f4da-f16d-678e119e47e7.png)
[出典: コンテンツプロバイダと他のコンポーネントとの関係](https://developer.android.com/guide/topics/providers/content-provider-basics)

`コンテンツプロバイダ`は、`ContentResolver`オブジェクトによって作成された`URI`を基に、指定された`ストレージ`に対して指定された`データ`をやり取りする。

端末の`ストレージ`へのアクセス手順を図式化すると、以下のようになる。

![コンテンツプロバイダ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/ed32e95f-66e6-9934-2758-aec9ce680dbb.png)
[出典: ストレージとのやり取りの流れ](https://developer.android.com/guide/topics/providers/content-provider-basics)

## 定義

```kotlin
ContentValues.put(
    key: String!, 
    value: <T>!
): Unit
// パラメータ
// key: データの種類を表すキー
// value: 値
```

### データの種類を表すContentValuesのキー

|キー名|内容|
|:--|:--|
|`MediaStore.Images.Media.TITLE`|画像ファイル名|
|`MediaStore.Images.Media.MIME_TYPE`|ファイルの種類(=`MIMEタイプ`)|

## サンプルコード

```kotlin:MainActivity.kt
// タイムスタンプを利用したファイル名の指定
val fileName = "CameraIntentPhoto_${nowStr}.jpg"

// ContentValuesオブジェクト
val values =  ContentValues()

// ContentValuesオブジェクトに格納するファイル名
values.put(MediaStore.Images.Media.TITLE, fileName)

// ContentValuesオブジェクトに格納するMIMEタイプ(=ファイルの種類)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
```

# Uriオブジェクトの生成
`ContentResolver`クラスの`insert()`メソッドを用いて、
「**データの格納先URI**」(=`Uri`オブジェクト)と、
格納する「**データ情報**」(=`ContentValues`オブジェクト)を保持する`Uri`オブジェクトを生成する。

なお、`ContentResolver`が`コンテンツプロバイダ`に対して渡す`URI`は`リレーショナルデータベース`のデータ構造をとっており、
`URI`に含まれる`データの格納先URI`毎に`テーブル`が存在し、
`テーブル`毎に取り扱う`データ情報`が含まれる。

[上記サンプルコード](https://qiita.com/b150005/items/b5659c7497e489ca1674#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89-2)の場合の`テーブル`のデータ構造は、以下の通り。

|キー|値|
|:--|:--|
|`MediaStore.Images.Media.TITLE`|`${filename}`|
|`MediaStore.Images.Media.MIME_TYPE`|`"image/jpeg"`|

## 定義

```kotlin
ContentResolver.insert(
    url: Uri, 
    values: ContentValues?
): Uri?
// パラメータ
// url: 「データの格納先」を表すUriオブジェクト
// values: 「データ情報」を表すContentValuesオブジェクト
```

## サンプルコード

```kotlin:MainActivity.kt
// ContentValuesオブジェクト(=データ情報)
val values =  ContentValues()

// ContentValuesオブジェクトに格納するファイル名
values.put(MediaStore.Images.Media.TITLE, fileName)

// ContentValuesオブジェクトに格納するMIMEタイプ(=ファイルの種類)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")

// データの格納先Uriとデータ情報(=ContentValuesオブジェクト)を保持するURI
_imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
```

# 暗黙的インテントの定義
[参考: 研修11日目](https://qiita.com/b150005/items/a0053b66b28e16967270#%E5%AE%9A%E7%BE%A9)
`インテント`が行う`アクション`が`アプリケーション`**そのもの**を表す場合、`URI`の指定は不要となる。

また、`「カメラ」アプリ`を利用し、撮影した`画像データ`を`ストレージ`に保存する場合、
`インテント`の`Extraデータ`に、`ContentResolver`を用いて作成した`URI`を追加する。
ただし、その場合の`Extraデータ`のキー(=`name`)は、値が「**データの出力先**」であることを表す`MediaStore.EXTRA_OUTPUT`とする。

## 定義

```kotlin
// 暗黙的インテントを利用したIntentオブジェクトの生成
Intent(action: String!)
// パラメータ
// action: インテントが実行するアクション

// データの出力先URIを指定するExtraデータの追加
Intent.putExtra(
    name: String!, 
    value: Parcelable?
): Intent
// name: 格納するデータの名称
// value: 格納するデータ
```

## サンプルコード

```kotlin:MainActivity.kt
// 「カメラ」アプリを起動するインテント
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

// 画像の出力先をURIで指定
intent.putExtra(MediaStore.EXTRA_OUTPUT, _imageUri)
```

# 遷移先での処理後に遷移元へ戻る画面遷移の実装
[参考: 研修3日目](https://qiita.com/b150005/items/f2c1d8236ac5e9770f4f)
`遷移先アクティビティ`での処理終了時に、自動的に`遷移元アクティビティ`に戻る場合、
`ComponentActivity`クラスの`startActivityForResult()`メソッドを用いて`インテント`を開始する。

## 定義

```kotlin
ComponentActivity.startActivityForResult(
    intent Intent!, 
    requestCode: Int
): Unit
// パラメータ
// intent: 遷移先アクティビティを表すIntentオブジェクト
// requestCode: onActivityResult()メソッド(後述)と
//              一対一で対応させるリクエストコード
```

## サンプルコード

```kotlin:MainActivity.kt
// 「カメラ」アプリを起動するインテント
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

// 遷移先での処理後に遷移元へ戻る画面遷移の実行
startActivityForResult(intent, 200)
```

# 画面遷移後に遷移元で呼び出される処理の記述
`ComponentActivity`クラスの`startActivityForResult()`メソッドを用いて`インテント`を開始した場合、`遷移先アクティビティ`での処理終了後、
`遷移元アクティビティ`で`ComponentActivity`クラスの`onActivityResult()`メソッドが呼び出される。

また、`遷移元アクティビティ`で撮影した`画像データ`を表示する場合は、
あらかじめ`ImageView`を配置しておき、`ImageView`に対して画像データが保存されている`格納先ストレージ`の`URI`を指定する。

## 定義

```kotlin
// startActivityForResult()メソッドの終了時に呼び出される処理
@CallSuper
ComponentActivity.onActivityResult(
    requestCode: Int, 
    resultCode: Int, 
    @Nullable data: Intent?
): Unit
// パラメータ
// requestCode: startActivityForResult()メソッドと
//              一対一で対応させるリクエストコード
// resultCode: 処理結果を表すActivityクラス定数

// URIを指定してImageViewに画像を表示
ImageView.setImageURI(uri: Uri?): Unit
// uri: 画像データのURI
```

### 処理結果を表すActivityクラス定数

|定数名|内容|
|:--|:--|
|`RESULT_OK`|正常終了|
|`RESULT_CANCELED`|キャンセル|

## サンプルコード

```kotlin:MainActivity.kt
// データの格納先URIとデータ情報(=ContentValuesオブジェクト)を保持するURI
_imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

// startActivityForResult()による遷移先アクティビティでの処理終了後、
// 遷移元アクティビティで呼び出される処理(=コールバック処理)
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // リクエストコードが一致し、かつ正常に処理が終了していた場合の処理
    if(requestCode == 200 && resultCode == AppCompatActivity.RESULT_OK) {

        // 「カメラ」アプリによって変換されたBitmapオブジェクト(=画像)
        // <- ストレージを利用しない場合はサムネイル画像しか取得できないため、
        //    解像度の低い画像となる
        // val bitmap = data?.getParcelableExtra<Bitmap>("data")

        // 画像を表示するImageView
        val ivCamera = findViewById<ImageView>(R.id.ivCamera)

        // Bitmapデータを指定してImageViewに画像を反映
        // ivCamera.setImageBitmap(bitmap)

        // URIを指定してImageViewに画像を反映
        ivCamera.setImageURI(_imageUri)
    }
}
```