Some of my users have reported to Google Play the following error when trying to select a ringtone in my app. (there's more to it, but it's not relevant)
java.lang.SecurityException: Permission Denial:
reading com.android.providers.media.MediaProvider
uri content://media/external/audio/media
from pid=5738, uid=10122 requires android.permission.READ_EXTERNAL_STORAGE
I assume this issue is happening due to certain tones that are on external storage. I don't want to include the READ_EXTERNAL_STORAGE permission in my app unless I absolutely have to.
Is there a way to circumvent the issue and just exclude any tones that may be on the external storage?
Note: I'm getting ringtones with RingtoneManager and convert them between String and Uri. No other code is touching the user's media.
Also, I do not have a line number since the stacktrace is from obfuscated code and re-mapping the stack trace did not provide line number.
Just had the same problem and came up with the following solution:
private Cursor createCursor()
{
Uri uri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
String[] columns = new String[]
{
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE_KEY
};
String filter = createBooleanFilter(MediaStore.Audio.AudioColumns.IS_ALARM);
String order = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
return getContext().getContentResolver().query(uri, columns, filter, null, order);
}
private String createBooleanFilter(String... columns)
{
if(columns.length > 0)
{
StringBuilder sb = new StringBuilder();
sb.append("(");
for(int i = columns.length - 1; i > 0; i--)
{
sb.append(columns[i]).append("=1 or ");
}
sb.append(columns[0]);
sb.append(")");
return sb.toString();
}
return null;
}
To get the Uri of a ringtone you need to combine the INTERNAL_CONTENT_URI with the _ID column value, you can do this by using ContentUris class:
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, cursor.getLong(0));
You can find the external storage directory without needing WRITE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE by using Environment.getExternalStorageDirectory().
You can then compare the paths for the URIs provided by RingtoneManager to this path to see if they are on the external storage or not and if so add those items to a List.
Then, rather than passing the raw Cursor to the UI you can use that List with a ListAdapter instead.
For example (untested, you may need to change the method of comparing paths):
class RingtoneDetails
{
public String ID;
public String Title;
public Uri Uri;
public RingtoneDetails(String id, String title, Uri uri)
{
ID = id;
Title = title;
Uri = uri;
}
}
private List<RingtoneDetails> getNonExternalRingtones(RingtoneManager manager)
{
List<RingtoneDetails> ringtones = new List<RingtoneDetails>();
Cursor cursor = manager.getCursor();
String extDir = Environment.getExternalStorageDirectory().getAbsolutePath();
while (cursor.moveToNext())
{
String id = cursor.getString(cursor.getColumnIndex(RingtoneManager.ID_COLUMN_INDEX));
String title = cursor.getString(cursor.getColumnIndex(RingtoneManager.TITLE_COLUMN_INDEX));
Uri uri= cursor.getString(cursor.getColumnIndex(RingtoneManager.URI_COLUMN_INDEX));
if(!uri.getPath().contains(extDir))
{
ringtones.add(new Ringtone(id, title, uri));
}
}
return ringtones;
}
Previously, I was using RingtoneManager to get a list and display that in a dialog for a user to select. It was throwing the SecurityException on ringtoneManager.getCursor();
I did not want to add the external storage permission, so I switched to doing:
final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select Ringtone");
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,RingtoneManager.TYPE_ALL);
startActivityForResult( intent, RINGTONE_RESULT);
And then in onActivityResult
if (requestCode == RINGTONE_RESULT&&resultCode == RESULT_OK&&data!=null) {
try {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (uri==null){
setSilent(); //UI stuff in this method
} else {
Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
String name = ringtone.getTitle(context);
changeTone.setText(name); //changeTone is a button
}
} catch (SecurityException e){
setSilent();
Toast.makeText(context, "Error. Tone on user storage. Select a different ringtone.", Toast.LENGTH_LONG).show();
} catch (Exception e){
setSilent();
Toast.makeText(context, "Unknown error. Select a different ringtone.", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(context, "Ringtone not selected. Tone set to silent.", Toast.LENGTH_SHORT).show();
setSilent();
}
Related
The app sends the user to SAF picker with ACTION_OPEN_DOCUMENT_TREE:
void openStoragePicker() {
String messageTitle = "Choose directory app to use";
Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(Intent.createChooser(intent, messageTitle), Dry.REQUEST_CHOOSE_APP_DIR);
}
In onActivityResult, we take persistable permission and store a String of the Uri:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
switch (resultCode) {
case Activity.RESULT_OK:
if (requestCode == Dry.REQUEST_CHOOSE_APP_DIR) {
if (resultData == null) {
Log.d(Dry.TAG, "result data null");
} else {
if (resultData.getData() != null) {
Uri uri = resultData.getData();
Storage.releasePersistedPermissions(this);
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Storage.setSharedPrefString(uri.toString(), Storage.SHARED_PREF_APP_DIR_URI, this);
dbw.clearAlreadyPlayed();
}
}
}
break;
case Activity.RESULT_CANCELED:
//
break;
}
}
We recreate the tree Uri when we need it:
static Uri getTheDir(Context context) {
String result = Storage.getSharedPrefString(SHARED_PREF_APP_DIR_URI, context);
if (result == DEFAULT_SHARED_PREF_STRING) {
return null;
}
Uri dirUriParsed = Uri.parse(Uri.decode(result));
Log.d(Dry.TAG, "the dir uri parsed: " + dirUriParsed.toPath());
return dirUriParsed;
}
We want to list the files, and we can, using the pattern shown here.
static ArrayList<String> getFiles(Context context) {
ArrayList<String> fileStrings = new ArrayList<>();
Uri rootUri = getTheDir(context);
if (rootUri == null) {
return fileStrings;
}
long startTime = System.currentTimeMillis();
ContentResolver contentResolver = context.getContentResolver();
String theDocToReturnChildrenFor = DocumentsContract.getTreeDocumentId(rootUri);
Log.d(Dry.TAG, "theDocToReturnChildrenFor: " + theDocToReturnChildrenFor);
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, theDocToReturnChildrenFor);
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0);
Cursor c = contentResolver.query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
if (isDirectory(mime)) {
if (Arrays.asList(SUBDIRECTORIES_TO_OMIT).contains(name)) {
continue;
}
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
} else {
for (String ext: SUPPORTED_FILE_EXTENSIONS) {
if (name.endsWith(ext)) {
fileStrings.add(docId);
break;
}
}
}
}
} finally {
closeQuietly(c);
}
}
Log.d(Dry.TAG, "fileStrings length: " + fileStrings.size() + "time spent building song list: " + ((System.currentTimeMillis() - startTime) / 1000.0) + "s");
return fileStrings;
}
But, this only works as expected when the directory happens to be a top-level directory within the storage volume. If the directory that the user chose is not a direct child of the volume root, then, when we try DocumentsContract.getTreeDocumentId(rootUri), it returns not the document ID for that URI, but rather the document ID for its highest parent before the volume root!
The log call that prints the reconstructed Uri gives this output:
the dir uri parsed: /tree/primary:a test dir/a child test dir/3rd level dir
But the other log call that prints the doc ID prints this:
theDocToReturnChildrenFor: primary:a test dir
Am I doing it wong? Is this an Android bug? I noticed this question describes the exact same behavior from this method. That issue was solvable by following the established recursive listing pattern, but, that user says:
It is almost like getTreeDocumentId(rootUri) is returning what getRootId(rootUri) should be returning.
The docs for this method are not helpful, they are brief and have a typo, leaving the meaning unclear. DocumentsContract.getTreeDocumentId docs.
Target SDK of the app is 30. The device Android version is also api 30 (Android 11).
If someone could help me to get the correct doc ID for the user-selected directory, I would appreciate it.
Uri dirUriParsed = Uri.parse(Uri.decode(result))
Try:
Uri dirUriParsed = Uri.parse(result)
My app requires a feature that backups WhatsApp status, voice notes, and images. As you know after Android Q google enforcing to access external media files using MediaStore API.
WhatsApp also moved their file to /Android/media/com.whatsapp/WhatsApp. I tried using MANAGE_EXTERNAL_STORAGE permission it works fine, but backing up these files is not the core functionality of the app, so I don't think google going to let me use this permission.
I wonder if there any way to read those files using MediaStore.VOLUME_EXTERNAL?
I tried something like this. I am not sure if this is even possible.
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
val selection= (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE)
val selectionArgs= arrayOf("%/WhatsApp/Media/.Statuses%")
val cursor = applicationContext.contentResolver.query(
collection, null, selection, selectionArgs, null)
debug(cursor?.columnCount)
cursor?.close()
it throws an exception.
Caused by: android.database.sqlite.SQLiteException: no such column: media_type
Try this...
To read whatsapp status you don't need to do more things, just changed its path.
Also you don't need to use MANAGE_EXTERNAL_STORAGE permission. This can work with older permission too.
BELOW ANSWER FOR SDK 29
This way I have done
String path=Environment.getExternalStorageDirectory().getAbsolutePath()+"/Android/media/"+"com.whatsapp" + "/WhatsApp/Media/.Statuses/"
File directory = new File(wa_path);
if (!directory.exists()) {
directory.mkdirs();
}
File[] files = directory.listFiles();
if (files != null) {
Arrays.sort(files, LastModifiedFileComparator.LASTMODIFIED_REVERSE);
for (int i = 0; i < files.length; i++) {
if (!files[i].getName().equals(".nomedia")) {
your_list.add(files[i]);
}
}
}
This is working in android 11 too. And I only ask for "READ_EXTERNAL_STORAGE" and "WRITE_EXTERNAL_STORAGE" like before.
So now you need to check whether "WhatsApp" folder is available in root storage(before where it was), if not available then need to check in path I mentioned above.
FOR SDK 30 AND ABOVE
First ask for app specific folder permission
try {
Intent createOpenDocumentTreeIntent = ((StorageManager) getSystemService("storage")).getPrimaryStorageVolume().createOpenDocumentTreeIntent();
String replace = ((Uri) createOpenDocumentTreeIntent.getParcelableExtra("android.provider.extra.INITIAL_URI")).toString().replace("/root/", "/document/");
createOpenDocumentTreeIntent.putExtra("android.provider.extra.INITIAL_URI", Uri.parse(replace + "%3A" + "Android%2Fmedia"));
startActivityForResult(createOpenDocumentTreeIntent, 500);
} catch (Exception unused) {
Toast.makeText(MainActivity.this, "can't find an app to select media, please active your 'Files' app and/or update your phone Google play services", 1).show();
}
Now in OnActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 500) {
if (data != null) {
Uri wa_status_uri = Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fmedia/document/primary%3AAndroid%2Fmedia%2Fcom.whatsapp%2FWhatsApp%2FMedia%2F.Statuses");
getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
//save shared preference to check whether app specific folder permission granted or not FastSave.getInstance().saveBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, true);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getDataForAndroid11OnlyStatus(wa_status_uri);
}
}, 100);
}
}
}
//now get status from whatsApp folder
ArrayList<Object> statusObjectsList = new ArrayList<>();
private void getDataForAndroid11OnlyStatus(Uri uriMain) {
//check for app specific folder permission granted or not
if (!FastSave.getInstance().getBoolean(Utils.KEY_IS_APP_SPECTIFIC_PERMISSION_GRANTED, false)) {
//ask for folder permission
}
Log.d("====", "uriMain ::: " + uriMain);
ContentResolver contentResolver = getContentResolver();
Uri uri = uriMain;
Uri buildChildDocumentsUriUsingTree = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
ArrayList arrayList = new ArrayList();
Cursor cursor = null;
try {
cursor = contentResolver.query(buildChildDocumentsUriUsingTree, new String[]{"document_id"}, (String) null, (String[]) null, (String) null);
while (cursor.moveToNext()) {
arrayList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
if (!DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)).toString().endsWith(".nomedia")) {
FileHelper fileHelper = new FileHelper(MainActivity.this);
String filePath = fileHelper.getRealPathFromUri(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
statusObjectsList.add(DocumentsContract.buildDocumentUriUsingTree(uriMain, cursor.getString(0)));
}
}
runOnUiThread(new Runnable() {
#Override
public void run() {
//here set your adapter in list and set data from statusObjectsList
}
});
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable th) {
throw th;
}
}
Let me know if you didn't understand or not works.
This is working tested in Android 11 emulator, OnePlus Nord, Redmi Note 10 Pro and Poco. In All of these Android 11 device WhatsApp statuses are displaying.
added:
So what is the api you targrt I think you target api 29 It will not work if you target api 30 prove your answer
Thank you
I am trying to learn how to add set as contact ringtone feature. I already know how to set default ringtone but I can't figure how to set as contact ringtone.
I got to the part where I choose contact, but I don't know how to assign ringtone to that contact.
That part is bugging me and I can't seem to find answer in questions that were already asked on this topic.
Here is my code so far:
static public final int CONTACT_CHOOSER_ACTIVITY_CODE = 73729;
private File csound;
private final File rpath = new File(Environment.getExternalStorageDirectory() + "/Ringtone sounds/Ringtones");
#Override
public void onClick(View v) {
setContRing();
}
private void setContRing() {
Boolean success = false;
csound = new File(rpath, FNAME);rpath.mkdirs();
if (!csound.exists()) {
try {
InputStream in = getResources().openRawResource(FPATH);
FileOutputStream out = new FileOutputStream(csound.getPath());
byte[] buff = new byte[1024];
int read = 0;
try {
while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read);
}
} finally {
in.close();
out.close();
}
} catch (Exception e) {
success = false;
}
} else {
success = true;
setContRingtone();
}
if (!success) {
setContRingtone();
}
}
private void setContRingtone() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(intent, CONTACT_CHOOSER_ACTIVITY_CODE);
}
});
}
Edit for bounty: I am wondering if someone can show me how to do so, I tried with codes found in other questions but I couldn't apply them to my code. I can copy file but how to get contact and assign ringtone to that contact?
From set custom ringtone to specific contact number
Android has a special column for this: ContactsContract.CUSTOM_RINGTONE.
So, you could use ContactsContract.Contacts.getLookupUri to get your contact's Uri, after that pretty much all that's left is to call ContentResolver.update.
Here's an example of looking up a contact by their phone number, then applying a custom ringtone:
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
// The Uri used to look up a contact by phone number
final Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "012-345-6789");
// The columns used for `Contacts.getLookupUri`
final String[] projection = new String[] {
Contacts._ID, Contacts.LOOKUP_KEY
};
// Build your Cursor
final Cursor data = getContentResolver().query(lookupUri, projection, null, null, null);
data.moveToFirst();
try {
// Get the contact lookup Uri
final long contactId = data.getLong(0);
final String lookupKey = data.getString(1);
final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
if (contactUri == null) {
// Invalid arguments
return;
}
// Get the path of ringtone you'd like to use
final String storage = Environment.getExternalStorageDirectory().getPath();
final File file = new File(storage + "/AudioRecorder", "hello.mp4");
final String value = Uri.fromFile(file).toString();
// Apply the custom ringtone
final ContentValues values = new ContentValues(1);
values.put(Contacts.CUSTOM_RINGTONE, value);
getContentResolver().update(contactUri, values, null, null);
} finally {
// Don't forget to close your Cursor
data.close();
}
Also, you'll need to add both permissions to read and write contacts:
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
To extend a bit on this, and how to modify it to your need, change phone number 012-345-6789 in this line to the one you are looking for
// The Uri used to look up a contact by phone number
final Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "012-345-6789");
And set your default CUSTOM_RINGTONE in your phone ContactsContract. There is another, similar, option here:
Setting contact custom ringtone, how?
Question: how to make the media store to refresh its entry of a DELETED file?
After deleting a photo in code from the external storage, I still see a slot for the deleted photo in the gallery - blank photo.
It seems that the gallery reflects the media store and the deleted photo is found in the media store until the phone is restarted or generally - until the media is rescanned.
Trying to scan the deleted file did not help scanning deleted files (works just for new or existing files): MediaScannerConnection.scanFile(Application.get(), new String[]{file.getPath()}, null, null) (I tried scanning the parent folder as well).
Also tried ACTION_MEDIA_SCANNER_SCAN_FILE to no avail. Example: Application.get().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
Sending a broadcast receiver to rescan the entire external storage (thus refreshing the media store)did the trick: Application.get().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())))
BUT, it seems that Android, as of 4.4, throws a security exception when trying to manually send the ACTION_MEDIA_MOUNTED system broadcast. See #CommonsWare's post: http://commonsware.com/blog/2013/11/06/android-4p4-permission-regressions.html
So, I'm stuck with no solution for refreshing the media store upon file(/photo/video/etc.) deletion.
I found the following that works for me in 4.4 on a Nexus 10.
// request scan
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, SELECT_PICTURE);
Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
scanIntent.setData(Uri.fromFile(refreshFile));
sendBroadcast(scanIntent);
"refreshFile" is the file I deleted that I get from my String "fPath" and then convert it to a file.
String filePath = fPath;
File refreshFile = new File(filePath);
I had the same issue. I wrote the following code and it worked on all versions from lollipop to oreo. I also called the mediastore.scanfile() method to ensure that MediaStore is updated. Adding the code below - you might not want to use the "delete()" method in future as the scanfile() might be comprehensive. But, if you want to support older phones then delete() would probably be safer.
// fileID == MediaStore.Images.Media._ID; for the file when you get the file from the content
// resolver
public static boolean deleteCREntryForFilePath(Context context, String filePath, long fileID) {
boolean fDeleted = false;
ContentResolver cr = context.getContentResolver();
int rowsDeleted = 0;
Uri imageURI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String deleteStr = MediaStore.Images.Media._ID + "=" + fileID;
MediaScannerConnection.scanFile(context, new String[]{filePath}, null, null);
// Remove entry from database
rowsDeleted = context.getContentResolver().delete(
imageURI, deleteStr, null);
if (rowsDeleted > 0)
fDeleted = true;
return(fDeleted);
}
Here is the code to get the file-id (name of the function is getfileId()) . It works for different file-types. You cannot compile the code as it is because it uses an internal object-type but you should be easily able to convert this for generic use.
public static String[] getCombinedEntityColumns(Constants.DELASHARE_OBJECT_TYPES objType) {
String[] entityColumns = new String[5];
switch (objType) {
case DELASHARE_OBJECT_PICTURE:
case DELASHARE_OBJECT_MUSIC:
case DELASHARE_OBJECT_VIDEO: {
entityColumns[0] = MediaStore.Images.Media.DISPLAY_NAME;
entityColumns[1] = MediaStore.Images.Media.DATA;
entityColumns[2] = MediaStore.Images.Media._ID;
entityColumns[3] = MediaStore.Images.Media.DATE_ADDED;
//entityColumns[3] = MediaStore.Images.Media.DATE_TAKEN;
entityColumns[4] = MediaStore.Images.Media.SIZE;
break;
}
case DELASHARE_OBJECT_APK:
case DELASHARE_OBJECT_DOCUMENT:
case DELASHARE_OBJECT_DOWNLOAD:
case DELASHARE_OBJECT_SEARCH_RESULTS:
default: {
entityColumns[0] = MediaStore.Files.FileColumns.DISPLAY_NAME;
entityColumns[1] = MediaStore.Files.FileColumns.DATA;
entityColumns[2] = MediaStore.Files.FileColumns._ID;
entityColumns[3] = MediaStore.Files.FileColumns.DATE_MODIFIED;
entityColumns[4] = MediaStore.Files.FileColumns.SIZE;
break;
}
}
return (entityColumns);
}
public static Uri getCategoryUri(Constants.DELASHARE_OBJECT_TYPES categoryObjType) {
Uri objUri = null;
switch(categoryObjType) {
case DELASHARE_OBJECT_PICTURE:
objUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_VIDEO:
objUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_MUSIC:
objUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
break;
case DELASHARE_OBJECT_DOWNLOAD: {
File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
objUri = Uri.fromFile( downloadDir);
//objUri = MediaStore.Files.getContentUri("external");
break;
}
case DELASHARE_OBJECT_APK:
case DELASHARE_OBJECT_DOCUMENT:
case DELASHARE_OBJECT_SEARCH_RESULTS:
default:
objUri = MediaStore.Files.getContentUri("external");
break;
}
return(objUri);
}
public static long getFileId(Context context, String dirPath, String filePath, String fileName, Constants.DELASHARE_OBJECT_TYPES objType) {
boolean fIDFound = false;
long id = 0;
if (!fIDFound) {
String sortOrder = null;
String[] entityColumns = getCombinedEntityColumns(objType);
Uri categoryUri = getCategoryUri(objType);
String selection = null;
String[] selectionArgs = new String[]{Constants.DELA_PERCENT_STR + dirPath};
ContentResolver cr = context.getContentResolver();
Cursor cursor = null;
switch (objType) {
case DELASHARE_OBJECT_PICTURE:
selection = MediaStore.Images.Media.DATA + " LIKE ?";
break;
case DELASHARE_OBJECT_VIDEO:
selection = MediaStore.Video.Media.DATA + " LIKE ?";
break;
case DELASHARE_OBJECT_DOCUMENT:
default:
selection = MediaStore.Files.FileColumns.DATA + " LIKE ?";
break;
}
cursor = cr.query(
categoryUri,
entityColumns,
selection,
selectionArgs,
sortOrder);
if (cursor != null && cursor.moveToFirst()) {
id = cursor.getLong(cursor.getColumnIndex(entityColumns[2]));
if (id != 0) {
fIDFound = true;
}
}
if (cursor != null) {
cursor.close();
cursor = null;
}
}
return(id);
}
I had the same question as you now that the sendBroadcast approach is disallowed in 4.4 and found a good solution here using the Media Store content provider: https://stackoverflow.com/a/20780472/1060805
I tested it out on Android 4.4 and it works nicely. I think it is a solid approach.
Try
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
Add this to your manifest:
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file" />
</intent-filter>
I have registered my app to receive files (of any type, not just images) from other apps following this post.
I have implemented the solution that was answered but I cannot find a way to retrieve the "file name" of the data stream.
As an example from an Uri like:
content://downloads/all_downloads/5
I can get the stream out but I don't know anything about the name of the original file generating it.
Is there a way to retrieve it?
In MOST cases this will solve your problem:
Uri intentData = intent.getData();
if (intentData != null) {
String filePath;
if("content".equals(intent.getScheme()))
{
filePath = getFilePathFromContentUri(intentData);
}
else
{
filePath = intentData.getPath();
}
}
private String getFilePathFromContentUri(Uri selectedUri) {
String filePath;
String[] filePathColumn = {MediaColumns.DATA};
Cursor cursor = getContentResolver().query(selectedUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
Is there a way to retrieve it?
Generally, no, because there may not be a name, in part because there may not be a file. You may be able to get an InputStream on the contents, but that does not mean that there is a file behind the InputStream.
There may be some specific hacks for some specific providers (e.g., MediaStore) to try to determine the file name associated with some data Uri, though such hacks may not be reliable.
onCreate()
Intent intent1 = getIntent();
String action = intent1.getAction();
String type = intent1.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
this.handleSend(intent1);
}
void handleSend(Intent intent) {
try {
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
imageShare.setImageURI(imageUri);
} catch (Exception e) {
e.printStackTrace();
}
}