アプリから起動したカメラで撮影した画像をフルサイズ保存する方法

# 検証環境
この記事の内容は、以下の環境で検証しました。

* Java:open jdk 1.8.0_152
* Kotlin 1.2.0
* Android Studio 3.0.2
* CompileSdkVersion:26
* MinSdkVersion:21
* TargetSdkVersion:26
* BuildToolsVersion:26.0.2
* gradle:4.0.0

# はじめに
以前の記事：[KotlinでAndroidのカメラ機能を利用する](https://qiita.com/naoi/items/04e44308221f6eb73024)
で表示していた画像の解像度が低すぎてこれでは使い物になりません。
本記事では、高解像度の画像を保存します。また、保存した画像はギャラリーからもアクセス可能にします。画像の外部ストレージ保存方法やギャラリーへの公開方法は、こちらを参照してください。
[Android端末のDCIMに保存してギャラリーで表示させる方法](https://qiita.com/naoi/items/70ac5826cc6de86d6b78)

上記以外の内容を本記事では説明していきます。
カメラで撮影した結果を高解像度での保存は、
暗黙的インテントに保存の設定を行うことにより実現します。
本記事では、暗黙的インテントに保存の設定方法について説明していきます。

# 追加した処理
以前の記事に対し、追加した処理は以下のとおりです。
* 外部ストレージへのアクセス許可取得
* パーミッションの確認に外部ストレージのアクセス許可を追加
* FileProviderの追加
* FileProviderのURIを取得
* 保存処理を暗黙的インテントに追加
* ギャラリーへの追加
* 画像ファイルを読み込んでBitmapに表示


# 説明
## FileProvider
### FileProviderとは・・・
Android Nからファイルアクセスで「file://」が、StrictModeの設定が変わり利用できなくなりました。その代わり「content://」でアクセスします。その時に利用するのがFileProviderです。FileProviderでアクセスすことにより、対象フォルダの一時許可が得られます。
android.support.v4サポートライブラリに格納されています。
詳細は、下記のサイトを読んで下さい。

https://developer.android.com/reference/android/support/v4/content/FileProvider.html

### 本アプリでの使い方
本アプリでは、暗黙的インテントにFileProviderで取得したURIを渡し、撮影した画像の保存までカメラアプリに処理を委譲します。

### 実装方法
#### AndroidManifest.xml
まず初めに、外部ストレージであるDCIMに保存するため、uses-permissionを追記します。

```xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

FileProviderを使用する場合、ContentProviderを定義したときと同じように、\<provider>タグを記述します。authorities属性にはアプリのパッケージを指定します。値として\<meta-data>タグを記述します。\<meta-data>タグではFileProviderが取り扱うパスを設定したxmlファイルを指定します。

```xml
<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="jp.co.casareal.camerabasicsample"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                />
        </provider>
```
### file_path.xml
FileProviderの設定で必要となるパスを設定したxmlを用意する必要があります。resフォルダにxmlという名前のフォルダに格納します。
ルートタグは\<paths>です。値として、以下のタグを指定します。複数指定可能です。詳細はAndroid Develpersを参照してください。記述するタグにより一時許可を得るディレクトリが変わります。

* \<cache-path>
* \<files-path>
* \<external-path>
* \<external-files-path>
* \<external-cache-path>

本記事ではDCIMの一時許可を得るために、下記の設定を記述します。
name属性は今回使用していないので、適当に付けています。

```xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="/" />
</paths>
```

## 画像ファイルのフルサイズ保存
カメラアプリで撮影した時に画像を保存させるには、FileProviderクラスのgetUriForFileメソッドでURIを作成する必要があります。getUriForFileメソッドのシグネチャーは以下の遠です。
第1引数はContextです。第2引数はAndroidManifest.xmlで記述したパッケージをしていします。第3引数には、保存するファイルのパスやファイル名が格納されているFileクラスのオブジェクトです。

```java
Uri getUriForFile (Context context, String authority, File file)
```

呼び出す時は下記の様なコードになります。

```kotlin
FileProvider.getUriForFile(this, "jp.co.casareal.camerabasicsample", file)
```

最終的に本記事では、下記のようなメソッドで定義しました。
ファイルのパスは画像を表示する時に使用するため、プロパティに保存しています。

```kotlin
    private fun createSaveFileUri(): Uri {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.JAPAN).format(Date())
        val imageFileName = "casalack_" + timeStamp

        val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/casalack")
        if (!storageDir.exists()) {
            storageDir.mkdir()
        }
        val file = File.createTempFile(
                imageFileName, /* prefix */
                ".jpg", /* suffix */
                storageDir      /* directory */
        )
        path = file.absolutePath

        return FileProvider.getUriForFile(this, "jp.co.casareal.camerabasicsample", file)
    }
```

これで保存するURIの生成が完了しました。カメラアプリを起動する時の暗黙的インテントにこのURIを格納します。格納する時のキーはMediaStore.EXTRA_OUTPUTを指定し、値にURIを指定します。

```kotlin
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            putExtra(MediaStore.EXTRA_OUTPUT, createSaveFileUri())
        }

        startActivityForResult(intent, CAMERA_REQUEST_CODE)
```
以上でDCIMに保存されます。

最終的な、AndroidManifest.xmlとActivityは以下のとおりです。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.casareal.camerabasicsample">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="jp.co.casareal.camerabasicsample"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                />
        </provider>

    </application>

</manifest>
```

```kotlin:MainActivity.kt
package jp.co.casareal.camerabasicsample

import android.Manifest
import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v4.content.FileProvider
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileInputStream
import java.text.SimpleDateFormat
import java.util.*


class MainActivity : AppCompatActivity() {
    companion object {
        const val CAMERA_REQUEST_CODE = 1
        const val CAMERA_PERMISSION_REQUEST_CODE = 2
    }

    private lateinit var path: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()

        btnLaunchCamera.setOnClickListener {
            // カメラ機能を実装したアプリが存在するかチェック
            Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(packageManager)?.let {
                if (checkPermission()) {
                    takePicture()
                } else {
                    grantCameraPermission()
                }
            } ?: Toast.makeText(this, "カメラを扱うアプリがありません", Toast.LENGTH_LONG).show()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            val contentValues = ContentValues().apply {
                put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
                put("_data", path)
            }
            contentResolver.insert(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)


            val inputStream = FileInputStream(File(path))
            val bitmap = BitmapFactory.decodeStream(inputStream)
            cameraImage.setImageBitmap(bitmap)

        }
    }

    private fun takePicture() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
            putExtra(MediaStore.EXTRA_OUTPUT, createSaveFileUri())
        }

        startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

    private fun checkPermission(): Boolean {
        val cameraPermission = PackageManager.PERMISSION_GRANTED ==
                ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA)

        val extraStoragePermission = PackageManager.PERMISSION_GRANTED ==
                ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA)

        return cameraPermission && extraStoragePermission
    }


    private fun grantCameraPermission() =
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    CAMERA_PERMISSION_REQUEST_CODE)

    private fun createSaveFileUri(): Uri {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.JAPAN).format(Date())
        val imageFileName = "casalack_" + timeStamp

        val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/casalack")
        if (!storageDir.exists()) {
            storageDir.mkdir()
        }
        val file = File.createTempFile(
                imageFileName, /* prefix */
                ".jpg", /* suffix */
                storageDir      /* directory */
        )
        path = file.absolutePath

        return FileProvider.getUriForFile(this, "jp.co.casareal.camerabasicsample", file)
    }

    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<out String>,
                                            grantResults: IntArray) {
        var isGranted = true
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (grantResults.isNotEmpty()) {
                grantResults.forEach {
                    if (it != PackageManager.PERMISSION_GRANTED) {
                        isGranted = false
                    }
                }
            } else {
                isGranted = false
            }
        } else {
            isGranted = false
        }

        if (isGranted) {
            takePicture()
        } else {
            grantCameraPermission()
        }

    }


}
```