Create and Share a File from Internal Storage - android

My goal is to create a XML file on internal storage and then send it through the share Intent.
I'm able to create a XML file using this code
FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();
I'm stuck trying to retrieve a Uri to the output file in order to share it. I first tried to access the file by converting the file to a Uri
File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);
This returns file:///data/data/com.my.package/files/myfile.xml but I cannot appear to attach this to an email, upload, etc.
If I manually check the file length, it's proper and shows there is a reasonable file size.
Next I created a content provider and tried to reference the file and it isn't a valid handle to the file. The ContentProvider doesn't ever seem to be called a any point.
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;
This returns content://com.my.package.provider/myfile.xml but I check the file and it's zero length.
How do I access files properly? Do I need to create the file with the content provider? If so, how?
Update
Here is the code I'm using to share. If I select Gmail, it does show as an attachment but when I send it gives an error Couldn't show attachment and the email that arrives has no attachment.
public void onClick(View view) {
Log.d(TAG, "onClick " + view.getId());
switch (view.getId()) {
case R.id.share_cancel:
setResult(RESULT_CANCELED, getIntent());
finish();
break;
case R.id.share_share:
MyXml xml = new MyXml();
Uri uri;
try {
uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
//uri is "file:///data/data/com.my.package/files/myfile.xml"
Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());
File f = new File(uri.getPath());
Log.d(TAG, "File length: " + f.length());
// shows a valid file size
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, "Share"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
break;
}
}
I noticed that there is an Exception thrown here from inside createChooser(...), but I can't figure out why it's thrown.
E/ActivityThread(572): Activity
com.android.internal.app.ChooserActivity has leaked IntentReceiver
com.android.internal.app.ResolverActivity$1#4148d658 that was
originally registered here. Are you missing a call to
unregisterReceiver()?
I've researched this error and can't find anything obvious. Both of these links suggest that I need to unregister a receiver.
ChooserActivity has leaked IntentReceiver
Why does Intent.createChooser() need a BroadcastReceiver and how to implement?
I have a receiver setup, but it's for an AlarmManager that is set elsewhere and doesn't require the app to register / unregister.
Code for openFile(...)
In case it's needed, here is the content provider I've created.
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}

It is possible to expose a file stored in your apps private directory via a ContentProvider. Here is some example code I made showing how to create a content provider that can do this.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.providertest"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="15" />
<application android:label="#string/app_name"
android:icon="#drawable/ic_launcher"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="MyProvider"
android:authorities="com.example.prov"
android:exported="true"
/>
</application>
</manifest>
In your ContentProvider override openFile to return the ParcelFileDescriptor
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File cacheDir = getContext().getCacheDir();
File privateFile = new File(cacheDir, "file.xml");
return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
Make sure you have copied your xml file to the cache directory
private void copyFileToInternal() {
try {
InputStream is = getAssets().open("file.xml");
File cacheDir = getCacheDir();
File outFile = new File(cacheDir, "file.xml");
OutputStream os = new FileOutputStream(outFile.getAbsolutePath());
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) > 0) {
os.write(buff, 0, len);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace(); // TODO: should close streams properly here
}
}
Now any other apps should be able to get an InputStream for your private file by using the content uri (content://com.example.prov/myfile.xml)
For a simple test, call the content provider from a seperate app similar to the following
private class MyTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
InputStream is = null;
StringBuilder result = new StringBuilder();
try {
is = getApplicationContext().getContentResolver().openInputStream(uri);
BufferedReader r = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = r.readLine()) != null) {
result.append(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (is != null) is.close(); } catch (IOException e) { }
}
return result.toString();
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
super.onPostExecute(result);
}
}

So Rob's answer is correct I assume but I did it a bit differently. As far as I understand, with the setting in in provider:
android:exported="true"
you are giving public access to all your files?! Anyway, a way to give only access to some files is to define file path permissions in the following way:
<provider
android:authorities="com.your.app.package"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
and then in your XML directory you define file_paths.xml file as follows:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="/" name="allfiles" />
<files-path path="tmp/" name="tmp" />
</paths>
now, the "allfiles" gives the same kind of public permission I guess as the option android:exported="true" but you don't really want that I guess so to define a subdirectory is the next line. Then all you have to do is store the files you want to share, there in that dir.
Next what you have to do is, as also Rob says, obtain a URI for this file. This is how I did it:
Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);
Then, when I have this URI, I had to attach to it permissions for other app to use it. I was using or sending this file URI to camera app. Anyway this is the way how I got the other app package info and granted permissions to the URI:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);
I left the code for camera as I did not want to take some other example I did not work on. But this way you see that you can attach permissions to URI itself.
The camera's thing is that I can set it via ClipData and then additionally set permissions. I guess in your case you only need FLAG_GRANT_READ_URI_PERMISSION as you are attaching a file to an email.
Here is the link to help on FileProvider as I based all of my post on the info I found there. Had some trouble finding a package info for camera app though.
Hope it helps.

None of the above answers helped. My problem was the point of passing intent extras but I'll walk you through all the steps to share a file.
Step 1: Create a Content Provider
This will make the file accessible to whichever app you want to share with.
Paste the following in the Manifest.xml file inside the <application></applicatio> tags
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="{YOUR_PACKAGE_NAME}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
Step 2: Define paths accessible by the content provider
Do this by creating a file called provider_paths.xml (or a name of your choice) under res/xml. Put the following code in the file
<?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>
Step 3: Create the Intent to share the file
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", fileToShare);
intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(fileToShare.getName()));
//Allow sharing apps to read the file Uri
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//Pass the file Uri instead of the path
intentShareFile.putExtra(Intent.EXTRA_STREAM,
uri);
startActivity(Intent.createChooser(intentShareFile, "Share File"));

If you need to permission other apps to see your app's private files (for Share, or otherwise) you might be able to save some time and just use v4 compat library's FileProvider

This is what i'm using:
I combined some answers and used the current AndroidX Doku:
Sharing files Android Development
Basic Process: You change the manifest to make it possible for other apps to access your local files. the filepath's that are allowed to be accessed from outside are found in the res/xml/filepaths.xml. When sharing you create an intent to share and set a Flag that temporarily allowed the other app to access your local files. Android documentation claims this is the secure way to share files
Step1: Add FileProvider to Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.YOUR.APP.PACKAGE.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
Step2: Add filepaths.xml to res/xml (if XML folder does not exists just create it yourself)
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="share/" name="share" />
</paths>
Step3: Use a function like this to start a file share. this function moves the file to the predefined share folder and creates a Url to it. the ShareDir is the File pointing to the files/share/ directory. the copy_File function copies the given file to the share directory in order to be accessible from the outside.
The function also makes it possible to Send the File as email with given header and body. if not needed just set it to empty strings
public void ShareFiles(Activity activity, List<File> files, String header, String body) {
ArrayList<Uri> uriList = new ArrayList<>();
if(files != null) {
for (File f : files) {
if (f.exists()) {
File file_in_share = copy_File(f, ShareDir);
if(file_in_share == null)
continue;
// Use the FileProvider to get a content URI
try {
Uri fileUri = FileProvider.getUriForFile(
activity,
"com.YOUR.APP.PACKAGE.fileprovider",
file_in_share);
uriList.add(fileUri);
} catch (IllegalArgumentException e) {
Log.e("File Selector",
"The selected file can't be shared: " + f.toString());
}
}
}
}
if(uriList.size() == 0)
{
Log.w("StorageModule", "ShareFiles: no files to share");
return;
}
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_SUBJECT, header);
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
activity.startActivity(Intent.createChooser(intent, "Share Files"));
}

Related

Open APK to update app with Download Manager + FileProvider

We have an enterprise app (not play store) that installs via APK that we distribute. In that app is an auto-update procedure that worked fine until SDK 29. I've been trying to update our code to use the new methodologies for downloading and starting a new intent with the downloaded APK, but its been a frustrating whack-a-mole experience.
My current state is that I CAN download the file and can see that it exists, but if I try to start an intent with it, I get the error about the file not being accessible because its an external file. A lot of googling later, I needed to implement file provider, which I've done, but the paths don't match between where the file is downloaded and what I'm passing to the intent.
Basically, I'm really confused about FileProvider and how I'm supposed to use it to get to the downloaded file path and let Android open the APK to install. Prompting the user to install is fine, that's how its always worked for us, and we're not trying to do a "silent" install. I just want it to work.
Pared down relevant code:
private void downloadUpdate(Uri uri) {
final String filename = File.separator + uri.getLastPathSegment();
DownloadManager downloadmanager = (DownloadManager) parent.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setDescription("Downloading");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalFilesDir(getContext(), Environment.DIRECTORY_DOWNLOADS, filename);
try {
onDownloadComplete = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadId == id) {
Toast.makeText(parent, "Download Completed", Toast.LENGTH_SHORT).show();
try {
Intent installIntent = new Intent(Intent.ACTION_VIEW);
String filepath = getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + filename;
File update = new File(filepath);
Uri updateUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", update);
installIntent.setDataAndType(updateUri, "application/vnd.android.package-archive");
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(installIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
parent.registerReceiver(onDownloadComplete,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
downloadId = downloadmanager.enqueue(request);
} catch (Exception ex) {
Log.e("UpdateDownload", "Update Error: " + ex.getMessage());
new ErrorLog(new Date(), "Error downloading update: " + ex.getMessage());
}
}
My provider definition in the manifest:
<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/filepaths" />
</provider>
And my filepaths xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="pictures" path="Pictures" />
<external-files-path name="Download" path="Download/" />
</paths>
Everything downloads fine, and I get all the way to the startActivity line, but then I get an error where android can't parse the package - because its not actually looking at the right file path. In debug mode if I do update.exists(), I get a true, so the file IS being downloaded, but the update filepath does not equal what is found for updateUri.
How do I get fileprovider to provide the proper filepath to the intent? I assume my XML is configured wrong, but I don't understand what exactly the FileProvider is doing here.

Captured Image is not storing in android 11

I'm unable to store captured image in (getExternalFilesDir(Environment.DIRECTORY_PICTURES)) Android 11 device.
I have added
<uses-permissionandroid:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> in manifest and all file access also. But it's not working.
if (Build.VERSION.SDK_INT >= 30) {
if (!Environment.isExternalStorageManager()) {
try {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.addCategory("android.intent.category.DEFAULT")
intent.data = Uri.parse(String.format("package:%s", applicationContext.packageName))
startActivityForResult(intent, 2296)
} catch (e: Exception) {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
startActivityForResult(intent, 2296)
}
}
}
This code is working below Android 11 device. But on Android 11 file is not creating File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) .toString() + "/" + FolderName )
Your phone's camera doesnot have permission to write in the specified location. So to fix this, you need to use file provider and give it appropriate permissions so that the camera can write the image to your file.
To do that,
create a FileProvider. In your manifest file, add:
<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" /> // <-------- see this
</provider>
Now create a files.xml file in your res/xml folder. In it, write some code:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="camera"
path="Camera/" />
<cache-path
name="cache"
path="/" />
<files-path
name="files"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="my_images"
path="/"/>
// todo: add necessary folders according to your requirements...
// also, this is an old example. Consider googling for the latest style. I'm just copying from an old project I have, and it kinda works...
</paths>
So here we are giving the FileProvider the folders that can be shared with external apps.
2. Now create a uri where you want to store the photo. in your activity:
Context applicationContext = getApplicationContext();
File root = getCachedDir(); // consider using getExternalFilesDir(Environment.DIRECTORY_PICTURES); you need to check the file_paths.xml
File capturedPhoto = new File(root, "some_photo.jpeg");
if(!photoFile.exists()) {
photoFile.mkdirs();
}
Uri photoURI = FileProvider.getUriForFile(applicationContext, applicationContext.getPackageName() + ".fileprovider", capturedPhoto);
Please note that my project needed to save picture temporarily, so I had used cachedDir. If you save photo permanently, use getExternalFilesDir(Environment.DIRECTORY_PICTURES); and modify file_paths.xml properly.
Now that we have the correct uri, we can call the camera intent:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoURI);
startActivityForResult(takePictureIntent, REQUEST_CODE);
Finally, in activty result, do something:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
// todo: maybe show photo in an imageView
}
}
I hope this works.
Edit
If you are using this app in production, relying on android's default camera app is a bad idea. Our app previously used this way, and it works with, say, samsung's defaul camera. But a lot of our users used third party apps, such as PixArt, which doesnot save photo to our given location. So we had to implement a builtin camera using CameraX. So consider using CameraX or some other camera library.
First Thing is "android.permission.MANAGE_EXTERNAL_STORAGE" Permission has no relation with saving image.
after Android 11 google say you should do your business in your space.
that mean you cant get or save image or any file as you did before Android 11.
you can only save in Shared folder or in your application storage data/App packagename/.....
if you want to access other app files then you need "android.permission.MANAGE_EXTERNAL_STORAGE" but google say this must be app prior functionality like filemanager or virus scanner like app.
As far as your App concern you havent provide save code.
in Android 11 i am suggestion using Media Api
ContentResolver resolver = mContext.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, s);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + getResources().getString(R.string.app_name) + File.separator + "imgfolder");
contentValues.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
contentValues.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
fos = resolver.openOutputStream(Objects.requireNonNull(imageUri));
try {
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
contentValues.clear();
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
resolver.update(imageUri, contentValues, null, null);
}
this is for image file
use this code for save captured image
String mPath = Environment.getExternalStorageDirectory() + "/Print";
Bitmap tmp = BitmapFactory.decodeFile(mPath);
File imageFile = new File(mPath);
FileOutputStream outStream;
try
{
outStream = new FileOutputStream(imageFile);
try
{
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
outStream.flush();
outStream.close();
} catch (IOException e)
{
e.printStackTrace();
}
} catch (Exception e)
{
e.printStackTrace();
}

Android - play video saved from raw assets to local storage using implicit intent failing

I have a file in my raw assets directory which I am trying to save to shared phone storage that will allow other apps to open and view this video.
Basic code to copy file from assets to local storage and generate intent
Intent in = new Intent(Intent.ACTION_VIEW);
// using getFilesDir() below causes this error:
File videoFile = new File(getExternalFilesDirs(null)[1], "talking.mp4");
// check if we have already copied file over
if (!videoFile.exists()) {
try {
InputStream is = getResources().openRawResource(R.raw.talking);
boolean newFile = videoFile.createNewFile();
if (!newFile) {
throw new Exception("Failed to create file");
}
OutputStream os = new FileOutputStream(videoFile);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
} catch (IOException e) {
Log.w("ExternalStorage", "Error writing " + videoFile, e);
Toast.makeText(this, "Failed to copy talking.mp4", Toast.LENGTH_LONG).show();
return;
} catch (Exception e) {
e.printStackTrace();
}
}
and getting the file using the FileProvider.getUriForFile(Context, String, File) and creating the intent following this:
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName(), videoFile);
// type of file to view
in.setDataAndType(uri, "video/*");
// ask some other app to deal with it
startActivity(in);
in addition, the following is required in my manifest:
<application ... >
//...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${getPackageName()}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
</application>
next, to define the paths mentioned above in the FileProvider:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_files_data" path="." />
<external-files-path name="my_files_ext_data" path="." />
<external-path name="my_files_ext" path="." />
</paths>
Question:
When selecting e.g. VLC for Android as the file to handle the intent, the app doesn't play the video. If I try playing it with Google Photos, it loads forever.
This shows my there is something wrong - however I am not quite sure why this isn't working. Is it related to app permissions accessing content root of another app?

Android Problem with sharing temporary file

I' m trying to create a temporary file and share it.
So I created this class:
public class GenerateFile {
public static File writeToFile(Context mcoContext, String sBody) {
String fileName = "LOG FILE_" + String.valueOf(System.currentTimeMillis()) +".txt";
File file = new File(mcoContext.getCacheDir(), fileName);
try{
FileWriter writer = new FileWriter(file);
writer.append(sBody);
writer.flush();
writer.close();
return file;
}catch (Exception e){
Toast.makeText(mcoContext, "File write failed: " + e.toString(), Toast.LENGTH_LONG).show();
}
return null;
}
}
to generate a file that after I will share here:
String logContent = "123";
File filePath = new File(file.getAbsolutePath(), "external_files");
filePath.mkdir();
Uri uri = FileProvider.getUriForFile(StatusActivity.this, getPackageName(), filePath);
Intent intent = ShareCompat.IntentBuilder.from(StatusActivity.this)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.setAction(Intent.ACTION_VIEW) //Change if needed
.setDataAndType(uri, "text/*")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
And in the manifest there are already this permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.READ"/>
and the provider declaration
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="android.getqardio.com.gmslocationtest"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
The provider_paths class is defined in this way:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="share"
path="external_files"/>
</paths>
But it generate the message, when I try to share it by mail or telegram "Unable to attach file" or "Unsupported attachment". Also it seems to me that the file is not created.
Other apps do not have access to your app's getCacheDir(). FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION are for content Uri values, not file Uri values. And, on Android 7.0+ devices, your code should crash with a FileUriExposedException.
Use FileProvider to make your content available to other apps, and use FileProvider.getUriForFile() to get the Uri to put in the Intent.
So I follow the suggestion of #CommonsWare, and I edited my code. This is the final result:
public class GenerateFile {
public static Uri getFileURI(Context context, String nameFile, String content, String fileExtension) {
DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd");
Date date = new Date();
String fileName = dateFormat.format(date)+nameFile+fileExtension;
File file = new File(context.getCacheDir(), fileName);
try{
FileWriter writer = new FileWriter(file);
writer.append(content);
writer.flush();
writer.close();
//Toast.makeText(context, "Writing to the file completed successfully", Toast.LENGTH_LONG).show();
}catch (Exception e){
Toast.makeText(context, "File writing failed: " + e.toString(), Toast.LENGTH_LONG).show();
}
File filePath = new File(context.getCacheDir(), "");
File newFile = new File(filePath, fileName);
return FileProvider.getUriForFile(context, "MYPACKAGE.fileprovider", newFile);
}
}
and in another class:
private void sendFile(String nameFile, String logContent, String fileExtension) {
Uri contentUri = GenerateFile.getFileURI(getApplicationContext(), nameFile, logContent, fileExtension);
Intent intent = ShareCompat.IntentBuilder.from(StatusActivity.this)
.setStream(contentUri) // uri from FileProvider
.setType("text/plain")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "send"));
}
so to send the file. I also deleted the permission (previously mentioned) in the manifest, because I didn't need it anymore.
And I also edited my provider and provider_path file like that:
<provider
android:name="android.support.v4.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>
<?xml version="1.0" encoding="utf-8"?>
<cache-path
name="my_files"
path=""
/>
Now it works! Thank you very much guys for the help!
Did you specifically ask the user for those permissions? It's not enough to just put the permissions in the manifest for target sdks below 28. Also, in Android Q, you will need to work around external storage permissions altogether as this is disallowed.

How to send(share) csv file?

How to send(share) *csv file?
I have android app, wich create a csv file. And i want to share it: send to email, open at excel and so on.
I read tutorials:set up file sharing and sharing a file, but can not understand: is it for my option or not?
I declare a manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.nam1.name2.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
But what i should do next?
Can you tell me some simple code example (maybe git-repo) how to share file to another apps?
Thank you!
EDIT: I write some new code to share files (look at FileProvider example)
public class ExportActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_export);
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
intentShareFile.setAction(Intent.ACTION_SEND);
File imagePath = new File(getApplicationContext().getFilesDir(), "statistics");
File newFile = new File(imagePath, "stat.csv");
CSVHelper csvHelper = new CSVHelper(SharedData.AllPlayersColl);
try {
String path=imagePath.getAbsolutePath();
csvHelper.SaveToCSV(path);
} catch (IOException e) {
e.printStackTrace();
}
Context context=getApplicationContext();
Uri contentUri = getUriForFile(context, "com.name1.name2.fileprovider", newFile);
List<ResolveInfo> resInfoList = getApplicationContext().getPackageManager()
.queryIntentActivities(intentShareFile, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
getApplicationContext()
.grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intentShareFile.setData(contentUri);
intentShareFile.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
setResult(100, intentShareFile);
startActivityForResult(intentShareFile,100);
}
}
Where CSVHelper is:
fun SaveToCSV(fileName: String?) {
if (fileName == null)
throw NullPointerException("fileName is null!")
val writer = CSVWriter(FileWriter(fileName), '\t')
// make csv file
writer.writeNext(firstLine);
}
And filepaths is:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<files-path name="stat" path="statistics/" />
</paths>
But, i get a error:
Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.SEND dat=content://com.name1.name2.fileprovider/stat/stat.csv flg=0x2 }
I want, that when i start an Intent a list of variants shows (at android device) : email_app,excel_app and so on. So, i want to send noname intent.
FileProvider is a helper class designed to share files as content:// URIs instead of file:// URIs which have been deprecated for some time and forbidden since API level 24.
You have declared the content provider, you need now to configure it, i.e. choose what file it can serve. This is done in the xml/filepath file that you have declared in the meta-data. You will then be able to generate shareable content URIs using FileProvider.getUriForFile().
There is a complete wrap up
Unfortunately not all application understand content:// URIs. If you need to share a file with a (very old or badly written) application which only handle file:// URIs you may still use them if you set the target level of your application to 23 or lower.

Categories

Resources