I'm writing an app that removes files that may or may not be listed in any one of the types of media libraries such as music or pictures. While I can use the MediaScannerConnection.scanFile method to add files to the media library there doesn't seem to be any call to notify the service that the file has been removed. Sending it the path of the file that no longer exists doesn't result in the desired behavior either. How should I go about removing items from the library that no longer exist on the Android storage?
I was able to put a method together using bits and pieces from these two questions
What is the String 'volumeName' argument of MediaStore.Audio.Playlists.Members.getContentUri referring to?
How can I refresh MediaStore on Android?
Basically I just run a query on each one of the MediaStore types (Audio, Video and Images) selecting by path and deleting any records I find.
public static void RemoveAllForPaths(String[] paths, Context context)
{
private static final String[] FIELDS = { MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.TITLE };
if(paths == null || paths.length == 0) return;
String select = "";
for(String path : paths)
{
if(!select.equals("")) select += " OR ";
select += MediaStore.MediaColumns.DATA + "=?";
}
Uri uri;
Cursor ca;
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ca = context.getContentResolver().query(uri, FIELDS, select, paths, null);
for(ca.moveToFirst(); !ca.isAfterLast(); ca.moveToNext()){
int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
context.getContentResolver().delete(uri, null, null);
}
ca.close();
// More of the same just setting the URI to Video and Images
}
I'm not entirely sure how safe this is to do but it's the only solution I've found so far and some initial testing seems to be working. I invite others to submit other answers if anyone has any further information on this approach or a better method for performing this functionality.
Answer of Spencer Ruport is right, but you don't need to query and open a cursor in order to delete.
So for one file that is music file the code is simple like that:
public void DeleteMP3FromMediaStore( Context context, String path )
{
Uri rootUri = MediaStore.Audio.Media.getContentUriForPath( path );
context.getContentResolver().delete( rootUri,
MediaStore.MediaColumns.DATA + "=?", new String[]{ path } );
}
P.S. I wanted to comment answer of Spencer Ruport but don't have enough reputation yet.
Easy as pie: whenever you add a file, let MediaStore ContentProvider knows about it using
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(fileToAddInMediaStore)));
For deletion: just use
getContentResolver().delete(Uri.fromFile(fileToDeleteFromMediaStore), null, null)
The following works well for me. You can delete or add files using this.
MediaScannerConnection.scanFile(
context,
new String[]{fileToDelete, fileToAdd},
null, null);
The available method is to remove the item from library.
This post is detailed expressed how to add into or remove from the Media Library.
http://androidyue.github.io/blog/2014/01/19/scan-media-files-in-android/ Hopes this could help you.
Related
The app I am working on needs a new lease on life and one renewing task for Android R and above: is getting the id of an image taken by CameraX from scoped storage. The purpose being to save the id in a datatable for upload later.
With regards to permissions the App only saves files to its own external directory, so no permissions are necessary.
After CameraX has captured the image using the following OutputOptions:
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, imgName)
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + "Custom directory")
val outputOptions = ImageCapture.OutputFileOptions.Builder(
activityContext.contentResolver,
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL),
contentValues
).build()
The onImageSaved CameraX callback method from the imageCapture.takePicture object returns an ImageCapture.OutputFileResults:
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri
This is where things get fuzzy; I have the Uri of the saved image. I tried using the Uri to retrieve the image column id by searching for the file name with:
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME
)
val selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?"
return contentResolver.query(
savedUri, // the image capture uri
projection, // query columns
selection, // query
arrayOf(Environment.DIRECTORY_DCIM + File.separator + "Custom directory"), // query arguments
null // order by
)?.use { cursor ->
if (cursor.count > 0) {
while (cursor.moveToNext()) {
val fileName =
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
Log.d(TAG, "index $fileName")
if (fileName.equals(displayName)) {
return#use cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
}
}
}
return#use null
}
I consistently get null results. So I tried the following content uri in the place of the savedUri:
val contentUri =
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
This also only gives null results and 0 cursor.count columns. Can someone please point me in the right direction on why the above file queries are giving null results?
Edit 1
After reading blackapps comment I went and read the documentation again. My understanding of scoped storage it seems is definitely lacking. I was using getExternalFilesDir before to get an output path and all was fine until I added getExternalFilesDir(Environment.DIRECTORY_DCIM) which according to the below comment is seen as public. That is when I started to meddle with the above MediaStore api and got tangled into thinking to much or little depending on my point of view.
Edit 2
Some more reading of the App specific sources where it does not state anywhere that getExternalFilesDir method will be removed in Api 32 or 33. So it can be used to even save in public directories. The example given is:
val file = File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName)
Further:
It's important that you use directory names provided by API constants like DIRECTORY_PICTURES. These directory names ensure that the files are treated properly by the system.
Caveat to the above quoted documentation is that this excludes DIRECTORY_DCIM: Why is this not in the documentation?
Edit 3:
A good question to ask when uncertain if you are dealing with scoped storage is whether the files are:
Shareable media files (images, audio files, videos)
I want to copy or move files from the internal storage to the sd card. I do this via the Storage Access Framework (SAF) and the DocumentFile class...
Copying is stream based and the DocumentFile does not have a function like the File class to set the last modified date.
I know, that I move/copy files to the sd card, so I know that I create a local file. With this information, is it somehow possible to update the last modified date of the underlying file of the DocumentFile?
It seems like you can't move/copy files from your internel storage to the sd card without losing the last modified date...
Reading - Working
public long lastModified(DocumentFile file, Context context)
{
long lastModified = 0;
final Cursor cursor = context.getContentResolver().query(file.getUri(), null, null, null, null);
try
{
if (cursor.moveToFirst())
lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED));
}
finally
{
cursor.close();
}
return lastModified;
}
WRITING - NOT WORKING
public boolean setLastModified(DocumentFile file, Context context, long time)
{
ContentValues updateValues = new ContentValues();
updateValues.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, time);
int updated = context.getContentResolver().update(file.getUri(), updateValues, null, null);
return updated == 1;
}
This fails with a java.lang.UnsupportedOperationException: Update not supported exception...
Probably you need permission "android.permission.WRITE_USER_DICTIONARY" in manifest
Since API >=26 you can use refresh. This should work to update the Documentfile instantly . This works for me:
context.getContentResolver().refresh(file.getUri(), null, null, null);
I've been looking everywhere since the past few days to find a way to retrieve a contact name using a phone number I already have stored in a variable, unfortunately everything I found so far seems to be using deprecated functions/calls.
Of Course, I tried doing it my own way but I feel like my Android/JAVA knowledge is not good enough to understand this concept yet, keep getting some errors or force close when I try to run anything.
So far the best thing I could find was something like this:
public String getContactName(final String phoneNumber)
{
Uri uri;
String[] projection;
if (Build.VERSION.SDK_INT >= 5)
{
uri = Uri.parse("content://com.android.contacts/phone_lookup");
projection = new String[] { "display_name" };
}
else
{
uri = Uri.parse("content://contacts/phones/filter");
projection = new String[] { "name" };
}
uri = Uri.withAppendedPath(uri, Uri.encode(phoneNumber));
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
String contactName = "";
if (cursor.moveToFirst())
{
contactName = cursor.getString(0);
}
cursor.close();
cursor = null;
return contactName;
}
But by using this code, Eclipse tells me: context cannot be resolved.
A lot of the codes and explanations I found were using this Context thing, but I still don't understand it even after reading this: What is 'Context' on Android?
Any help will be greatly appreciated,
Thank you very much
If you're using this inside an activity, then a context is what you get by using this. So basically here, instead of calling context.getContentResolver(), call this.getContentResolver() or simply just getContentResolver().
Eclipse complains basically because you're trying to call a method of something called context which Eclipse doesn't know because it hasn't been declared anywhere. It would work if you previously did something like Context context = this;, but that's really useless.
getContentResolver() is a method declared and defined by Activity which is a class that your activity extends, therefore you can call it just like that.
I hope it helps. As to what this context really is, I am sorry, but I can't help you with that as I am not even sure I understand it correctly.
Also, please notice, that I haven't checked the code you posted and I don't know if it works for obtaining a contact's name from a phone number. Just wanted to help you with getting rid of the context cannot be resolved error.
I'm attempting to update a calendar's event on my phone from my code, but context.getContentResolver().update keeps returning 0, and of course there are no changes made to the event when I look at it in the Calendar app.
I'm getting the event ID, start time, etc with context.getContentResolver().query, and I'm getting unique numbers like 431, 4, 233, etc, so I'm presuming the event IDs I'm using are real.
I understand the official way to do this is to go through Google's servers instead of using update(), but for my implementation it doesn't make sense to do it that way (or even in general, but I digress).
Am I doing something wrong, or am I trying to do something that Android simply isn't going to allow?
Uri updateEventUri = ContentUris.withAppendedId(Uri.parse("content://com.android.calendar/events"), id);
ContentValues cv = new ContentValues();
begin.set(Calendar.HOUR_OF_DAY, arg0.getCurrentHour()); //begin is a java.util.Calendar object
begin.set(Calendar.MINUTE, arg0.getCurrentMinute());
//cv.put("_id", id);
//cv.put("title", "yeahyeahyeah!");
cv.put("dtstart", begin.getTimeInMillis());
int updatedrowcount = context.getContentResolver().update(updateEventUri, cv, null, null);
System.out.println("updated "+updatedrowcount+" rows with id "+id);
A related question was posted here with no replies https://stackoverflow.com/questions/5636350/update-android-calendar-event
Let me know if I can clarify anything; I would really appreciate any input you guys and dolls could provide!
i had tried a lot and finally ended up with solution (Unreliable though).. but works fine..
public static boolean updateCalendar(Context context,String cal_Id,String eventId)
{
try{
Uri CALENDAR_URI = Uri.parse(CAL_URI+"events");
Cursor c = context.getContentResolver().query(CALENDAR_URI, null, null, null, null);
String[] s = c.getColumnNames();
if (c.moveToFirst())
{
while (c.moveToNext())
{
String _id = c.getString(c.getColumnIndex("_id"));
String CalId = c.getString(c.getColumnIndex("calendar_id"));
if ((_id==null) && (CalId == null))
{
return false;
}
else
{
if (_id.equals(eventId) && CalId.equals(cal_Id))
{
Uri uri = ContentUris.withAppendedId(CALENDAR_URI, Integer.parseInt(_id));
context.getContentResolver().update(uri, null, null, null);// need to give your data here
return true;
}
}
}
}
}
finally
{
return true;
}
}
and finally i'm not sure if it works with every device.
Ok, so, the problem was that I was using different URIs between fetching the events and editing them. I used the code sample from here and was using the URI "content://com.android.calendar/instances/when" to fetch the events and display them on the screen. When I had made a change I was using "content://com.android.calendar/events" to edit by id as in my example above.
What I found, thanks to your response, ntc, was that the ids for events between the two URIs were different, and therefore I couldn't edit the events consistently with the information each was giving me. I was presuming the event ids I was getting were system ids and universal to the phone.
I guess I'll have to do some testing and see what hardware isn't compatible with this method. I am using an HTC Evo for testing and so far so good.
When querying the Instances table, use Instances.EVENT_ID to get the identifier for the event you want to edit, instead of Instances._ID.
I am currently making an app which works with images. I need to implement functionality where the user picks a file stored on the SD card. Once they pick the picture (using the Android gallery), the the file-location of the image will be sent to another Activity, where other work will be done upon it.
I have seen similar posts here on SO, but none to answer my question specifically. Basically this is the code I am doing when the user clicks the "Load a Picture" button:
// Create a new Intent to open the picture selector:
Intent loadPicture = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
// To start it, run the startActivityForResult() method:
startActivityForResult(loadPicture, SELECT_IMAGE);
From that code, I then have a onActivityResult() method to listen to the call-back:
// If the user tried to select an image:
if(requestCode == SELECT_IMAGE)
{
// Check if the user actually selected an image:
if(resultCode == Activity.RESULT_OK)
{
// This gets the URI of the image the user selected:
Uri selectedImage = data.getData();
// Create a new Intent to send to the next Activity:
Intent i = new Intent(currentActivty.this, nextActivity.class);
// ----------------- Problem Area -----------------
// I would like to send the filename to the Intent object, and send it over.
// However, the selectedImage.toString() method will return a
// "content://" string instead of a file location. How do I get a file
// location from that URI object?
i.putExtra("PICTURE_LOCATION", selectedImage.toString());
// Start the activity outlined with the Intent above:
startActivity(i);
As the code above states, the uri.toString() will return a content:// string instead of the file location of the selected picture. How do I obtain the file location?
Note: Another possible solution is to send over the content:// string and convert that into a Bitmap (which is what happens in the next Activity). However, I don't know how to do that.
I have found the answer to my own question. After doing some more searching, I finally stumbled upon a post here on SO which asks the same question here: android get real path by Uri.getPath().
Unfortunately, the answer has a broken link. After some Google searching, I found the correct link to the site here: http://www.androidsnippets.org/snippets/130/ (I have verified that this code does indeed work.)
However, I decided to take a different route. Since my next Activity is using an ImageView to display the picture, I am instead going to use the Uri content string for all methods that link to the next Activity.
In the next Activity, I am using the ImageView.setImageUri() method.
Here is the code I am doing in the next Activity to display the picture from the content:// string:
// Get the content string from the previous Activity:
picLocation = getIntent().getStringExtra("PICTURE_LOCATION");
// Instantiate the ImageView object:
ImageView imageViewer = (ImageView)findViewById(R.id.ImageViewer);
// Convert the Uri string into a usable Uri:
Uri temp = Uri.parse(picLocation);
imageViewer.setImageURI(temp);
I hope that this question and answer will be helpful to future Android developers.
Here's another answer that I hope someone finds useful:
You can do this for any content in the MediaStore. In my app, I have to get the path from URIs and get the URI from paths. The former:
/**
* Gets the corresponding path to a file from the given content:// URI
* #param selectedVideoUri The content:// URI to find the file path from
* #param contentResolver The content resolver to use to perform the query.
* #return the file path as a string
*/
private String getFilePathFromContentUri(Uri selectedVideoUri,
ContentResolver contentResolver) {
String filePath;
String[] filePathColumn = {MediaColumns.DATA};
Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
The latter (which I do for videos, but can also be used for Audio or Files or other types of stored content by substituting MediaStore.Audio (etc) for MediaStore.Video:
/**
* Gets the MediaStore video ID of a given file on external storage
* #param filePath The path (on external storage) of the file to resolve the ID of
* #param contentResolver The content resolver to use to perform the query.
* #return the video ID as a long
*/
private long getVideoIdFromFilePath(String filePath,
ContentResolver contentResolver) {
long videoId;
Log.d(TAG,"Loading file " + filePath);
// This returns us content://media/external/videos/media (or something like that)
// I pass in "external" because that's the MediaStore's name for the external
// storage on my device (the other possibility is "internal")
Uri videosUri = MediaStore.Video.Media.getContentUri("external");
Log.d(TAG,"videosUri = " + videosUri.toString());
String[] projection = {MediaStore.Video.VideoColumns._ID};
// TODO This will break if we have no matching item in the MediaStore.
Cursor cursor = contentResolver.query(videosUri, projection, MediaStore.Video.VideoColumns.DATA + " LIKE ?", new String[] { filePath }, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(projection[0]);
videoId = cursor.getLong(columnIndex);
Log.d(TAG,"Video ID is " + videoId);
cursor.close();
return videoId;
}
Basically, the DATA column of MediaStore (or whichever sub-section of it you're querying) stores the file path, so you use what you know to look up the DATA, or you query on the DATA field to select the content you care about.