FileProvider is unable to attach file to email client on Android 11 - android

I used to attach a logs file, that I was storing in externalCacheDir, to a mail client.
Using this : intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(logFile))
However, starting from Android 11 it just stopped working. So I tried using FileProvider. But once the mail client opens it says "Couldn't attach file".
I have tried doing everything I could find here on SO and on the internet in general. But I can't seem to find the solution that will actually work.
Activity :
val logFile: File = File(globalContext.externalCacheDir, "MyLogFile.log")
fun Activity.openMail(
type: String = "text/plain"
): Boolean {
val intent = Intent(Intent.ACTION_SENDTO)
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("<some address...>"))
intent.putExtra(Intent.EXTRA_SUBJECT, "Android Logs")
intent.putExtra(Intent.EXTRA_TEXT, "some text")
val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", logFile)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.type = type
intent.data = Uri.parse("mailto:")
if (intent.resolveActivity(packageManager) != null) {
startActivity(Intent.createChooser(intent, "Send email via::"))
return true
} else {
return false
}
}
Manifest :
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<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/provider_paths" />
</provider>
provider_paths :
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-cache-path name="external_cache" path="."/>
</paths>

Related

`Intent.ACTION_VIEW` fails with a `no file received` error

My current code :
Just focus on the fileOpener variable. The SafeFiles class is a self declared variable which has the file's extensions and such. Don't worry too much about it.
fileOpener = { file: File ->
val uri: Uri = Uri.fromFile(file).normalizeScheme()
val mime = MimeTypeMap
.getSingleton()
.getMimeTypeFromExtension(safeFile.extension.substring(1))
Log.d(TAG, "mime = $mime")
val intent = Intent(Intent.ACTION_VIEW)
intent.data = uri
intent.type = mime
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
Log.d(
TAG, "data received:" +
"uri = $uri\n" +
"extension = ${safeFile.extension.substring(1)}\n" +
"path = ${file.absolutePath}\n" +
"size = ${file.totalSpace} "
)
CoroutineScope(Dispatchers.Main).launch {
context!!.startActivity(intent)
}
}
Logs -
2022-07-30 16:41:41.005 19233-19285/com.example.cryptile D/SafeViewerFragment: mime = image/png
2022-07-30 16:41:41.005 19233-19285/com.example.cryptile D/SafeViewerFragment: data received:uri = file:///storage/emulated/0/Cryptile/CRYPTILE_2022_07_30/.CACHE/aa854c7e-9fa4-4313-9750-cf58fde467b8.png
extension = png
path = /storage/emulated/0/Cryptile/CRYPTILE_2022_07_30/.CACHE/aa854c7e-9fa4-4313-9750-cf58fde467b8.png
size = 119640424448
Clearly, the size not being zero means the file exists. I have also opened the mentioned file from the device's file explorer and everything seems to work fine. the only conclusion I came to is this might be some issue with the file provider. So, here's the manifest -
Manifest -
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cryptile">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application
android:name=".app_data.AppApplication"
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/Theme.CRYPTILE">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.cryptile.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
</application>
</manifest>
And the filepaths.xml -
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="/storage/emulated/0"
path="." />
</paths>
the only conclusion I came to is this might be some issue with the file provider
You are not using FileProvider. You are using Uri.fromFile(), which has been all but banned for six years.
Replace:
val uri: Uri = Uri.fromFile(file).normalizeScheme()
with:
val uri: Uri = FileProvider.getUriForFile(context!!, "com.example.cryptile.fileProvider", file)
(and then rewrite your code to avoid the !!)
Adding this might have helped -
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
My final code is -
fileOpener = { file: File ->
val uri: Uri = FileProvider.getUriForFile(
requireContext(),
"com.example.cryptile.fileProvider",
file
)
val mime = MimeTypeMap
.getSingleton()
.getMimeTypeFromExtension(safeFile.extension.substring(1))
Log.d(TAG, "mime = $mime")
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.action = Intent.ACTION_VIEW
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.setDataAndType(uri, mime)
Log.d(
TAG, "data received:" +
"uri = $uri\n" +
"extension = ${safeFile.extension.substring(1)}\n" +
"path = ${file.absolutePath}\n" +
"size = ${file.totalSpace} "
)
CoroutineScope(Dispatchers.Main).launch {
context!!.startActivity(intent)
}
}
Didn't make any changes anywhere else outside this lambda. Just make sure to compare the intent code because it is a little different compared to the question's code.

How can I share pdf file in android?

I try to share a pdf file from android/data/mypackage/files/file.pdf
I generate these pdfs also in this app, and when I try to share it, the pdf doesn't appear in on attached files from email, or google drive says something like: "No data to share".
Here is my code for sharing pdf:
val aName = intent.getStringExtra("iName")
val file = File(this.getExternalFilesDir(null)?.absolutePath.toString(), "$aName")
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.putExtra(Intent.EXTRA_STREAM, file)
shareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
shareIntent.type = "application/pdf"
startActivity(Intent.createChooser(shareIntent, "share.."))
Toast.makeText(this,"$file",Toast.LENGTH_SHORT).show()
The pdf path looks correct when I toast it:
The problem is that you are not using a URI, just sending a path, you need several things.
Provider paths
You have to create provider_paths.xml under xml folder in res :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="files_root"
path="/" />
</paths>
Set the provider in the Manifest under Aplication:
<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>
Get the URI
fun uriFromFile(context:Context, file:File):Uri {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
}
else
{
return Uri.fromFile(file)
}
}
Your final code:
val aName = intent.getStringExtra("iName")
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.putExtra(Intent.EXTRA_STREAM, uriFromFile(context,File(this.getExternalFilesDir(null)?.absolutePath.toString(), "$aName")))
shareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
shareIntent.type = "application/pdf"
startActivity(Intent.createChooser(shareIntent, "share.."))
I didn't test the code, write it from "memory", let me know if it works for you.

Install apk FileProvider

I need to download .apk from server and promt user to install it. Its an actual update of b2b app and not malware.
For new android versions i need to use FileProvider.
Download works fine with download manager and on download is also triggered.
File is visible in device file explorer and File apk has correct path.
Allowed it in unknown apps.
Full app path: file:///storage/emulated/0/Android/data/com.example.trebovanje/files/Download/update_1.0-5.apk
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
<provider
android:name=".GenericFileProvider"
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>
And the code for install intent:
File apk = new File(Uri.parse(file).getPath());
if(apk.exists()) {
MimeTypeMap map = MimeTypeMap.getSingleton();
String ext = MimeTypeMap.getFileExtensionFromUrl(apk.getName());
String mimeType = map.getMimeTypeFromExtension(ext);
if (mimeType == null) mimeType = "*/*";
Intent promptInstall = new Intent(Intent.ACTION_VIEW);
promptInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
promptInstall.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
promptInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
promptInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //new androids
try {
Uri data = getFileUri(getApplicationContext(), apk);
promptInstall.setDataAndType(data, mimeType);
startActivity(promptInstall);
}catch(Exception e){
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}else{ //old anrdoids
Uri data = Uri.fromFile(apk);
promptInstall.setDataAndType(data, mimeType);
startActivity(promptInstall);
}
}
private Uri getFileUri(Context context, File file) {
return FileProvider.getUriForFile(context, getPackageName() + ".provider", file);
}
I get always get "there is a problem parsing the package".

how to share multiple files with ShareCompat

My question is how to share multiple files with ShareCompat intentBuilder in android.
My code throws exception:
android.content.ActivityNotFoundException: No Activity found to handle
Intent { act=android.intent.action.SEND_MULTIPLE flg=0x80001
pkg=com.google.android.gm (has clip) (has extras) }
My code is:
static void with(Activity activity, ArrayList<String> imageFile,String app){
ArrayList<Uri> imagesUri=new ArrayList<>();
for (String i:imageFile)
imagesUri.add(FileProvider.getUriForFile(
activity, FILES_AUTHORITY, new File(i)));
Intent shareIntent = ShareCompat.IntentBuilder.from(activity).getIntent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM,imagesUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(shareIntent);
}
Is throws error for every app (package name).
the following works for me:
ShareCompat.IntentBuilder shareIntent = ShareCompat.IntentBuilder.from(activity).setType("image/*");
for(String address : addresses ) {
File file = new File(address);
Uri imageUri = FileProvider.getUriForFile(
activity,
"app.signature.fileprovider",
file);
shareIntent.addStream(imageUri);
}
Intent intent=shareIntent.getIntent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Choose Application"));
you should use addStream() to add multiple files.
After a very hard time searching on sites like stackoverflow. I found a solution in #Ian Lake's blog post that works for me and makes sense too.
ShareCompat.IntentBuilder shareIntent = ShareCompat.IntentBuilder.from((Activity) context)
.setType("video/image/*");
String[] files = parentDirectory.list();
if (files != null) {
for (String address : files) {
File file = new File(address);
File filePath = new File(context.getExternalFilesDir(null), "vids"); //vids is a directory local to my app in i.e storage/0/emulated/Android/data/com.myapp.videoeditor/vids
File newFile = new File(filePath, file.getName());
if (address.contains("cut_video")) {
Uri uri = FileProvider.getUriForFile(
context,
"com.myapp.videoeditor.fileprovider",
newFile);
shareIntent.addStream(uri);
}
}
}
Intent intent = shareIntent.getIntent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, "Choose..."));
res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="external_files" path="vids/"/>
</paths>
Manifest
<application
....
....>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.myapp.videoeditor.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<!-- ressource file to create -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
...
...
</application>
Did you add intent filter in AndroidManifest.xml for the activity that is supposed to receive the files?
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>

install apk from url

I am trying to install an APK from a URL. This is my code:
Intent promptInstall = new Intent(android.content.Intent.ACTION_VIEW);
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
promptInstall.setDataAndType(Uri.parse("http://10.0.2.2:8081/MyAPPStore/apk/Teflouki.apk"), "application/vnd.android.package-archive" );
startActivity(promptInstall);
But I have this problem:
05-10 15:09:29.511: ERROR/AndroidRuntime(1668): android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=http://10.0.2.2:8081/MyAPPStore/apk/Teflouki.apk typ=application/vnd.android.package-archive flg=0x10000000 }
Thanks in advance.
You should download xxx.apk in storage before install by:
Intent promptInstall = new Intent(android.content.Intent.ACTION_VIEW);
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
promptInstall.setDataAndType(Uri.parse("storage/xxx.apk"), "application/vnd.android.package-archive" );
startActivity(promptInstall);
This won't help if the app is not available on the mearketplace, but in case it is:
Uri marketUri = Uri.parse("market://search?q=pname:com.appmaker.tefloukipackage");
Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
try {
context.startActivity(marketIntent);
} catch (ActivityNotFoundException ex) {
showAlertDialog(context, "Error", "Could not launch the market application.", true, null);
}
Follow along.
In your module manifest, add
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
//Inside application block
<application>
<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_path"/>
</provider>
</application>
In your module res/xml folder, if not create this folder, with file provider_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
And use this method.
private fun updateApplication(activity: Activity) {
//This will get you the root directory path
val externalStoragePublicDirectory: String =
Environment.getExternalStorageDirectory().path
val externalStoragePublicDirectoryFile =
File(externalStoragePublicDirectory, "MyApp" + ".apk")
val uri = FileProvider.getUriForFile(
activity.applicationContext,
activity.applicationContext.packageName + ".provider",
externalStoragePublicDirectoryFile
)
val installAppIntent = Intent(Intent.ACTION_VIEW)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
.setDataAndType(
uri,
"application/vnd.android.package-archive"
)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
activity.startActivity(installAppIntent)
//This will close your app, remove if not needed
exitProcess(0)
}
Important, also go to your phone settings, search for unknown sources, enable it in old devices, but in new devices, search for your app and allow it the permission to install new app packages. Only then you will get the pop up to install the app.

Categories

Resources