diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ae7cb65642ed7389ad8141c7acd8e2cf599c474e..3e9145ebb82193c74c5cbad185cf12f3376dde87 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + @@ -10,6 +14,7 @@ + + + + + diff --git a/app/src/main/java/jp/atled/agileworks/view/ui/documentweb/DocumentWebDownload.kt b/app/src/main/java/jp/atled/agileworks/view/ui/documentweb/DocumentWebDownload.kt index f133e2030937f63a598dea22a4a52bf839bbe515..1db820a684b6a557547796b29e28a1517c026bd7 100644 --- a/app/src/main/java/jp/atled/agileworks/view/ui/documentweb/DocumentWebDownload.kt +++ b/app/src/main/java/jp/atled/agileworks/view/ui/documentweb/DocumentWebDownload.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.NotificationManager import android.app.PendingIntent import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.provider.OpenableColumns import android.util.Base64 @@ -13,6 +14,7 @@ import android.webkit.ValueCallback import android.webkit.WebChromeClient import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.FileProvider import androidx.fragment.app.Fragment import jp.atled.agileworks.R import jp.atled.agileworks.AwApp @@ -23,9 +25,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.File import java.io.IOException import java.net.* import javax.net.ssl.HttpsURLConnection +import android.Manifest +import android.os.Environment +import android.provider.MediaStore +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import java.text.SimpleDateFormat +import java.util.Date /** * 書類用 WebVie でのファイルダウンロードを請け負うクラス。 @@ -40,6 +52,29 @@ class DocumentWebDownloader(private val handlingFragment: Fragment, private val /** SAF による保存先選択アクティビティから戻った時にダウンロード元を参照するためのもの。これもひとつだけ保持できれば十分のはず。 */ private var downloadSource: DownloadSource? = null + private var currentPhotoPath: String? = null + private lateinit var requestPermissionLauncher: ActivityResultLauncher + private var pendingIntent: Intent? = null + + init { + requestPermissionLauncher = handlingFragment.registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + if (filePathCallback != null && pendingIntent != null) { + showFileChooserInternal(filePathCallback, pendingIntent!!) + pendingIntent = null + } + } else { + // パーミッションが拒否された場合、ファイル選択のみを行う + if (filePathCallback != null && pendingIntent != null) { + showFileOnlyChooserInternal(filePathCallback, pendingIntent!!) + pendingIntent = null + } + } + } + } + /** * ファイル選択アクティビティを表示する。 * @@ -52,7 +87,69 @@ class DocumentWebDownloader(private val handlingFragment: Fragment, private val Log.w(TAG, "simultaneous showFileChooser call") } this.filePathCallback = filePathCallback - handlingFragment.startActivityForResult(intent, chooseFileRequestCode) + pendingIntent = intent + + // カメラのパーミッションを確認 + if (ContextCompat.checkSelfPermission( + handlingFragment.requireContext(), + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + ) { + // パーミッションが許可されている場合、カメラを選択肢に含める + showFileChooserInternal(filePathCallback, intent) + } else { + // パーミッションが許可されていない場合、パーミッションを要求 + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + + private fun showFileChooserInternal(filePathCallback: ValueCallback>?, intent: Intent) { + // ファイル選択とカメラ撮影の Intent を作成 + val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + val photoFile: File? = try { + createImageFile() + } catch (ex: IOException) { + Toast.makeText(handlingFragment.requireContext(), "Failed to create image file.", Toast.LENGTH_SHORT).show() + null + } + photoFile?.also { + val photoURI: Uri = FileProvider.getUriForFile( + handlingFragment.requireContext(), + "jp.atled.agileworks.fileprovider", // AndroidManifest.xml で定義した authority + it + ) + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) + } + + // 選択肢を提示する Intent を作成 + val chooserIntent = Intent.createChooser(intent, "ファイルを選択") + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(takePictureIntent)) + handlingFragment.startActivityForResult(chooserIntent, chooseFileRequestCode) + } + + private fun showFileOnlyChooserInternal(filePathCallback: ValueCallback>?, intent: Intent) { + // 選択肢を提示する Intent を作成 + val chooserIntent = Intent.createChooser(intent, "ファイルを選択") + handlingFragment.startActivityForResult(chooserIntent, chooseFileRequestCode) + } + + private fun createImageFile(): File { + // 画像ファイル名を作成 + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) + val storageDir: File? = handlingFragment.requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) + if (storageDir == null) { + // ストレージディレクトリが取得できない場合、例外をスロー + throw IOException("Failed to get external storage directory.") + } + try { + return File.createTempFile("img_${timeStamp}_", ".jpg", storageDir).apply { + // ACTION_VIEW intents で使用する + currentPhotoPath = absolutePath + } + } catch (e: IOException) { + Log.e(TAG, "Error creating image file", e) + throw e + } } /** @@ -62,22 +159,25 @@ class DocumentWebDownloader(private val handlingFragment: Fragment, private val * @param data [Fragment.onActivityResult] に渡されたデータ */ fun handleFileChooseResult(resultCode: Int, data: Intent?) { - filePathCallback?.let { - val clipdata = data?.clipData - var uris = WebChromeClient.FileChooserParams.parseResult(resultCode, data) - - if (clipdata != null) { - var uriList: MutableList = mutableListOf() - val loopcount = clipdata.itemCount - 1 - for (i in 0..loopcount) { - uriList.add(clipdata.getItemAt(i).uri) - } - uris = uriList.toTypedArray() - } + if (filePathCallback == null) return - it.onReceiveValue(uris) + if (resultCode == Activity.RESULT_OK) { + if (data?.data != null) { + // ファイル選択の結果 + val result = WebChromeClient.FileChooserParams.parseResult(resultCode, data) + filePathCallback?.onReceiveValue(result) + } else if (currentPhotoPath != null) { + // カメラ撮影の結果 + val uri = Uri.fromFile(File(currentPhotoPath!!)) + filePathCallback?.onReceiveValue(arrayOf(uri)) + } else { + filePathCallback?.onReceiveValue(null) + } + } else { + filePathCallback?.onReceiveValue(null) } filePathCallback = null + currentPhotoPath = null } /** diff --git a/app/src/main/res/xml/file_path.xml b/app/src/main/res/xml/file_path.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad3cd0d3728621a1804328b4172a37f2cce3b955 --- /dev/null +++ b/app/src/main/res/xml/file_path.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file