Followed https://developer.android.com/training/secure-file-sharing/index.html and able to share files in the internal directory(/data/data/package/files/xxx/) of app to client app using fileprovider.
How to share the files in assets folder(instead of internal directory) to the client app.
Thanks
See CWAC-Provider from CommonsWare which is a library to do precisely what you want.
This is the way i used finally, hope this will help someone.
Added provider in manifest file
<provider
android:name=".AssetsProvider"
android:authorities="yourpackage.provider"
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="yourpermission"></provider>
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="application/octet-stream" />
</intent-filter>
</activity>
Following inProvider Activity onCreate() to get assets list and return uriArray to caller (Consumer App)
String[] assetFilesList = null;
// Get Asset Mangaer
AssetManager assetManager = getAssets();
try {
assetFilesList = assetManager.list();
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
// Set up an Intent to send back to apps that request files
mResultIntent = new Intent("yourpackage.ACTION_SEND_MULTIPLE");
// new Uri list
ArrayList<Uri> uriArrayList = new ArrayList<>();
// Set the Activity's result to null to begin with
setResult(Activity.RESULT_CANCELED, null);
Uri fileUri;
if (assetFilesList != null) {
for (String currFile : assetFilesList) {
Log.i(TAG, "Adding File " + currFile);
// parse and create uri
fileUri = Uri.parse("content://" + this.getPackageName() + ".provider/" + currFile);
// add current file uri to the list
uriArrayList.add(fileUri);
}
}
else {
Log.e(TAG, "files array is pointing to null");
}
if (uriArrayList.size() != 0) {
// Put the UriList Intent
mResultIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriArrayList);
mResultIntent.setType("application/octet-stream");
// Set the result
this.setResult(Activity.RESULT_OK, mResultIntent);
} else {
// Set the result to failed
mResultIntent.setDataAndType(null, "");
this.setResult(RESULT_CANCELED, mResultIntent);
}
// Finish Activity and return Result to Caller
finish();
My Assets Provider Class, I have not implemented query, update etc... as these are not necessary for my case.
public class AssetsProvider extends ContentProvider {
static final String TAG = "AssetsProvider";
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
Log.v(TAG, "AssetsGetter: Open asset file " + uri.toString());
AssetManager am = getContext().getAssets();
String file_name = uri.getLastPathSegment();
if (file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
return afd;
}
#Override
public String getType(Uri p1) {
// TODO: Implement this method
return null;
}
#Override
public int delete(Uri p1, String p2, String[] p3) {
// TODO: Implement this method
return 0;
}
#Override
public Cursor query(Uri p1, String[] p2, String p3, String[] p4, String p5) {
// TODO: Implement this method
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
// TODO: Implement this method
return super.query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
}
#Override
public Uri insert(Uri p1, ContentValues p2) {
// TODO: Implement this method
return null;
}
#Override
public boolean onCreate() {
// TODO: Implement this method
return false;
}
#Override
public int update(Uri p1, ContentValues p2, String p3, String[] p4) {
// TODO: Implement this method
return 0;
}
}
Gradle build options to avoid compression for assets files (these are the types of files i had in assets)
aaptOptions {
noCompress '.json' , '.xls'
}
Following in the Consumer activity
In onCreate() -- setPackage() is required since we want send ACTION_PICK to specific application
Intent mRequestFileIntent = new Intent(Intent.ACTION_PICK);
mRequestFileIntent.setPackage("yourAssetsProviderpackage");
mRequestFileIntent.setType("application/octet-stream");
try {
startActivityForResult(mRequestFileIntent, 0);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Install Assets Provider app before start", Toast.LENGTH_LONG).show();
finish();
}
Added Override method onActivityResult()
public void onActivityResult(int requestCode, int resultCode,
Intent returnIntent) {
// If the selection didn't work
if (resultCode != Activity.RESULT_OK) {
// Exit without doing anything else
Log.e(TAG, "Activity returned fail");
} else {
// get array list
ArrayList<Uri> uriArrayList = returnIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
// create directory in internal storage to store the assets from uri list
String toPath = this.getFilesDir().getPath();
if (uriArrayList != null) {
AssetFileDescriptor mInputAFD;
for (int i = 0; i < uriArrayList.size(); i++) {
// Get the file's content URI
Uri returnUri = uriArrayList.get(i);
try {
mInputAFD = getContentResolver().openAssetFileDescriptor(returnUri, "r");
// Get file name
String fileName = returnUri.getLastPathSegment();
Log.i(TAG, "URI " + returnUri.toString() + " fileName " + fileName);
// Create dest filename and copy
File dest = new File(toPath + "/" + fileName);
copyRaw(mInputAFD, dest);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
// Break loop at first exception
break;
}
}
}
}
}
CopyRaw method to copy the file using AssetFileDescriptor
public void copyRaw(AssetFileDescriptor fd, File destinationFile) throws IOException {
FileChannel sourceChannel = new FileInputStream(fd.getFileDescriptor()).getChannel();
FileChannel destinationChannel = new FileOutputStream(destinationFile).getChannel();
sourceChannel.transferTo(fd.getStartOffset(), fd.getLength(), destinationChannel);
}
Add Permission in Consumer manifest file
<uses-permission android:name="yourpermission" />
Related
I want to save a file on a SD card folder.
And I can't use V4 support on my project.
So I call:
Intent itent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
itent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
itent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(itent, requestCodeTree);
Then on the onActivityResult, I have:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
switch(requestCode) {
case requestCodeTree:
saveFile(intent.getData());
break;
}
}
}
And the code for saveFile is:
private void saveFile(Uri data) {
ContentResolver contentResolver = context.getContentResolver();
InputStream in = null;
OutputStream out = null;
try {
// Problems start here ************************
Uri toUriFile= getUriBackupFile(context, data);
// ********************************************
if (toUriFile==null) {
Uri toUriFolder = DocumentsContract.buildDocumentUriUsingTree(data, DocumentsContract.getTreeDocumentId(data));
toUriFile = DocumentsContract.createDocument(contentResolver, toUriFolder, "", backupName);
}
out = contentResolver.openOutputStream(toUriFile);
in = new FileInputStream(fromFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
// write the output file (the file is now copied)
out.flush();
out.close();
} catch (FileNotFoundException e) {
Log.e(TAG, "Failed", e);
} catch (Exception e) {
Log.e(TAG, "Failed", e);
}
}
So far so good.
Problems start when I call getUriBackupFile to get the uri of the destination file.
To do that, I query the ContentResolver with buildChildDocumentsUriUsingTree and try to filter the result where DocumentsContract.Document.COLUMN_DISPLAY_NAME matches my file's display name, like this:
private static Uri getUriBackupFile(Context context, Uri treeUri) {
final ContentResolver resolver = context.getContentResolver();
final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
treeUri,
DocumentsContract.getTreeDocumentId(treeUri));
Cursor c = null;
try {
String[] projections = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME};
// this line doesn't seem to have any effect !
String selection = DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = '" + backupName + "' ";
// *************************************************************************
c = resolver.query(childrenUri, projections, selection, null, null);
if (c!=null && c.moveToFirst()) {
// Here I expect to have c.getCount() == 1 or == 0
// But actually c.getCount() == [Number of children in the treeUri] regardless of the selection
String documentId = c.getString(0);
Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,
documentId);
return documentUri;
}
} catch (Exception e) {
Log.w(TAG, "Failed query: " + e);
} finally {
if (c!=null) c.close();
}
return null;
}
But the query always return all the children of the treeUri, regardless of the selection. So, it seems the selection has no effect.
I could always loop through all the results, but if the selected folder has a large number of files it won't be good for the performance.
So my questions are:
How I can filter the result of my query?
Is this even the right approach to save a file on a sd card path?
The file system provider doesn't really support filtering:
https://github.com/aosp-mirror/platform_frameworks_base/blob/003ab94333bd6d47b35d7c241136d54b86a445b9/core/java/com/android/internal/content/FileSystemProvider.java#L370
The only choice is to get all rows and filter yourself.
You need to add permission to read/write external storage in your manifest. Add this line before your application tag.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
If your app is running on device with android 6 or higher. you need to request permission before you can write to sd card/external storage. Follow this documentation on checking permissions at run time.
Get external directory by using Environment.getExternalStorageDirectory(). This will return external directory.
Also refer to this documentation (https://developer.android.com/training/data-storage/files) and this question.
I am trying to access a file using Storage Access Framework which I have stored in locally and send it to server. but when ever I try to get file using URI I get NullPointerException. However I get the URI of file. but catches exception when converting to file by getting path.
Minimum API is 17
uriString =
content://com.android.providers.downloads.documents/document/349
warantyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(Intent. ACTION_OPEN_DOCUMENT );
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
Intent i = Intent.createChooser(intent, "File");
getActivity().startActivityForResult(i, FILE_REQ_CODE);
//Toast.makeText(getContext(),"Files",Toast.LENGTH_SHORT).show();
}
});
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_REQ_CODE) {
if (resultCode == RESULT_OK) {
String path="";
Uri uri = data.getData();
if (uri != null) {
try {
file = new File(getPath(getContext(),uri));
if(file!=null){
ext = getMimeType(uri);
sendFileToServer(file,ext);
}
} catch (Exception e) {
Toast.makeText(getContext(),getString(R.string.general_error_retry),Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
}
}
}
public static String getPath(Context context, Uri uri) throws URISyntaxException {
if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = { "_data" };
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
int column_index = cursor.getColumnIndexOrThrow("_data");
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
} catch (Exception e) {
// Eat it
}
}
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
I am trying to access a file using Storage Access Framework which I have stored in locally and send it to server.
Your users are welcome to choose anything they want, which does not include files that you can access directly (e.g., in Google Drive, on removable storage).
but catches exception when converting to file by getting path
You cannot "convert to file by getting path". The path portion of a content Uri is a meaningless set of characters that identifies the particular piece of content. Next, you will think that all computers have a file on their local filesystem at the path /questions/43818723/unable-to-access-file-from-uri, just because https://stackoverflow.com/questions/43818723/unable-to-access-file-from-uri happens to be a valid Uri.
So, get rid of getPath().
Use ContentResolver and openInputStream() to get an InputStream on the content. Either use that stream directly or use it in conjunction with a FileOutputStream on your own file, to make a local copy of the content that you can use as a file.
#CommonsWare answer is correct
here is code snippet
To read file content from Uri :
// Use ContentResolver to access file from Uri
InputStream inputStream = null;
try {
inputStream = mainActivity.getContentResolver().openInputStream(uri);
assert inputStream != null;
// read file content
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String mLine;
StringBuilder stringBuilder = new StringBuilder();
while ((mLine = bufferedReader.readLine()) != null) {
stringBuilder.append(mLine);
}
Log.d(TAG, "reading file :" + stringBuilder);
To save file from Uri to local copy inside your app dir :
String dirPath = "/data/user/0/-your package name -/newLocalFile.name"
try (InputStream ins = mainActivity.getContentResolver().openInputStream(uri)) {
File dest = new File(dirPath);
try (OutputStream os = new FileOutputStream(dest)) {
byte[] buffer = new byte[4096];
int length;
while ((length = ins.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.flush();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
Try Below Code For Getting Path:
public String getPath(Uri uri) throws URISyntaxException {
final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19;
String selection = null;
String[] selectionArgs = null;
// Uri is different in versions after KITKAT (Android 4.4), we need to
// deal with different Uris.
if (needToCheckUri && DocumentsContract.isDocumentUri(mainActivity, uri)) {
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
uri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
} else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("image".equals(type)) {
uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
selection = "_id=?";
selectionArgs = new String[] {
split[1]
};
}
}
if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = {
MediaStore.Images.Media.DATA
};
Cursor cursor = null;
try {
cursor = mainActivity.getContentResolver()
.query(uri, projection, selection, selectionArgs, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
} catch (Exception e) {
}
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}`/**
* #param uri The Uri to check.
* #return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* #param uri The Uri to check.
* #return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}`
I'm trying to implement a File Picker in my Android project. What I've been able to do so far is :
Intent chooseFile;
Intent intent;
chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
chooseFile.setType("*/*");
intent = Intent.createChooser(chooseFile, "Choose a file");
startActivityForResult(intent, PICKFILE_RESULT_CODE);
And then in my onActivityResult()
switch(requestCode){
case PICKFILE_RESULT_CODE:
if(resultCode==-1){
Uri uri = data.getData();
String filePath = uri.getPath();
Toast.makeText(getActivity(), filePath,
Toast.LENGTH_LONG).show();
}
break;
}
This is opening a file picker, but its not what I want. For example, I want to select a file (.txt), and then get that File and then use it. With this code I thought I would get the full path but it doesn't happen; for example I get: /document/5318/. But with this path I can't get the file. I've created a method called PathToFile() that returns a File :
private File PathToFile(String path) {
File tempFileToUpload;
tempFileToUpload = new File(path);
return tempFileToUpload;
}
What I'm trying to do is let the user choose a File from anywhere means DropBox, Drive, SDCard, Mega, etc... And I don't find the way to do it correctly, I tried to get the Path then get a File by this Path... but it doesn't work, so I think it's better to get the File itself, and then with this File programmatically I Copy this or Delete.
EDIT (Current code)
My Intent
Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
chooseFile.addCategory(Intent.CATEGORY_OPENABLE);
chooseFile.setType("text/plain");
startActivityForResult(
Intent.createChooser(chooseFile, "Choose a file"),
PICKFILE_RESULT_CODE
);
There I've got a question because I don't know what is supported as text/plain, but I'm gonna investigate about it, but it doesn't matter at the moment.
On my onActivityResult() I've used the same as #Lukas Knuth answer, but I don't know if with it I can Copy this File to another part from my SDcard I'm waitting for his answer.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICKFILE_RESULT_CODE && resultCode == Activity.RESULT_OK){
Uri content_describer = data.getData();
//get the path
Log.d("Path???", content_describer.getPath());
BufferedReader reader = null;
try {
// open the user-picked file for reading:
InputStream in = getActivity().getContentResolver().openInputStream(content_describer);
// now read the content:
reader = new BufferedReader(new InputStreamReader(in));
String line;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null){
builder.append(line);
}
// Do something with the content in
text.setText(builder.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
getPath() from #Y.S.
I'm doing this :
String[] projection = { MediaStore.Files.FileColumns.DATA };
Cursor cursor = getActivity().getContentResolver().query(content_describer, projection, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(projection[0]);
cursor.moveToFirst();
cursor.close();
Log.d( "PATH-->",cursor.getString(column_index));
Is getting a NullPointerException :
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=131073, result=-1, data=Intent { dat=file:///path typ=text/plain flg=0x3 }} to activity {info.androidhive.tabsswipe/info.androidhive.tabsswipe.MainActivity2}: java.lang.NullPointerException
EDIT with code working thanks to #Y.S., #Lukas Knuth, and #CommonsWare.
This is the Intent where I only accept files text/plain.
Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
chooseFile.addCategory(Intent.CATEGORY_OPENABLE);
chooseFile.setType("text/plain");
startActivityForResult(
Intent.createChooser(chooseFile, "Choose a file"),
PICKFILE_RESULT_CODE
);
On my onActivityResult() I create an URI where I get the data of the Intent, I create a File where I save the absolute path doing content_describer.getPath();, and then I keep the name of the path to use it in a TextView with content_describer.getLastPathSegment(); (that was awesome #Y.S. didn't know about that function), and I create a second File which I called destination and I send the AbsolutePath to can create this File.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICKFILE_RESULT_CODE && resultCode == Activity.RESULT_OK){
Uri content_describer = data.getData();
String src = content_describer.getPath();
source = new File(src);
Log.d("src is ", source.toString());
String filename = content_describer.getLastPathSegment();
text.setText(filename);
Log.d("FileName is ",filename);
destination = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Test/TestTest/" + filename);
Log.d("Destination is ", destination.toString());
SetToFolder.setEnabled(true);
}
}
Also I've created a function that you have to send the source file, and destination file that we have created previously to copy this to the new folder.
private void copy(File source, File destination) throws IOException {
FileChannel in = new FileInputStream(source).getChannel();
FileChannel out = new FileOutputStream(destination).getChannel();
try {
in.transferTo(0, in.size(), out);
} catch(Exception e){
Log.d("Exception", e.toString());
} finally {
if (in != null)
in.close();
if (out != null)
out.close();
}
}
Also I've created a function that says to me if this folder exist or not (I have to send the destination file, if it doesn't exist I create this folder and if it does not I do not do nothing.
private void DirectoryExist (File destination) {
if(!destination.isDirectory()) {
if(destination.mkdirs()){
Log.d("Carpeta creada","....");
}else{
Log.d("Carpeta no creada","....");
}
}
Thanks again for your help, hope you enjoy this code made with everyone of you guys :)
STEP 1 - Use an Implicit Intent:
To choose a file from the device, you should use an implicit Intent
Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
chooseFile.setType("*/*");
chooseFile = Intent.createChooser(chooseFile, "Choose a file");
startActivityForResult(chooseFile, PICKFILE_RESULT_CODE);
STEP 2 - Get the absolute file path:
To get the file path from a Uri, first, try using
Uri uri = data.getData();
String src = uri.getPath();
where data is the Intent returned in onActivityResult().
If that doesn't work, use the following method:
public String getPath(Uri uri) {
String path = null;
String[] projection = { MediaStore.Files.FileColumns.DATA };
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
if(cursor == null){
path = uri.getPath()
}
else{
cursor.moveToFirst();
int column_index = cursor.getColumnIndexOrThrow(projection[0]);
path = cursor.getString(column_index);
cursor.close();
}
return ((path == null || path.isEmpty()) ? (uri.getPath()) : path);
}
At least one of these two methods should get you the correct, full path.
STEP 3 - Copy the file:
What you want, I believe, is to copy a file from one location to another.
To do this, it is necessary to have the absolute file path of both the source and destination locations.
First, get the absolute file path using either my getPath() method or uri.getPath():
String src = getPath(uri); /* Method defined above. */
or
Uri uri = data.getData();
String src = uri.getPath();
Then, create two File objects as follows:
File source = new File(src);
String filename = uri.getLastPathSegment();
File destination = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/CustomFolder/" + filename);
where CustomFolder is the directory on your external drive where you want to copy the file.
Then use the following method to copy a file from one place to another:
private void copy(File source, File destination) {
FileChannel in = new FileInputStream(source).getChannel();
FileChannel out = new FileOutputStream(destination).getChannel();
try {
in.transferTo(0, in.size(), out);
} catch(Exception){
// post to log
} finally {
if (in != null)
in.close();
if (out != null)
out.close();
}
}
Try this. This should work.
Note: Vis-a-vis Lukas' answer - what he has done is use a method called openInputStream() that returns the content of a Uri, whether that Uri represents a file or a URL.
Another promising approach - the FileProvider:
There is one more way through which it is possible to get a file from another app. If an app shares its files through the FileProvider, then it is possible to get hold of a FileDescriptor object which holds specific information about this file.
To do this, use the following Intent:
Intent mRequestFileIntent = new Intent(Intent.ACTION_GET_CONTENT);
mRequestFileIntent.setType("*/*");
startActivityForResult(mRequestFileIntent, 0);
and in your onActivityResult():
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent returnIntent) {
// If the selection didn't work
if (resultCode != RESULT_OK) {
// Exit without doing anything else
return;
} else {
// Get the file's content URI from the incoming Intent
Uri returnUri = returnIntent.getData();
/*
* Try to open the file for "read" access using the
* returned URI. If the file isn't found, write to the
* error log and return.
*/
try {
/*
* Get the content resolver instance for this context, and use it
* to get a ParcelFileDescriptor for the file.
*/
mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.e("MainActivity", "File not found.");
return;
}
// Get a regular file descriptor for the file
FileDescriptor fd = mInputPFD.getFileDescriptor();
...
}
}
where mInputPFD is a ParcelFileDescriptor.
References:
1. Common Intents - File Storage.
2. FileChannel.
3. FileProvider.
4. Requesting a Shared File.
As #CommonsWare already noted, Android returns you a Uri, which is a more abstract concept than a file-path.
It can describe a simple file-path too, but it can also describe a resource that is accessed through an application (like content://media/external/audio/media/710).
If you want your user to pick any file from the phone to read it from your application, you can do so by asking for the file (as you did correctly) and then use the ContentResolver to get an InputStream for the Uri that is returned by the picker.
Here is an example:
Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
// Ask specifically for something that can be opened:
chooseFile.addCategory(Intent.CATEGORY_OPENABLE);
chooseFile.setType("*/*");
startActivityForResult(
Intent.createChooser(chooseFile, "Choose a file"),
PICKFILE_REQUEST_CODE
);
// And then somewhere, in your activity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICKFILE_REQUEST_CODE && resultCode == RESULT_OK){
Uri content_describer = data.getData();
BufferedReader reader = null;
try {
// open the user-picked file for reading:
InputStream in = getContentResolver().openInputStream(content_describer);
// now read the content:
reader = new BufferedReader(new InputStreamReader(in));
String line;
StringBuilder builder = new StringBuilder();
while ((line = reader.readLine()) != null){
builder.append(line);
}
// Do something with the content in
some_view.setText(builder.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Important: Some providers (like Dropbox) store/cache their data on the external storage. You'll need to have the android.permission.READ_EXTERNAL_STORAGE-permission declared in your manifest, otherwise you'll get FileNotFoundException, even though the file is there.
Update: Yes, you can copy the file by reading it from one stream and writing it to another:
// Error handling is omitted for shorter code!
Uri content_describer = data.getData();
InputStream in = null;
OutputStream out = null;
try {
// open the user-picked file for reading:
in = getContentResolver().openInputStream(content_describer);
// open the output-file:
out = new FileOutputStream(new File("some/path/to/a/writable/file"));
// copy the content:
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
// Contents are copied!
} finally {
if (in != null) {
in.close();
}
if (out != null){
out.close();
}
}
Deleting the file is probably not possible, since the file doesn't belong to you, it belongs to the application that shared it with yours. Therefor, the owning application is responsible for deleting the file.
With ActivityResultLauncher it works alike this:
ActivityResultLauncher<Intent> startActivityForResult = requireActivity().registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
Uri contentUri = data.getData();
...
}
});
Usage example:
Intent data = new Intent(Intent.ACTION_GET_CONTENT);
data.addCategory(Intent.CATEGORY_OPENABLE);
data.setType("*/*");
Intent intent = Intent.createChooser(data, "Choose a file");
startActivityForResult.launch(intent);
The following dependency is required (with or without -ktx):
implementation "androidx.activity:activity:1.5.0"
I did the same to let the user choose an image from a folder :
1) there is a button OPEN:
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_open:
myOpenImagePicker();
break;
}
}
2) the open image folder function:
#SuppressLint("InlinedApi")
public void myOpenImagePicker() {
if (Build.VERSION.SDK_INT < 19) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
Intent.createChooser(intent, "Select Picture"),
SELECT_FOLDER);
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, SELECT_FOLDER);
}
}
3) the activity result where i get the image file path and do whatever i want with the image path:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case SELECT_FOLDER:
if (resultCode == RESULT_OK && data != null) {
Uri originalUri = data.getData();
String id01 = W_ImgFilePathUtil.getPath(
getApplicationContext(), originalUri);
Bitmap unscaledBitmap = W_ImgScalingUtil.decodeResource(id01,
xdrawing.getViewWidth(), xdrawing.getViewHeight(),
ScalingLogic.FIT);
if (unscaledBitmap == null) {
zprefsutil.ShowToast("IMAGE ERROR", 1);
} else {
setExternalScaledBitmap(W_ImgScalingUtil
.createScaledBitmap(unscaledBitmap,
xdrawing.getViewWidth(),
xdrawing.getViewHeight(), ScalingLogic.FIT));
unscaledBitmap.recycle();
xdrawing.invalidate();
}
}
break;
default:
break;
}
}
4) and now the MOST IMPORTANT PART, the W_ImgFilePathUtil class, the code is not from me but it allows you to retrieve the full path of any selected file be it on sd card, google drive, ...:
public class W_ImgFilePathUtil {
/**
* Method for return file path of Gallery image
*
* #param context
* #param uri
* #return path of the selected image file from gallery
*/
#SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
// check here to KITKAT or new version
final boolean isKitKatorUp = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKatorUp && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/"
+ split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection,
selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* #param context
* The context.
* #param uri
* The Uri to query.
* #param selection
* (Optional) Filter used in the query.
* #param selectionArgs
* (Optional) Selection arguments used in the query.
* #return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri,
String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection,
selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* #param uri
* The Uri to check.
* #return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri
.getAuthority());
}
/**
* #param uri
* The Uri to check.
* #return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri
.getAuthority());
}
/**
* #param uri
* The Uri to check.
* #return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri
.getAuthority());
}
/**
* #param uri
* The Uri to check.
* #return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri
.getAuthority());
}
}
CONCLUSION : the code works with image path but sure works with any kind of file.
Hope this helps solve your problem.
PEACE.
A Uri is not a file. A Uri is closer to a Web server URL. It is an opaque address, which only has meaning to the "server" (or in this case, the ContentProvider).
Just as you use an InputStream to read in the bytes represented by a Web URL, you use an InputStream to read in the bytes represented by the Uri. You get such a stream by calling openInputStream() on a ContentResolver.
Here is how to implement a file picker and move the selected file into another location (e.g picture).
Firstly, add a file picker with a button on click listener to your code like this:
A button to pick file:
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_choose_file:
showFileChooser();
break;
}
}
private String filePath = null;
private File sourceFile;
private static final int FILE_SELECT_CODE = 0;
private void showFileChooser() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(
Intent.createChooser(intent, "Select a File to Copy"),
FILE_SELECT_CODE);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(this, "Please install a File Manager.",
Toast.LENGTH_SHORT).show();
}
}
Then handle onActivityResult like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case FILE_SELECT_CODE:
if (resultCode == RESULT_OK) {
// Get the Uri of the selected file
Uri uri = data.getData();
File file = new File(getCacheDir(), getFileName(uri));
int maxBufferSize = 1 * 1024 * 1024;
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
Log.e("InputStream Size","Size " + inputStream);
int bytesAvailable = inputStream.available();
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
final byte[] buffers = new byte[bufferSize];
FileOutputStream outputStream = new FileOutputStream(file);
int read = 0;
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
Log.e("File Size","Size " + file.length());
inputStream.close();
outputStream.close();
file.getPath();
Log.e("File Path","Path " + file.getPath());
file.length();
Log.e("File Size","Size " + file.length());
if(file.length() > 0){
sourceFile = file;
saveFile(sourceFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
} else {
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
private void saveFile (File sourceFile) {
try {
File currentFile = sourceFile;
Bitmap myBitmap = BitmapFactory.decodeFile(currentFile.getAbsolutePath());
String path = currentFile.getAbsolutePath();
String idStr = path.substring(path.lastIndexOf('/') + 1);
File filepath = Environment.getExternalStorageDirectory();
File dir = new File(filepath.getAbsolutePath() + "/" + "yourFolderName" + "/");
if (!dir.exists()) {
dir.mkdirs();
}
String fileName = currentFile.getName();
file = new File(dir, fileName);
if (file.exists()) file.delete();
FileOutputStream fos = new FileOutputStream(file);
myBitmap.compress(CompressFormat.JPEG, 65, fos);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Note: Don't forget to add this permission to the manifest file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Hope this helps.
Pass the URI returned in onActivityResult in this method
private String getPath(Uri contentURI) {
String result;
Cursor cursor = getActivity().getContentResolver().query(contentURI,
null, null, null, null);
if (cursor == null) {
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor
.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
Here's how you do it in newer Android versions:
private ActivityResultLauncher<String> filePicker;
//execute this in your AppCompatActivity onCreate
public void registerFilePicker() {
filePicker = registerForActivityResult(
new ActivityResultContracts.GetContent(),
uri -> onPickFile(uri)
);
}
//execute this to launch the picker
public void openFilePicker() {
String mimeType = "*/*";
filePicker.launch(mimeType);
}
//this gets executed when the user picks a file
public void onPickFile(Uri uri) {
try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
} catch (IOException exception) {
}
}
More info: https://developer.android.com/training/basics/intents/result
I am trying to share my internal storage file via Gmail client on my Moto Razr, but every time I sent to my test gmail account, I got everything except attachment.
This is how I invoke and start gmail, while add file as attachment.
private void saveDaily() {
Intent intent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { loadEmailAddress() });
intent.putExtra(Intent.EXTRA_SUBJECT, "Daily");
intent.putExtra(Intent.EXTRA_TEXT, "Daily Log");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ArrayList<Uri> uris = new ArrayList<Uri>();
uris.add(saveDaily2File("dailyRecord.txt"));
Log.d(TAG_D, "Size: " + uris.size());
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
startActivity(Intent.createChooser(intent, "Send email"));
}
This is how I implement my customized content provider.
public class SavedFileProvider extends ContentProvider {
private static final String TAG_D = "ContentProvider";
private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();
static {
MIME_TYPES.put(".txt", "text/plain");
}
#Override
public String getType(Uri uri) {
String path = uri.toString();
for (String extension : MIME_TYPES.keySet()) {
if (path.endsWith(extension)) {
return (MIME_TYPES.get(extension));
}
}
return (null);
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
Log.d(TAG_D, "openFile()");
File f = new File(getContext().getFilesDir(), uri.getPath());
Log.d(TAG_D, f.getAbsolutePath());
if (f.exists()) {
return (ParcelFileDescriptor.open(f,
ParcelFileDescriptor.MODE_READ_ONLY));
}
throw new FileNotFoundException(uri.getPath());
}
#Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
throw new RuntimeException("Operation not supported");
}
#Override
public Uri insert(Uri uri, ContentValues initialValues) {
throw new RuntimeException("Operation not supported");
}
#Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
private void copy(InputStream in, File dst) throws IOException {
FileOutputStream out = new FileOutputStream(dst);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
#Override
public boolean onCreate() {
Log.d(TAG_D, "onCreate()");
File f = new File(getContext().getFilesDir(), "dailyRecord.txt");
if (!f.exists()) {
AssetManager assets = getContext().getResources().getAssets();
try {
copy(assets.open("dailyRecord.txt"), f);
} catch (IOException e) {
Log.e("FileProvider", "Exception copying from assets", e);
return (false);
}
}
return (true);
}
}
Then, I add the following lines in my AndroidManifest.xml File.
<provider
android:name=".SavedFileProvider"
android:authorities="Package Path here"
android:exported="true"
android:grantUriPermissions="true"
android:multiprocess="true" >
</provider>
I wonder what I am missing here.
I have check the link:Link1, Link2
I use this:
AndroidManifest.xml
<provider android:name="com.myapp.main.MyContentProvider" android:authorities="com.myapp.main"></provider>
Button click:
public void onClick(View v) {
List<Intent> targetedShareIntents = new ArrayList<Intent>();
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/jpg");
Uri theUri = Uri.parse("content://com.myapp.main/"+srcImage);
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(shareIntent, 0);
int i=0;
List<ResolveInfo> reInfoToDelete = new ArrayList<ResolveInfo>();
if (!resInfo.isEmpty()){
for (ResolveInfo resolveInfo : resInfo) {
String packageName = resolveInfo.activityInfo.packageName;
Intent targetedShareIntent = new Intent(android.content.Intent.ACTION_SEND);
targetedShareIntent.setType("image/jpg");
targetedShareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Share file");
if (packageName.equals("com.google.android.gm")){
targetedShareIntent.setType("image/png");
targetedShareIntent.putExtra(Intent.EXTRA_TEXT, "some text");
targetedShareIntent.putExtra(Intent.EXTRA_STREAM, theUri);
targetedShareIntent.setPackage(packageName);
targetedShareIntents.add(targetedShareIntent);
}
}
startActivity(targetedShareIntents.remove(0));
}
}
My Content Provider Class
public class MyContentProvider extends ContentProvider implements PipeDataWriter<InputStream>{
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
//Adapt this to your code
AssetManager am = getContext().getAssets();
String file_name = "path/"+uri.getLastPathSegment();
if(file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
e.printStackTrace();
}
return afd;//super.openAssetFile(uri, mode);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
#Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
public void writeDataToPipe(ParcelFileDescriptor arg0, Uri arg1,
String arg2, Bundle arg3, InputStream arg4) {
// Transfer data from the asset to the pipe the client is reading.
byte[] buffer = new byte[8192];
int n;
FileOutputStream fout = new FileOutputStream(arg0.getFileDescriptor());
try {
while ((n=arg4.read(buffer)) >= 0) {
fout.write(buffer, 0, n);
}
} catch (IOException e) {
Log.i("InstallApk", "Failed transferring", e);
} finally {
try {
arg4.close();
} catch (IOException e) {
}
try {
fout.close();
} catch (IOException e) {
}
}
}
}
I'm trying to share an image from my assets folder. My code is:
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpg");
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///assets/myImage.jpg"));
startActivity(Intent.createChooser(share, "Share This Image"));
but it doesn't work. Do you have any ideas?
It is possible to share files (images including) from the assets folder through a custom ContentProvider
You need to extend ContentProvider, register it in your manifest and implement the openAssetFile method. You can then assess the assets via Uris
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
AssetManager am = getContext().getAssets();
String file_name = uri.getLastPathSegment();
if(file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
e.printStackTrace();
}
return afd;
}
Complementing what #intrepidis answered:
You will need override methods like example class above:
package com.android.example;
import android.content.ContentProvider;
import android.net.Uri;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import java.io.FileNotFoundException;
import android.content.ContentValues;
import android.database.Cursor;
import java.io.IOException;
import android.os.CancellationSignal;
public class AssetsProvider extends ContentProvider
{
#Override
public AssetFileDescriptor openAssetFile( Uri uri, String mode ) throws FileNotFoundException
{
Log.v( TAG, "AssetsGetter: Open asset file" );
AssetManager am = getContext( ).getAssets( );
String file_name = uri.getLastPathSegment( );
if( file_name == null )
throw new FileNotFoundException( );
AssetFileDescriptor afd = null;
try
{
afd = am.openFd( file_name );
}
catch(IOException e)
{
e.printStackTrace( );
}
return afd;//super.openAssetFile(uri, mode);
}
#Override
public String getType( Uri p1 )
{
// TODO: Implement this method
return null;
}
#Override
public int delete( Uri p1, String p2, String[] p3 )
{
// TODO: Implement this method
return 0;
}
#Override
public Cursor query( Uri p1, String[] p2, String p3, String[] p4, String p5 )
{
// TODO: Implement this method
return null;
}
#Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal )
{
// TODO: Implement this method
return super.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal );
}
#Override
public Uri insert( Uri p1, ContentValues p2 )
{
// TODO: Implement this method
return null;
}
#Override
public boolean onCreate( )
{
// TODO: Implement this method
return false;
}
#Override
public int update( Uri p1, ContentValues p2, String p3, String[] p4 )
{
// TODO: Implement this method
return 0;
}
}
I needed to override two times the query method.
And add these lines above tag in your androidmanifest.xml:
<provider
android:name="com.android.example.AssetsProvider"
android:authorities="com.android.example"
android:grantUriPermissions="true"
android:exported="true" />
And with this, all work like a charm :D
This blog explains it all:
http://nowherenearithaca.blogspot.co.uk/2012/03/too-easy-using-contentprovider-to-send.html
Basically, this goes in the manifest:
<provider android:name="yourclass.that.extendsContentProvider" android:authorities="com.yourdomain.whatever"/>
The content provider class has this:
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
AssetManager am = getContext().getAssets();
String file_name = uri.getLastPathSegment();
if(file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
e.printStackTrace();
}
return afd;//super.openAssetFile(uri, mode);
}
And the calling code does this:
Uri theUri = Uri.parse("content://com.yourdomain.whatever/someFileInAssetsFolder");
Intent theIntent = new Intent(Intent.ACTION_SEND);
theIntent.setType("image/*");
theIntent.putExtra(Intent.EXTRA_STREAM,theUri);
theIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,"Subject for message");
theIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Body for message");
startActivity(theIntent);
Many apps require you to provide name and size of the image. So here is an improved code (using Google's FileProvider code as an example):
public class AssetsProvider extends ContentProvider {
private final static String LOG_TAG = AssetsProvider.class.getName();
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
/**
* Source: {#link FileProvider#query(Uri, String[], String, String[], String)} .
*/
if (projection == null) {
projection = COLUMNS;
}
final AssetManager am = getContext().getAssets();
final String path = getRelativePath(uri);
long fileSize = 0;
try {
final AssetFileDescriptor afd = am.openFd(path);
fileSize = afd.getLength();
afd.close();
} catch(IOException e) {
Log.e(LOG_TAG, "Can't open asset file", e);
}
final String[] cols = new String[projection.length];
final Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = uri.getLastPathSegment();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = fileSize;
}
}
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
#Override
public String getType(Uri uri) {
/**
* Source: {#link FileProvider#getType(Uri)} .
*/
final String file_name = uri.getLastPathSegment();
final int lastDot = file_name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file_name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
final AssetManager am = getContext().getAssets();
final String path = getRelativePath(uri);
if(path == null) {
throw new FileNotFoundException();
}
AssetFileDescriptor afd = null;
try {
afd = am.openFd(path);
} catch(IOException e) {
Log.e(LOG_TAG, "Can't open asset file", e);
}
return afd;
}
private String getRelativePath(Uri uri) {
String path = uri.getPath();
if (path.charAt(0) == '/') {
path = path.substring(1);
}
return path;
}
}
Since none of the other answers here worked for me (in 2019) I made a workaround by copying the asset to the app's internal file directory and then sharing this file.
In my case, I needed to share a pdf file from the assets folder.
In the AndroidManifest.xml add a file provider (no need to use a custom one):
<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/filepaths" />
</provider>
Create a filepaths.xml file in res/xml/
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="root"
path="/" />
</paths>
Of course you should use a subdirectory here if you manage other files in your app directory.
Now in the class where you want to trigger the share intent.
1. Create an empty file in the files directory
private fun createFileInFilesDir(filename: String): File {
val file = File(filesDir.path + "/" + filename)
if (file.exists()) {
if (!file.delete()) {
throw IOException()
}
}
if (!file.createNewFile()) {
throw IOException()
}
return file
}
2. Copy the content of the asset to the file
private fun copyAssetToFile(assetName: String, file: File) {
val buffer = ByteArray(1024)
val inputStream = assets.open(assetName)
val outputStream: OutputStream = FileOutputStream(file)
while (inputStream.read(buffer) > 0) {
outputStream.write(buffer)
}
}
3. Create a share intent for the file
private fun createIntentForFile(file: File, intentAction: String): Intent {
val uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
val intent = Intent(intentAction)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.setDataAndType(uri, "application/pdf")
return intent
}
4. Execute 1-3 and fire the intent
private fun sharePdfAsset(assetName: String, intentAction: String) {
try {
val file = createFileInFilesDir(assetName)
copyAssetToFile(assetName, file)
val intent = createIntentForFile(file, intentAction)
startActivity(Intent.createChooser(intent, null))
} catch (e: IOException) {
e.printStackTrace()
AlertDialog.Builder(this)
.setTitle(R.string.error)
.setMessage(R.string.share_error)
.show()
}
}
5. Call the function
sharePdfAsset("your_pdf_asset.pdf", Intent.ACTION_SEND)
If you want to delete the file after sharing it, you probably could use startActivityForResult() and delete it afterwards. By changing the intentAction you can also use this process for an "open with..." action by using Intent.ACTION_VIEW.
For assets, filesDir, ... you need to be in an Activity or have a Context of course.
AFAIK, there's no way to share an image from the assets folder. But it's possible to share resources from the res folder.
To share from assets folder I can only recommend the cwac-provider library (StreamProvider).
Among avoiding to develop your own content provider, it adds some support for capricious legacy apps (check USE_LEGACY_CURSOR_WRAPPER).