Opening shared image via Gmail Intent - android

I have the following code that correctly attaches the image to the email and sends:
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Set tht type to image/* and add the extra text field for the message to send
sharingIntent.setType(Application2.instance().getResString(R.string.share_intent_type_text_image));
sharingIntent.putExtra(Intent.EXTRA_TEXT, String.format(Application2.instance().getResString(R.string.share_intent_body_question), question.question));
if (destFile != null)
{
Uri uri = Uri.fromFile(destFile);
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
}
R.string.share_intent_type_text_image is defined as "image/png"
destFile is an image grabbed from the external cache directory of the app, (((ActivityMain) getActivity()).getExternalCacheDir()
However, when I attempt to open the file in Gmail, a dialog appears that says: Info - No app can open this attachment for viewing. I've downloaded the file via my PC and the extension comes up as .File. I can open it with paint and other image viewers.
Anyone experience this before?

Considering the FileProvider problems, and also because I wanted to implement a max cache size for collected temp files, I went with a ContentProvider solution and it works a treat. Basically, you're allowed to use your internal cache without any problem but still provide third party apps with a URI they can use to reference your temporary files you want to share with them. Because you use your internal cache, there will be no unnecessary WRITE_EXTERNAL_STORAGE permission to ask for.
The added max cache size limit (that you can remove from the class by simply deleting everything from checkSize() to the end of the class, for instance, if you can make sure you delete all files directly after sharing, so they won't remain on the device) works by checking the cumulated max size upon each call and clearing up half the cache (deleting the oldest files) if necessary.
public class TemporaryFile extends ContentProvider {
private static final long MAX_SIZE = 512 * 1024;
// commented out on purpose so that you don't forget to rewrite it...
// public static final String AUTHORITY = "com.example.tempfile";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (uriMatcher.match(uri) == 1) {
final String file = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
return ParcelFileDescriptor.open(new File(file), ParcelFileDescriptor.MODE_READ_ONLY);
}
else
throw new FileNotFoundException(uri.toString());
}
#Override
public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int delete (Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
public static File getFile(Context context, String prefix, String extension) throws IOException {
checkSize(context);
File file = File.createTempFile(prefix, extension, context.getCacheDir());
file.setReadable(true);
file.deleteOnExit();
return file;
}
public static Uri getPublicUri(File file) {
return Uri.withAppendedPath(Uri.parse("content://" + AUTHORITY), file.getName());
}
public static void checkSize(Context context) throws IOException {
File dir = context.getCacheDir();
if (getDirSize(dir) > MAX_SIZE)
cleanDir(dir, MAX_SIZE / 2);
}
private static long getDirSize(File dir) {
long size = 0;
for (File file : dir.listFiles())
if (file.isFile())
size += file.length();
return size;
}
private static void cleanDir(File dir, long atLeast) {
long deleted = 0;
File[] files = dir.listFiles();
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
}
});
for (File file : files) {
deleted += file.length();
file.delete();
if (deleted >= atLeast)
break;
}
}
}
Using it couldn't be simpler, just call
File file = TemporaryFile.getFile(this, "prefix", ".extension");
whenever you want to create a new file and
TemporaryFile.getPublicUri(file)
whenever you want to get a public Uri to the file, eg. to pass it to an intent as data or Intent.EXTRA_STREAM.
Being a provider, don't forget to add the necessary manifest entry, either:
<provider
android:name=".TemporaryFile"
android:authorities="com.example.tempfile"
android:exported="true"
tools:ignore="ExportedContentProvider" >
</provider>

This works but requires external storage and the relating permissions. When downloading an app, a dialog will show that the app is requesting to be able to read/write data which may turn users away. Use the FileProvider as Simon suggested in my initial post if that's a concern.
Useful links:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
I attempted to use the File Provider as Simon suggested in my initial post to no avail. I received a NullPointerException on the following line:
final ProviderInfo info = context.getPackageManager()
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
I was unable to track the problem after following the guide at:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
as well as the other thread at:
How to use support FileProvider for sharing content to other apps?
At this point I realized there is no file type set for the images being used. I simply added .png to the files and the attachments work correctly in Gmail as well as the previous apps that already worked.
I provided the following code if anyone was curious how I shared an internal file. It's not complete and does not handle errors completely but it may be useful for someone as a start.
// Copy image file to external memory and send with the intent
File srcFile = getImage();
File destDir = new File(((ActivityMain) getActivity()).getExternalCacheDir(),
Application2.instance().getResString(R.string.temporary_external_image_path));
if(!destDir.exists())
{
destDir.mkdirs();
}
if(destDir != null && srcFile != null)
{
File destFile = new File(destDir, srcFile.getName());
if (!destFile.exists())
{
try
{
Application2.instance().copy(srcFile, destFile);
}
catch (IOException e)
{
if (BuildConfig.DEBUG) Log.e("Failed to copy file '" + srcFile.getName() + "'");
}
}
if (destFile != null)
{
Uri uri = Uri.fromFile(destFile);
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
}
}

Related

Get File from Google drive using Intent

I am trying to upload document from my app.
Everything working fine but when i choose file from drive.
data=Intent { act=android.intent.action.VIEW dat=content://com.google.android.apps.docs.storage.legacy/enc=ckpgt5KcEEF_JYniJQafRV_5pEnu_D5UAI1WF-Lu6h2Z_Vw4
(has extras) }}
Can any body know how to handle this file.
I had already handle all files and images only facing problem with google drive files.
I am getting this content://com.google.android.apps.docs.storage.legacy/enc=ckpgt5KcEEF_JYniJQafRV_5pEnu_D5UAI1WF-Lu6h2Z_Vw4 in intent data Uri.
Handle Uri received by Google-Drive files when selected through file chooser.
as stated earlier it receives Virtual File Uri.
I found this sample code simple and easy to understand.
the given code sample worked for me .hope it works in your case.
1.So detect this Uri is received by google drive.
public static File getFileFromUri(final Context context, final Uri uri) throws Exception {
if (isGoogleDrive(uri)) // check if file selected from google drive
{
return saveFileIntoExternalStorageByUri(context, uri);
}else
// do your other calculation for the other files and return that file
return null;
}
public static boolean isGoogleDrive(Uri uri)
{
return "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());
}
2.if yes,the uri is stored to external path(here its root directory u can change it according to your need) and the file with that uri is created.
public static File saveFileIntoExternalStorageByUri(Context context, Uri uri) throws
Exception {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
int originalSize = inputStream.available();
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
String fileName = getFileName(context, uri);
File file = makeEmptyFileIntoExternalStorageWithTitle(fileName);
bis = new BufferedInputStream(inputStream);
bos = new BufferedOutputStream(new FileOutputStream(
file, false));
byte[] buf = new byte[originalSize];
bis.read(buf);
do {
bos.write(buf);
} while (bis.read(buf) != -1);
bos.flush();
bos.close();
bis.close();
return file;
}
public static String getFileName(Context context, Uri uri)
{
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
public static File makeEmptyFileIntoExternalStorageWithTitle(String title) {
String root = Environment.getExternalStorageDirectory().getAbsolutePath();
return new File(root, title);
}
Note:Here the virtual file is retrieved from Intent getData() and used in context.getContentResolver().openInputStream(intent.getData()), this will return an InputStream. It's handle to get selected file from google drive.
for more info go through this link
I think you are getting Virtual File Uri from google drive
Read more about Virtual Files
FROM DOCS
Virtual Files
Android 7.0 adds the concept of virtual files to the Storage Access Framework. The virtual files feature allows your DocumentsProvider to return document URIs that can be used with an ACTION_VIEW intent even if they don't have a direct bytecode representation. Android 7.0 also allows you to provide alternate formats for user files, virtual or otherwise
Now question is how to check the the Uri is VirtualFile or not
You can find sample code from DOCS Open virtual files
first check that Uri is VirtualFile or not
private boolean isVirtualFile(Uri uri) {
if (!DocumentsContract.isDocumentUri(this, uri)) {
return false;
}
Cursor cursor = getContentResolver().query(
uri,
new String[] { DocumentsContract.Document.COLUMN_FLAGS },
null, null, null);
int flags = 0;
if (cursor.moveToFirst()) {
flags = cursor.getInt(0);
}
cursor.close();
return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}
The following code snippet shows how to check whether a virtual file can be represented as an image, and if so, gets an input stream from the virtual file
private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
throws IOException {
ContentResolver resolver = getContentResolver();
String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);
if (openableMimeTypes == null ||
openableMimeTypes.length < 1) {
throw new FileNotFoundException();
}
return resolver
.openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
.createInputStream();
}
For more information of Virtual Files you can read below article
Virtual Files FAQ
Open files using storage access framework
An Android Storage Access Framework Example

Delete cache files for a specific application [duplicate]

I am creating a file to send as an attachment to an email. Now I want to delete the image after sending the email. Is there a way to delete the file?
I have tried myFile.delete(); but it didn't delete the file.
I'm using this code for Android, so the programming language is Java using the usual Android ways to access the SD card. I am deleting the file in the onActivityResult method, when an Intent is returned to the screen after sending an email.
File file = new File(selectedFilePath);
boolean deleted = file.delete();
where selectedFilePath is the path of the file you want to delete - for example:
/sdcard/YourCustomDirectory/ExampleFile.mp3
Also you have to give permission if you are using >1.6 SDK
uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
in AndroidManifest.xml file
Change for Android 4.4+
Apps are not allowed to write (delete, modify ...) to external storage except to their package-specific directories.
As Android documentation states:
"Apps must not be allowed to write to secondary external storage
devices, except in their package-specific directories as allowed by
synthesized permissions."
However nasty workaround exists (see code below). Tested on Samsung Galaxy S4, but this fix does't work on all devices. Also I wouldn’t count on this workaround being available in future versions of Android.
There is a great article explaining (4.4+) external storage permissions change.
You can read more about workaround here.
Workaround source code is from this site.
public class MediaFileFunctions
{
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static boolean deleteViaContentProvider(Context context, String fullname)
{
Uri uri=getFileUri(context,fullname);
if (uri==null)
{
return false;
}
try
{
ContentResolver resolver=context.getContentResolver();
// change type to image, otherwise nothing will be deleted
ContentValues contentValues = new ContentValues();
int media_type = 1;
contentValues.put("media_type", media_type);
resolver.update(uri, contentValues, null, null);
return resolver.delete(uri, null, null) > 0;
}
catch (Throwable e)
{
return false;
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static Uri getFileUri(Context context, String fullname)
{
// Note: check outside this class whether the OS version is >= 11
Uri uri = null;
Cursor cursor = null;
ContentResolver contentResolver = null;
try
{
contentResolver=context.getContentResolver();
if (contentResolver == null)
return null;
uri=MediaStore.Files.getContentUri("external");
String[] projection = new String[2];
projection[0] = "_id";
projection[1] = "_data";
String selection = "_data = ? "; // this avoids SQL injection
String[] selectionParams = new String[1];
selectionParams[0] = fullname;
String sortOrder = "_id";
cursor=contentResolver.query(uri, projection, selection, selectionParams, sortOrder);
if (cursor!=null)
{
try
{
if (cursor.getCount() > 0) // file present!
{
cursor.moveToFirst();
int dataColumn=cursor.getColumnIndex("_data");
String s = cursor.getString(dataColumn);
if (!s.equals(fullname))
return null;
int idColumn = cursor.getColumnIndex("_id");
long id = cursor.getLong(idColumn);
uri= MediaStore.Files.getContentUri("external",id);
}
else // file isn't in the media database!
{
ContentValues contentValues=new ContentValues();
contentValues.put("_data",fullname);
uri = MediaStore.Files.getContentUri("external");
uri = contentResolver.insert(uri,contentValues);
}
}
catch (Throwable e)
{
uri = null;
}
finally
{
cursor.close();
}
}
}
catch (Throwable e)
{
uri=null;
}
return uri;
}
}
Android Context has the following method:
public abstract boolean deleteFile (String name)
I believe this will do what you want with the right App premissions as listed above.
Recursively delete all children of the file ...
public static void DeleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
DeleteRecursive(child);
}
}
fileOrDirectory.delete();
}
This works for me: (Delete image from Gallery)
File file = new File(photoPath);
file.delete();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(photoPath))));
public static boolean deleteDirectory(File path) {
// TODO Auto-generated method stub
if( path.exists() ) {
File[] files = path.listFiles();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
return(path.delete());
}
This Code will Help you.. And In Android Manifest You have to get Permission to make modification..
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Try this.
File file = new File(FilePath);
FileUtils.deleteDirectory(file);
from Apache Commons
Sorry: There is a mistake in my code before because of the site validation.
String myFile = "/Name Folder/File.jpg";
String myPath = Environment.getExternalStorageDirectory()+myFile;
File f = new File(myPath);
Boolean deleted = f.delete();
I think is clear...
First you must to know your file location.
Second,,, Environment.getExternalStorageDirectory() is a method who gets your app directory.
Lastly the class File who handle your file...
I had a similar issue with an application running on 4.4. What I did was sort of a hack.
I renamed the files and ignored them in my application.
ie.
File sdcard = Environment.getExternalStorageDirectory();
File from = new File(sdcard,"/ecatAgent/"+fileV);
File to = new File(sdcard,"/ecatAgent/"+"Delete");
from.renameTo(to);
This worked for me.
String myFile = "/Name Folder/File.jpg";
String my_Path = Environment.getExternalStorageDirectory()+myFile;
File f = new File(my_Path);
Boolean deleted = f.delete();
private boolean deleteFromExternalStorage(File file) {
String fileName = "/Music/";
String myPath= Environment.getExternalStorageDirectory().getAbsolutePath() + fileName;
file = new File(myPath);
System.out.println("fullPath - " + myPath);
if (file.exists() && file.canRead()) {
System.out.println(" Test - ");
file.delete();
return false; // File exists
}
System.out.println(" Test2 - ");
return true; // File not exists
}
You can delete a file as follow:
File file = new File("your sdcard path is here which you want to delete");
file.delete();
if (file.exists()){
file.getCanonicalFile().delete();
if (file.exists()){
deleteFile(file.getName());
}
}
File filedel = new File("/storage/sdcard0/Baahubali.mp3");
boolean deleted1 = filedel.delete();
Or, Try This:
String del="/storage/sdcard0/Baahubali.mp3";
File filedel2 = new File(del);
boolean deleted1 = filedel2.delete();

How to send a large file or multiple files to other apps, and know when to delete them?

Background
I have an App-Manager app, which allows to send APK files to other apps.
Up until Android 4.4 (including), all I had to do for this task is to send the paths to the original APK files (all were under "/data/app/..." which is accessible even without root).
This is the code for sending the files (docs available here) :
intent=new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("*/*");
final ArrayList<Uri> uris=new ArrayList<>();
for(...)
uris.add(Uri.fromFile(new File(...)));
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM,uris);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_NO_HISTORY|Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET|Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
The problem
What I did worked since all apps' APK files had a unique name (which was their package name).
Ever since Lollipop (5.0), all apps' APK files are simply named "base.APK" , which make other apps unable to comprehend attaching them.
This means I have some options to send the APK files. This is what I was thinking about:
copy them all to a folder, rename them all to unique names and then send them.
compress them all to a single file and then send it. The compression level could be minimal, as APK files are already compressed anyway.
The problem is that I would have to send the files as quickly as possible, and if I really have to have those temporary files (unless there is another solution), to also dispose them as quickly as possible.
Thing is, I don't get notified when third party apps have finished handling the temporary file, and I also think that choosing multiple files would take quite some time to prepare no matter what I choose.
Another issue is that some apps (like Gmail) actually forbid sending APK files.
The question
Is there an alternative to the solutions I've thought of? Is there maybe a way to solve this problem with all the advantages I had before (quick and without junk files left behind) ?
Maybe some sort of way to monitor the file? or create a stream instead of a real file?
Will putting the temporary file inside a cache folder help in any way?
Any app registered for that Intent should be able to process files with the same file name but different paths. To be able to cope with the fact that access to files provided by other apps can only be accessed while the receiving Activity is running (see Security Exception when trying to access a Picasa image on device running 4.2 or SecurityException when downloading Images with the Universal-Image-Downloader) receiving apps need to copy the files to a directory they have permanently access to. My guess is that some apps haven't implemented that copy process to deal with identical file names (when copied the file path would likely be the same for all files).
I'd suggest to serve the files through a ContentProvider instead of directly from the file system. That way you can create a unique file name for each file you want to send.
Receiving apps "should" receive files more or less like this:
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(uri, new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, null, null, null);
// retrieve name and size columns from the cursor...
InputStream in = contentResolver.openInputStream(uri);
// copy file from the InputStream
Since apps should open the file using contentResolver.openInputStream() a ContentProvider should/will work instead of just passing a file uri in the Intent. Of course there might be apps that misbehave and this needs to be tested thoroughly but in case some apps won't handle ContentProvider served files you could add two different share options (one legacy and the regular one).
For the ContentProvider part there's this:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
Unfortunately there's also this:
A FileProvider can only generate a content URI for files in
directories that you specify beforehand
If you can define all directories you want to share files from when the app is built, the FileProvider would be your best option.
I'm assuming your app would want to share files from any directory, so you'll need your own ContentProvider implementation.
The problems to solve are:
How do you include the file path in the Uri in order to extract the very same path at a later stage (in the ContentProvider)?
How do you create a unique file name that you can return in the ContentProvider to the receiving app? This unique file name needs to be the same for multiple calls to the ContentProvider meaning you can't create a unique id whenever the ContentProvider is called or you'd get a different one with each call.
Problem 1
A ContentProvider Uri consists of a scheme (content://), an authority and the path segment(s), e.g.:
content://lb.com.myapplication2.fileprovider/123/base.apk
There are many solutions to the first problem. What I suggest is to base64 encode the file path and use it as the last segment in the Uri:
Uri uri = Uri.parse("content://lb.com.myapplication2.fileprovider/" + new String(Base64.encode(filename.getBytes(), Base64.DEFAULT));
If the file path is e.g.:
/data/data/com.google.android.gm/base.apk
then the resulting Uri would be:
content://lb.com.myapplication2.fileprovider/L2RhdGEvZGF0YS9jb20uZ29vZ2xlLmFuZHJvaWQuZ20vYmFzZS5hcGs=
To retrieve the file path in the ContentProvider simply do:
String lastSegment = uri.getLastPathSegment();
String filePath = new String(Base64.decode(lastSegment, Base64.DEFAULT) );
Problem 2
The solution is pretty simple. We include a unique identifier in the Uri generated when we create the Intent. This identifier is part of the Uri and can be extracted by the ContentProvider:
String encodedFileName = new String(Base64.encode(filename.getBytes(), Base64.DEFAULT));
String uniqueId = UUID.randomUUID().toString();
Uri uri = Uri.parse("content://lb.com.myapplication2.fileprovider/" + uniqueId + "/" + encodedFileName );
If the file path is e.g.:
/data/data/com.google.android.gm/base.apk
then the resulting Uri would be:
content://lb.com.myapplication2.fileprovider/d2788038-53da-4e84-b10a-8d4ef95e8f5f/L2RhdGEvZGF0YS9jb20uZ29vZ2xlLmFuZHJvaWQuZ20vYmFzZS5hcGs=
To retrieve the unique identifier in the ContentProvider simply do:
List<String> segments = uri.getPathSegments();
String uniqueId = segments.size() > 0 ? segments.get(0) : "";
The unique file name the ContentProvider returns would be the original file name (base.apk) plus the unique identifier inserted after the base file name. E.g. base.apk becomes base<unique id>.apk.
While this might all sound very abstract, it should become clear with the full code:
Intent
intent=new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("*/*");
final ArrayList<Uri> uris=new ArrayList<>();
for(...)
String encodedFileName = new String(Base64.encode(filename.getBytes(), Base64.DEFAULT));
String uniqueId = UUID.randomUUID().toString();
Uri uri = Uri.parse("content://lb.com.myapplication2.fileprovider/" + uniqueId + "/" + encodedFileName );
uris.add(uri);
}
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM,uris);
ContentProvider
public class FileProvider extends ContentProvider {
private static final String[] DEFAULT_PROJECTION = new String[] {
MediaColumns.DATA,
MediaColumns.DISPLAY_NAME,
MediaColumns.SIZE,
};
#Override
public boolean onCreate() {
return true;
}
#Override
public String getType(Uri uri) {
String fileName = getFileName(uri);
if (fileName == null) return null;
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName);
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileName = getFileName(uri);
if (fileName == null) return null;
File file = new File(fileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String fileName = getFileName(uri);
if (fileName == null) return null;
String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
MatrixCursor ret = new MatrixCursor(columnNames);
Object[] values = new Object[columnNames.length];
for (int i = 0, count = columnNames.length; i < count; i++) {
String column = columnNames[i];
if (MediaColumns.DATA.equals(column)) {
values[i] = uri.toString();
}
else if (MediaColumns.DISPLAY_NAME.equals(column)) {
values[i] = getUniqueName(uri);
}
else if (MediaColumns.SIZE.equals(column)) {
File file = new File(fileName);
values[i] = file.length();
}
}
ret.addRow(values);
return ret;
}
private String getFileName(Uri uri) {
String path = uri.getLastPathSegment();
return path != null ? new String(Base64.decode(path, Base64.DEFAULT)) : null;
}
private String getUniqueName(Uri uri) {
String path = getFileName(uri);
List<String> segments = uri.getPathSegments();
if (segments.size() > 0 && path != null) {
String baseName = FilenameUtils.getBaseName(path);
String extension = FilenameUtils.getExtension(path);
String uniqueId = segments.get(0);
return baseName + uniqueId + "." + extension;
}
return null;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0; // not supported
}
#Override
public int delete(Uri uri, String arg1, String[] arg2) {
return 0; // not supported
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null; // not supported
}
}
Note:
My sample code uses the org.apache.commons library for the file name manipulations (FilenameUtils.getXYZ)
using base64 encoding for the file path is a valid approach because all character used in base64 ([a-zA-Z0-9_-=] according to this https://stackoverflow.com/a/6102233/534471) are valid in an Uri path (0-9, a-z, A-Z, _-!.~'()*,;:$&+=/# --> see https://developer.android.com/reference/java/net/URI.html)
Your manifest would have to define the ContentProvider like so:
<provider
android:name="lb.com.myapplication2.fileprovider.FileProvider"
android:authorities="lb.com.myapplication2.fileprovider"
android:exported="true"
android:grantUriPermissions="true"
android:multiprocess="true"/>
It won't work without android:grantUriPermissions="true" and android:exported="true" because the other app wouldn't have permission to access the ContentProvider (see also http://developer.android.com/guide/topics/manifest/provider-element.html#exported) . android:multiprocess="true" on the other hand is optional but should make it more efficient.
Here's a working solution for using SymLinks. Disadvantages:
works from API 14, and not on API 10 , not sure about in between.
uses reflection, so might not work in the future, and on some devices.
must create the symlinks in the path of "getFilesDir", so you have to manage them by yourself, and create unique files names as needed.
The sample shares the APK of the current app.
Code:
public class SymLinkActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(lb.com.myapplication2.R.layout.activity_main);
final Intent intent=new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType(MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"));
final String filePath;
try
{
final android.content.pm.ApplicationInfo applicationInfo=getPackageManager().getApplicationInfo(getPackageName(),0);
filePath=applicationInfo.sourceDir;
}
catch(NameNotFoundException e)
{
e.printStackTrace();
finish();
return;
}
final File file=new File(filePath);
final String symcLinksFolderPath=getFilesDir().getAbsolutePath();
findViewById(R.id.button).setOnClickListener(new android.view.View.OnClickListener(){
#Override
public void onClick(final android.view.View v)
{
final File symlink=new File(symcLinksFolderPath,"CustomizedNameOfApkFile-"+System.currentTimeMillis()+".apk");
symlink.getParentFile().mkdirs();
File[] oldSymLinks=new File(symcLinksFolderPath).listFiles();
if(oldSymLinks!=null)
{
for(java.io.File child : oldSymLinks)
if(child.getName().endsWith(".apk"))
child.delete();
}
symlink.delete();
// do some dirty reflection to create the symbolic link
try
{
final Class<?> libcore=Class.forName("libcore.io.Libcore");
final java.lang.reflect.Field fOs=libcore.getDeclaredField("os");
fOs.setAccessible(true);
final Object os=fOs.get(null);
final java.lang.reflect.Method method=os.getClass().getMethod("symlink",String.class,String.class);
method.invoke(os,file.getAbsolutePath(),symlink.getAbsolutePath());
final ArrayList<Uri> uris=new ArrayList<>();
uris.add(Uri.fromFile(symlink));
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM,uris);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_NO_HISTORY|Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET|Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
startActivity(intent);
android.widget.Toast.makeText(SymLinkActivity.this,"succeeded ?",android.widget.Toast.LENGTH_SHORT).show();
}
catch(Exception e)
{
android.widget.Toast.makeText(SymLinkActivity.this,"failed :(",android.widget.Toast.LENGTH_SHORT).show();
e.printStackTrace();
// TODO handle the exception
}
}
});
}
}
EDIT: for the symlink part, for Android API 21 and above, you can use this instead of reflection :
Os.symlink(originalFilePath,symLinkFilePath);

Sharing images that are stored on internal memory

I have an application in which an ImageView is set and can be clicked to be opened in the gallery.
By default, I use the following code to get a file directory from the external storage to store my jpegs:
File picsDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"MyCameraApp");
But! Suppose the external storage is not mounted or simply does not exist (Galaxy Nexus), this doesn't work. So I wrote an if-statement around it and get the internal cache dir as a fall back.
String state = Environment.getExternalStorageState()
if(Environment.MEDIA_MOUNTED.equals(state)){
File picsDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"MyCameraApp");
}else{
context.getCacheDir();
}
The images show up fine in the ImageView, but don't come through when my intent launches.
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(imgFile), "image/jpeg");
startActivity(intent);
The gallery gets loaded, but shows a black screen. Presumably because the gallery has no access to files in the cache dir of my app.
As an alternative, I tried using the media content provider that uses MediaStore.Images.Media.INTERNAL_CONTENT_URI, but this leads to an error when trying to inser the image:
java.lang.UnsupportedOperationException: Writing to internal storage is not supported.
What should I do?
i suppose the problem here is that you are trying open with the gallery a file saved in a private space of memory (getCacheDir return a path relative to your application and only your application can access that memory path)
If you can't save in external memory, you can try to save in a public path (but that way your media files can be manipulated by every app and if you uninstall your application it doesn't clean generated media that you saved there)
If you want to use private internal memory, you can write your ContentProvider
i edit to post a content provider i use to acomplish what i said.
this is my content provider (i just posted the relevant part you need):
public class MediaContentProvider extends ContentProvider {
private static final String TAG = "MediaContentProvider";
// name for the provider class
public static final String AUTHORITY = "com.way.srl.HandyWay.contentproviders.media";
private MediaData _mediaData;
// UriMatcher used to match against incoming requests
private UriMatcher _uriMatcher;
#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() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add a URI to the matcher which will match against the form
// 'content://com.stephendnicholas.gmailattach.provider/*'
// and return 1 in the case that the incoming Uri matches this pattern
_uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#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;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Log.v(TAG, "Called with uri: '" + uri + "'." + uri.getLastPathSegment());
// Check incoming Uri against the matcher
switch (_uriMatcher.match(uri)) {
// If it returns 1 - then it matches the Uri defined in onCreate
case 1:
// The desired file name is specified by the last segment of the
// path
// E.g.
// 'content://com.stephendnicholas.gmailattach.provider/Test.txt'
// Take this and build the path to the file
// String fileLocation = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
Integer mediaID = Integer.valueOf(uri.getLastPathSegment());
if (_mediaData == null) {
_mediaData = new MediaData();
}
Media m = _mediaData.get(mediaID);
// Create & return a ParcelFileDescriptor pointing to the file
// Note: I don't care what mode they ask for - they're only getting
// read only
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(m.filePath), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
// Otherwise unrecognised Uri
default:
Log.v(TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
then you need in the manifest the reference to your contentprovider, in my case it was
<provider
android:name=".contentproviders.MediaContentProvider"
android:authorities="com.way.srl.HandyWay.contentproviders.media" >
</provider>
and then use it like this
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("content://" + MediaContentProvider.AUTHORITY + "/" + m.id), "image/jpg");
in my case m is an entity that store an id that point to a sqlite db and i use a class that fetch data to populate again the object (with _mediaData), you can just change the code to fit your needs
this way i solved exactly your problem in my application
I have understood that this fallback is not needed. Devices that have Google Play are guaranteed to have at least 2 GB available in Environment.getExternalStorageDirectory().
I suppose in the Galaxy Nexus this is a partition on the internal memory marked as external. I'll just show a warning if it is not available.

How to delete a file from SD card

I am creating a file to send as an attachment to an email. Now I want to delete the image after sending the email. Is there a way to delete the file?
I have tried myFile.delete(); but it didn't delete the file.
I'm using this code for Android, so the programming language is Java using the usual Android ways to access the SD card. I am deleting the file in the onActivityResult method, when an Intent is returned to the screen after sending an email.
File file = new File(selectedFilePath);
boolean deleted = file.delete();
where selectedFilePath is the path of the file you want to delete - for example:
/sdcard/YourCustomDirectory/ExampleFile.mp3
Also you have to give permission if you are using >1.6 SDK
uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
in AndroidManifest.xml file
Change for Android 4.4+
Apps are not allowed to write (delete, modify ...) to external storage except to their package-specific directories.
As Android documentation states:
"Apps must not be allowed to write to secondary external storage
devices, except in their package-specific directories as allowed by
synthesized permissions."
However nasty workaround exists (see code below). Tested on Samsung Galaxy S4, but this fix does't work on all devices. Also I wouldn’t count on this workaround being available in future versions of Android.
There is a great article explaining (4.4+) external storage permissions change.
You can read more about workaround here.
Workaround source code is from this site.
public class MediaFileFunctions
{
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static boolean deleteViaContentProvider(Context context, String fullname)
{
Uri uri=getFileUri(context,fullname);
if (uri==null)
{
return false;
}
try
{
ContentResolver resolver=context.getContentResolver();
// change type to image, otherwise nothing will be deleted
ContentValues contentValues = new ContentValues();
int media_type = 1;
contentValues.put("media_type", media_type);
resolver.update(uri, contentValues, null, null);
return resolver.delete(uri, null, null) > 0;
}
catch (Throwable e)
{
return false;
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static Uri getFileUri(Context context, String fullname)
{
// Note: check outside this class whether the OS version is >= 11
Uri uri = null;
Cursor cursor = null;
ContentResolver contentResolver = null;
try
{
contentResolver=context.getContentResolver();
if (contentResolver == null)
return null;
uri=MediaStore.Files.getContentUri("external");
String[] projection = new String[2];
projection[0] = "_id";
projection[1] = "_data";
String selection = "_data = ? "; // this avoids SQL injection
String[] selectionParams = new String[1];
selectionParams[0] = fullname;
String sortOrder = "_id";
cursor=contentResolver.query(uri, projection, selection, selectionParams, sortOrder);
if (cursor!=null)
{
try
{
if (cursor.getCount() > 0) // file present!
{
cursor.moveToFirst();
int dataColumn=cursor.getColumnIndex("_data");
String s = cursor.getString(dataColumn);
if (!s.equals(fullname))
return null;
int idColumn = cursor.getColumnIndex("_id");
long id = cursor.getLong(idColumn);
uri= MediaStore.Files.getContentUri("external",id);
}
else // file isn't in the media database!
{
ContentValues contentValues=new ContentValues();
contentValues.put("_data",fullname);
uri = MediaStore.Files.getContentUri("external");
uri = contentResolver.insert(uri,contentValues);
}
}
catch (Throwable e)
{
uri = null;
}
finally
{
cursor.close();
}
}
}
catch (Throwable e)
{
uri=null;
}
return uri;
}
}
Android Context has the following method:
public abstract boolean deleteFile (String name)
I believe this will do what you want with the right App premissions as listed above.
Recursively delete all children of the file ...
public static void DeleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
DeleteRecursive(child);
}
}
fileOrDirectory.delete();
}
This works for me: (Delete image from Gallery)
File file = new File(photoPath);
file.delete();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(photoPath))));
public static boolean deleteDirectory(File path) {
// TODO Auto-generated method stub
if( path.exists() ) {
File[] files = path.listFiles();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
return(path.delete());
}
This Code will Help you.. And In Android Manifest You have to get Permission to make modification..
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Try this.
File file = new File(FilePath);
FileUtils.deleteDirectory(file);
from Apache Commons
Sorry: There is a mistake in my code before because of the site validation.
String myFile = "/Name Folder/File.jpg";
String myPath = Environment.getExternalStorageDirectory()+myFile;
File f = new File(myPath);
Boolean deleted = f.delete();
I think is clear...
First you must to know your file location.
Second,,, Environment.getExternalStorageDirectory() is a method who gets your app directory.
Lastly the class File who handle your file...
I had a similar issue with an application running on 4.4. What I did was sort of a hack.
I renamed the files and ignored them in my application.
ie.
File sdcard = Environment.getExternalStorageDirectory();
File from = new File(sdcard,"/ecatAgent/"+fileV);
File to = new File(sdcard,"/ecatAgent/"+"Delete");
from.renameTo(to);
This worked for me.
String myFile = "/Name Folder/File.jpg";
String my_Path = Environment.getExternalStorageDirectory()+myFile;
File f = new File(my_Path);
Boolean deleted = f.delete();
private boolean deleteFromExternalStorage(File file) {
String fileName = "/Music/";
String myPath= Environment.getExternalStorageDirectory().getAbsolutePath() + fileName;
file = new File(myPath);
System.out.println("fullPath - " + myPath);
if (file.exists() && file.canRead()) {
System.out.println(" Test - ");
file.delete();
return false; // File exists
}
System.out.println(" Test2 - ");
return true; // File not exists
}
You can delete a file as follow:
File file = new File("your sdcard path is here which you want to delete");
file.delete();
if (file.exists()){
file.getCanonicalFile().delete();
if (file.exists()){
deleteFile(file.getName());
}
}
File filedel = new File("/storage/sdcard0/Baahubali.mp3");
boolean deleted1 = filedel.delete();
Or, Try This:
String del="/storage/sdcard0/Baahubali.mp3";
File filedel2 = new File(del);
boolean deleted1 = filedel2.delete();

Categories

Resources