Commit 0df9b16b authored by Tiger Oakes's avatar Tiger Oakes Committed by Tiger Oakes
Browse files

Fixes #2587 - Show camera inside file prompt

parent c6e58ae7
......@@ -4,39 +4,29 @@
package mozilla.components.browser.engine.gecko.prompt
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.Alert
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FileCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.support.ktx.kotlin.toDate
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_HOST
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_ONLY_PASSWORD
......@@ -47,15 +37,24 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEGATIVE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEUTRAL
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_POSITIVE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_MONTH
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_TIME
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_WEEK
import java.io.FileInputStream
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FileCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback
import java.io.FileOutputStream
import java.io.IOException
import java.security.InvalidParameterException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
typealias GeckoChoice = GeckoSession.PromptDelegate.Choice
......@@ -412,29 +411,30 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}
private fun Uri.toFileUri(context: Context): Uri {
val uri = this
val temporalFile = java.io.File(context.cacheDir, uri.getFileName(context))
val contentResolver = context.contentResolver
val cacheUploadDirectory = java.io.File(context.cacheDir, "/uploads")
if (!cacheUploadDirectory.exists()) {
cacheUploadDirectory.mkdir()
}
val temporalFile = java.io.File(cacheUploadDirectory, getFileName(contentResolver))
try {
val inStream = context.contentResolver.openInputStream(uri) as FileInputStream
val outStream = FileOutputStream(temporalFile)
val inChannel = inStream.channel
val outChannel = outStream.channel
inChannel.transferTo(0, inChannel.size(), outChannel)
inStream.close()
outStream.close()
contentResolver.openInputStream(this)!!.use { inStream ->
FileOutputStream(temporalFile).use { outStream ->
inStream.copyTo(outStream)
}
}
} catch (e: IOException) {
val logger = Logger("GeckoPromptDelegate")
logger.warn("Could not convert uri to file uri", e)
Logger("GeckoPromptDelegate").warn("Could not convert uri to file uri", e)
}
return Uri.parse("file:///" + temporalFile.absolutePath)
return Uri.parse("file:///${temporalFile.absolutePath}")
}
@SuppressLint("Recycle")
private fun Uri.getFileName(context: Context): String {
private fun Uri.getFileName(contentResolver: ContentResolver): String {
val returnUri = this
var fileName = ""
this.let { returnUri ->
context.contentResolver.query(returnUri, null, null, null, null)
}?.use { cursor ->
contentResolver.query(returnUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
......
......@@ -4,39 +4,29 @@
package mozilla.components.browser.engine.gecko.prompt
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.Alert
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FileCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.support.ktx.kotlin.toDate
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_HOST
import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_FLAG_ONLY_PASSWORD
......@@ -47,15 +37,24 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions.AUTH_LEVEL_
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEGATIVE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_NEUTRAL
import org.mozilla.geckoview.GeckoSession.PromptDelegate.BUTTON_TYPE_POSITIVE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MENU
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice.CHOICE_TYPE_SINGLE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_MONTH
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_TIME
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DATETIME_TYPE_WEEK
import java.io.FileInputStream
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FileCallback
import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback
import java.io.FileOutputStream
import java.io.IOException
import java.security.InvalidParameterException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
typealias GeckoChoice = GeckoSession.PromptDelegate.Choice
......@@ -133,6 +132,8 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
val isMultipleFilesSelection = selectionType == GeckoSession.PromptDelegate.FILE_TYPE_MULTIPLE
val captureMode = PromptRequest.File.FacingMode.NONE
val onSelectSingle: (Context, Uri) -> Unit = { context, uri ->
callback.confirm(context, uri.toFileUri(context))
}
......@@ -146,6 +147,7 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
PromptRequest.File(
mimeTypes ?: emptyArray(),
isMultipleFilesSelection,
captureMode,
onSelectSingle,
onSelectMultiple,
onDismiss
......@@ -412,33 +414,30 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}
private fun Uri.toFileUri(context: Context): Uri {
val uri = this
val contentResolver = context.contentResolver
val cacheUploadDirectory = java.io.File(context.cacheDir, "/uploads")
if (!cacheUploadDirectory.exists()) {
cacheUploadDirectory.mkdir()
}
val temporalFile = java.io.File(cacheUploadDirectory, uri.getFileName(context))
val temporalFile = java.io.File(cacheUploadDirectory, getFileName(contentResolver))
try {
(context.contentResolver.openInputStream(uri) as FileInputStream).use { inStream ->
contentResolver.openInputStream(this)!!.use { inStream ->
FileOutputStream(temporalFile).use { outStream ->
inStream.copyTo(outStream)
}
}
} catch (e: IOException) {
val logger = Logger("GeckoPromptDelegate")
logger.warn("Could not convert uri to file uri", e)
Logger("GeckoPromptDelegate").warn("Could not convert uri to file uri", e)
}
return Uri.parse("file:///" + temporalFile.absolutePath)
return Uri.parse("file:///${temporalFile.absolutePath}")
}
@SuppressLint("Recycle")
private fun Uri.getFileName(context: Context): String {
private fun Uri.getFileName(contentResolver: ContentResolver): String {
val returnUri = this
var fileName = ""
this.let { returnUri ->
context.contentResolver.query(returnUri, null, null, null, null)
}?.use { cursor ->
contentResolver.query(returnUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
......
......@@ -518,6 +518,12 @@ class SystemEngineView @JvmOverloads constructor(
val isMultipleFilesSelection = fileChooserParams?.mode == MODE_OPEN_MULTIPLE
val captureMode = if (fileChooserParams?.isCaptureEnabled == true) {
PromptRequest.File.FacingMode.ANY
} else {
PromptRequest.File.FacingMode.NONE
}
val onSelectMultiple: (Context, Array<Uri>) -> Unit = { _, uris ->
filePathCallback?.onReceiveValue(uris)
}
......@@ -535,6 +541,7 @@ class SystemEngineView @JvmOverloads constructor(
PromptRequest.File(
mimeTypes,
isMultipleFilesSelection,
captureMode,
onSelectSingle,
onSelectMultiple,
onDismiss
......
......@@ -6,6 +6,9 @@ package mozilla.components.concept.engine.prompt
import android.content.Context
import android.net.Uri
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Level
import mozilla.components.concept.engine.prompt.PromptRequest.Authentication.Method
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type
/**
* Value type that represents a request for showing a native dialog for prompt web content.
......@@ -45,9 +48,9 @@ sealed class PromptRequest {
val title: String,
val message: String,
val hasShownManyDialogs: Boolean = false,
val onDismiss: () -> Unit,
override val onDismiss: () -> Unit,
val onConfirm: (Boolean) -> Unit
) : PromptRequest()
) : PromptRequest(), Dismissible
/**
* Value type that represents a request for an alert prompt to enter a message.
......@@ -63,9 +66,9 @@ sealed class PromptRequest {
val inputLabel: String,
val inputValue: String,
val hasShownManyDialogs: Boolean = false,
val onDismiss: () -> Unit,
override val onDismiss: () -> Unit,
val onConfirm: (Boolean, String) -> Unit
) : PromptRequest()
) : PromptRequest(), Dismissible
/**
* Value type that represents a request for a date prompt for picking a year, month, and day.
......@@ -95,17 +98,43 @@ sealed class PromptRequest {
* Value type that represents a request for a selecting one or multiple files.
* @property mimeTypes a set of allowed mime types. Only these file types can be selected.
* @property isMultipleFilesSelection true if the user can select more that one file false otherwise.
* @property captureMode indicates if the local media capturing capabilities should be used,
* such as the camera or microphone.
* @property onSingleFileSelected callback to notify that the user has selected a single file.
* @property onMultipleFilesSelected callback to notify that the user has selected multiple files.
* @property onDismiss callback to notify that the user has canceled the file selection.
*/
data class File(
val mimeTypes: Array<out String>,
val isMultipleFilesSelection: Boolean,
val isMultipleFilesSelection: Boolean = false,
val captureMode: FacingMode = FacingMode.NONE,
val onSingleFileSelected: (Context, Uri) -> Unit,
val onMultipleFilesSelected: (Context, Array<Uri>) -> Unit,
val onDismiss: () -> Unit
) : PromptRequest()
) : PromptRequest() {
/**
* @deprecated Use the new primary constructor.
*/
constructor(
mimeTypes: Array<out String>,
isMultipleFilesSelection: Boolean,
onSingleFileSelected: (Context, Uri) -> Unit,
onMultipleFilesSelected: (Context, Array<Uri>) -> Unit,
onDismiss: () -> Unit
) : this(
mimeTypes,
isMultipleFilesSelection,
FacingMode.NONE,
onSingleFileSelected,
onMultipleFilesSelected,
onDismiss
)
enum class FacingMode {
NONE, ANY, FRONT_CAMERA, BACK_CAMERA
}
}
/**
* Value type that represents a request for an authentication prompt.
......@@ -135,8 +164,8 @@ sealed class PromptRequest {
val previousFailed: Boolean = false,
val isCrossOrigin: Boolean = false,
val onConfirm: (String, String) -> Unit,
val onDismiss: () -> Unit
) : PromptRequest() {
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible {
enum class Level {
NONE, PASSWORD_ENCRYPTED, SECURED
......@@ -156,8 +185,8 @@ sealed class PromptRequest {
data class Color(
val defaultColor: String,
val onConfirm: (String) -> Unit,
val onDismiss: () -> Unit
) : PromptRequest()
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible
/**
* Value type that represents a request for showing a pop-pup prompt.
......@@ -201,6 +230,10 @@ sealed class PromptRequest {
val onConfirmPositiveButton: (Boolean) -> Unit,
val onConfirmNegativeButton: (Boolean) -> Unit,
val onConfirmNeutralButton: (Boolean) -> Unit,
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible
interface Dismissible {
val onDismiss: () -> Unit
) : PromptRequest()
}
}
......@@ -4,15 +4,15 @@
package mozilla.components.concept.engine.prompt
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.Alert
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.TimeSelection.Type
import mozilla.components.support.test.mock
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.util.Date
......@@ -76,9 +76,16 @@ class PromptRequestTest {
dateRequest.onConfirm(Date())
dateRequest.onClear()
val filePickerRequest = PromptRequest.File(emptyArray(), true, { _, _ -> }, { _, _ -> }) {}
val filePickerRequest = PromptRequest.File(
emptyArray(),
true,
PromptRequest.File.FacingMode.NONE,
{ _, _ -> },
{ _, _ -> }
) {}
assertTrue(filePickerRequest.mimeTypes.isEmpty())
assertTrue(filePickerRequest.isMultipleFilesSelection)
assertEquals(filePickerRequest.captureMode, PromptRequest.File.FacingMode.NONE)
filePickerRequest.onSingleFileSelected(mock(), mock())
filePickerRequest.onMultipleFilesSelected(mock(), emptyArray())
filePickerRequest.onDismiss()
......@@ -153,4 +160,4 @@ class PromptRequestTest {
confirmRequest.onConfirmNegativeButton(true)
confirmRequest.onConfirmNeutralButton(true)
}
}
\ No newline at end of file
}
......@@ -2,4 +2,16 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mozilla.components.feature.prompts" />
package="mozilla.components.feature.prompts">
<application>
<provider
android:authorities="mozilla.components.feature.prompts.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.feature.prompts
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.Intent.EXTRA_INITIAL_INTENTS
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.Uri
import android.provider.MediaStore.EXTRA_OUTPUT
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.fragment.app.Fragment
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.File
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.ktx.android.content.isPermissionGranted
typealias OnNeedToRequestPermissions = (permissions: Array<String>) -> Unit
/**
* @property activity The [Activity] which hosts the file picker.
* @property fragment The [Fragment] which hosts the file picker.
* @property sessionManager The [SessionManager] instance in order to subscribe
* to the selected [mozilla.components.browser.session.Session].
* @property onNeedToRequestPermissions a callback invoked when permissions
* need to be requested before a prompt (e.g. a file picker) can be displayed.
* Once the request is completed, [onPermissionsResult] needs to be invoked.
*/
internal class FilePicker private constructor(
private val activity: Activity? = null,
private val fragment: Fragment? = null,
private val sessionManager: SessionManager,
private var sessionId: String? = null,
override val onNeedToRequestPermissions: OnNeedToRequestPermissions
) : PermissionsFeature {
constructor(
activity: Activity,
sessionManager: SessionManager,
sessionId: String? = null,
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(activity, null, sessionManager, sessionId, onNeedToRequestPermissions)
constructor(
fragment: Fragment,
sessionManager: SessionManager,
sessionId: String? = null,
onNeedToRequestPermissions: OnNeedToRequestPermissions
) : this(null, fragment, sessionManager, sessionId, onNeedToRequestPermissions)
private val context get() = activity ?: requireNotNull(fragment).requireContext()
/**
* The image capture intent doesn't return the URI where the image is saved,
* so we track it here.
*/
private var captureUri: Uri? = null
@Suppress("LongMethod")
fun handleFileRequest(promptRequest: File, requestPermissions: Boolean = true) {
// Track which permissions are needed.
val neededPermissions = mutableListOf<String>()
// Build a list of intents for capturing media and opening the file picker to combine later.
val intents = mutableListOf<Intent>()
captureUri = null
// Compare the accepted values against image/*, video/*, and audio/*
for (type in MimeType.values()) {
val hasPermission = context.isPermissionGranted(type.permission)
// The captureMode attribute can be used if the accepted types are exactly for
// image/*, video/*, or audio/*.
if (hasPermission && type.shouldCapture(promptRequest.mimeTypes, promptRequest.captureMode)) {
type.buildIntent(context, promptRequest)?.also {
saveCaptureUriIfPresent(it)
startActivityForResult(it, FILE_PICKER_ACTIVITY_REQUEST_CODE)
return
}
}
// Otherwise, build the intent and create a chooser later
if (type.matches(promptRequest.mimeTypes)) {
if (hasPermission) {
type.buildIntent(context, promptRequest)?.also {
saveCaptureUriIfPresent(it)
intents.add(it)
}
} else {
neededPermissions.add(type.permission)
}
}
}
val canSkipPermissionRequest = !requestPermissions && intents.isNotEmpty()
if (neededPermissions.isEmpty() || canSkipPermissionRequest) {
// Combine the intents together using a chooser.
val lastIntent = intents.removeAt(intents.lastIndex)
val chooser = Intent.createChooser(lastIntent, null).apply {
putExtra(EXTRA_INITIAL_INTENTS, intents.toTypedArray())
}
startActivityForResult(chooser, FILE_PICKER_ACTIVITY_REQUEST_CODE)
} else {
onNeedToRequestPermissions(neededPermissions.toTypedArray())