This question already has answers here:
Create a file from a photo URI on Android
(1 answer)
Android - Get real path of a .txt file selected from the file explorer
(1 answer)
Closed 1 year ago.
I'm trying to get an backup file from downloads folder by an intent. The intent picker work's fine
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.type = "application/zip"
intent.flags = FLAG_GRANT_READ_URI_PERMISSION
registerForActivityResult.launch(intent)
in the file result i'm trying like this:
try {
val path: String = data.dataString.toString()
val uri = FileProvider.getUriForFile(this, applicationContext.packageName.toString() + ".provider", File(path))
val file = File(uri.path.toString())
if (file.exists()) {
restoreDatabase()
}
else{
//error
}
} catch (e: Exception) {
Log.e("pickFile", e.toString())
}
int the exception i'm getting the error (the error ocurrs in line: val uri = FileProvider.getUriForFile..... ):
"java.lang.IllegalArgumentException: Failed to find configured root that contains /content:/com.android.externalstorage.documents/document/primary%3ADownload%2Fdatabase.zip"
i have an provider configured:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="myPackage.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
providers xml
<paths>
<external-files-path
name="external_files"
path="." />
<external-path
name="external"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Related
Programming in kotlin and using an implicit intent. I have created a txt-file and want to attach this automatically to the email created with the intent. This file is not attaching.
binding.shareAction.setOnClickListener {
lifecycleScope.launch {
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_STREAM, File("src/main/java/com/example/openlog/item_logs.txt"))
sendIntent.type = "*/*"
startActivity(Intent.createChooser(sendIntent, "SHARE"))
}
}
Need to convert File into Uri.
val contentUri = FileProvider.getUriForFile(
this,
"com.stackkotlin.provider", //use your app signature + ".provider"
newFile
)
Try below code:
val newFile = getFileFromAssets(this, "demo.txt")
val contentUri = FileProvider.getUriForFile(
this,
"com.stackkotlin.provider", //use your app signature + ".provider"
newFile
)
Log.e("filePath-----",""+contentUri)
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.type = "*/*"
sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(sendIntent, "SHARE"))
I am using asset file, Get file from assets folder.
#Throws(IOException::class)
fun getFileFromAssets(context: Context, fileName: String): File = File(context.cacheDir, fileName)
.also {
if (!it.exists()) {
it.outputStream().use { cache ->
context.assets.open(fileName).use { inputStream ->
inputStream.copyTo(cache)
}
}
}
}
For getting Uri from File we need file provider
Add FileProvider in AndroidManifest.xml
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Add file_paths.xml file under res-> xml folder
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
I am using :
/* to pass to dl manager https://github.com/tonyofrancis/Fetch */
val destinationPath = contexct!!.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
as destination path to store file
println("$TAG , downloadAndShow >> destinationPath : $destinationPath , ${destinationPath .exists()}")
it prints: destinationPath
: storage/emulated/0/Android/data/ir.vasl.datyar.lawyer/files/Download/tiny-824-۲۰۲۱-۱۰-۳۱-۱۴-۱۳-۳۲.jpg , true
In AndroidManifest
<application
android:requestLegacyExternalStorage="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And file_paths:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
<files-path
name="files-path"
path="." />
<external-files-path
name="Download"
path="." />
<cache-path
name="cache_files"
path="." />
</paths>
but on onComplete
method of dl manager listener
val uriFromFile = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
/* Error Line */
FileProvider.getUriForFile(
it,
it.applicationContext.packageName + ".provider",
File(download.file)
)
} else Uri.fromFile(File(download.file))
Faced error :
IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.pachagename.project/files/Download/tiny-824-۲۰۲۱-۱۰-۳۱-۱۴-۱۳-۳۲.jpg
File(download.file).exists() returns true
Solution:
val destinationPath = File(it.filesDir, "files")
<external-files-path
name="files"
path="files/" />
Not sure why I'm getting this as I thought I followed the example here correctly.
Many others have asked questions around this same thing but after looking through the suggestions it's still broken.
The full exception looks like this:
java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.company.app/files/app_videos/VIDEO_20210202_184514.mp4
at androidx.core.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:744)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:418)
The manifest looks like this:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
The provider_paths.xml file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="/"/>
<external-cache-path name="external_cache" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />
<files-path name="app_videos" path="files/" />
</paths>
The code I am using (pretty much cut and paste from Google's site) looks like this:
val imagePath = File(context.filesDir, "app_videos")
Timber.i("imagePath: ${imagePath.name}")
val newFile = File(imagePath, name)
Timber.i("newFile exists: ${newFile.exists()} length: ${newFile.length()}")
val uri = FileProvider.getUriForFile(context.applicationContext, context.packageName + ".provider", newFile)
Timber.i("Adding Uri: $uri")
That last line never executes as the crash happens on the line before it.
Now, I know that I saved the file off here:
/data/user/0/com.company.app/files/VIDEO_20210202_/data/user/0/com.standardandroid.stalkersport/files/VIDEO_20210202_200455.mp4
I know it's there because I can play it back just fine using other code
So, I know the file exists. I just can't create a Uri for it.
Can anyone see what I am doing wrong please?
OK...got it. Needed to tweak a couple things. First the code should look like this
private fun shareMultipleVideos(names: List<String>, context: Context) {
val uris: ArrayList<Uri> = ArrayList()
for (name in names) {
val videoFile = File(context.filesDir, name)
Timber.i("videoFile ${videoFile.name} exists: ${videoFile.exists()}")
val uri = FileProvider.getUriForFile(context.applicationContext, context.packageName + ".provider", videoFile)
Timber.i("Adding Uri: $uri")
uris.add(uri)
}
val intent = Intent()
intent.action = Intent.ACTION_SEND_MULTIPLE
intent.putExtra(Intent.EXTRA_SUBJECT, "Shared files")
intent.type = "video/mp4"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Timber.i("Intent: $intent")
try{
ContextCompat.startActivity(context, Intent.createChooser(intent, "Shared Videos"), null)
} catch (e: Exception) {
Timber.e("Exception starting activity. \nException was ${e.message}\n Stack trace to follow:\n ${e.stackTrace}")
}
}
Next, the paths xml file needed to be tweaked a bit. Here are the results there:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="/"/>
<external-cache-path name="external_cache" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />
<files-path name="app_videos" path="." />
</paths>
When attaching file to Gmail I briefly see the file in attachments and then get Toast saying "Unable to attach file" and then it's gone. It works fine with Drive, Discord and other apps..
Also the file stays in attachments on emulator but when I send it, the mail is send without attachments. I have a simple .csv file and attach it via FileProvider.
Tried writing to internal storage, didn't help.
val fileLocation = File(requireContext().getExternalFilesDir("data"), "data.csv")
// Saving the file into device
val streamOut =
FileOutputStream(fileLocation)
streamOut.write(myString.toByteArray())
streamOut.close()
// Exporting
val contentUri = FileProvider.getUriForFile(
requireContext(),
"mypackage.fileprovider",
fileLocation
)
val fileIntent = Intent(Intent.ACTION_SEND)
.setType("text/csv")
.putExtra(Intent.EXTRA_SUBJECT, "Data")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, contentUri)
val chooser = Intent.createChooser(
fileIntent,
requireContext().resources.getText(R.string.send_to)
)
chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val resInfoList: List<ResolveInfo> = requireActivity().packageManager
.queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
requireActivity().grantUriPermission(
packageName,
contentUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
requireActivity().startActivity(
chooser
)
provider_paths
<paths>
<external-files-path
name="data"
path="." />
</paths>
Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="mypackage.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
Solved it by changing file_paths.xml according to this template:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Can you try to update your file_paths.xml to use a specific path with the external-path and try if it works?
See my working solution below. It uses multiple attachments but it works with the Gmail app:
java class
private void prepareEmail(File report, List<Expense> openExpenses) {
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{preferences.getEmailReceiver()});
intent.putExtra(Intent.EXTRA_SUBJECT, preferences.getEmailSubject());
intent.putExtra(Intent.EXTRA_TEXT, preferences.getEmailBody());
ArrayList<Uri> uris = new ArrayList<>();
uris.add(FileProvider.getUriForFile(getApplicationContext(), "my.package", report));
for (Expense expense : openExpenses) {
if (expense.getType() == ExpenseType.EXPENSE.getValue()) {
File file = new File(expense.getReceipt());
uris.add(FileProvider.getUriForFile(getApplicationContext(), "my.package", file));
}
}
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
startActivity(Intent.createChooser(intent, getResources().getString(R.string.report_report_send)));
}
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/my.package/files/Pictures" />
<external-path name="my_pdfs" path="Android/data/my.package/files/Documents" />
<external-path name="my_reports" path="Android/data/my.package/files" />
<files-path name="files" path="." />
</paths>
manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="my.package"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
I'm trying to launch the camera to take a picture, then save it into gallery and recover the Uri, to show it into image view.
With this, later on I will send it to FirebaseStorage.
(I simplified names of variables and methods to do ir more readable)
At this moment I got the click action that checks the permissions needed.
button.setOnClickListener {
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
Permissions.check(context!!, permissions, null, null, object : PermissionHandler() {
override fun onGranted() {
dispatchTakePictureIntent()
}
})
}
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(context!!.packageManager)?.also {
val photoFile: File? = try {
createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
Log.d("PictureIntent", "Error trying to get file")
null
}
photoFile?.also {
val photoURI: Uri = FileProvider.getUriForFile(
context!!,
"com.elias.myapplication.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
activity!!.startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
}
Following the documentation of google, in Manifest I added the declaration of FileProvider like this:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
And here is where all the problems come...
In the xml file file_paths if I put the path like example :
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="my_images"
path="/storage/emulated/0/Android/data/com.elias.myapplication/files/Pictures/" />
</paths>
I get this error:
Process: com.elias.myapplication, PID: 24093
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
at androidx.core.content.FileProvider.parsePathStrategy(FileProvider.java:605)
at androidx.core.content.FileProvider.getPathStrategy(FileProvider.java:579)
at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:417)
at com.elias.myapplication.ui.main.MainFragment.dispatchTakePictureIntent(MainFragment.kt:76)
at com.elias.myapplication.ui.main.MainFragment.access$dispatchTakePictureIntent(MainFragment.kt:28)
at com.elias.myapplication.ui.main.MainFragment$onViewCreated$1$1.onGranted(MainFragment.kt:59)
at com.nabinbhandari.android.permissions.Permissions.check(Permissions.java:105)
at com.el.myapplication.ui.main.MainFragment$onViewCreated$1.onClick(MainFragment.kt:57)
at android.view.View.performClick(View.java:7333)
at android.widget.TextView.performClick(TextView.java:14160)
at android.view.View.performClickInternal(View.java:7299)
at android.view.View.access$3200(View.java:846)
at android.view.View$PerformClick.run(View.java:27773)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6981)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
Reading more question on SO, i found a "solution". Modify the xml file with this:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
With this solution doesn't crash, BUT, in the onActivityResult, the intent arrives null
And here is where I'm lost.
How can I solve this?
I get this error:
path is relative to the base location defined in the element name. Use:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="my_images"
path="Pictures" />
</paths>
BUT, in the onActivityResult, the intent arrives null
It's supposed to be null. You know where the image is supposed to be: in the location identified by photoFile. Look for it there. See this sample app for an example of using FileProvider with ACTION_IMAGE_CAPTURE.