Media files information not updating on ContentResolver.query - android

I am writing an android app that allows me to update media files.
I have an AsyncTask that (among other things) scans the device for media files.
The code below is the method used to get all the media files and create a list of FileInfo (my class, holding basic meta data of files).
The problem is that the data received from the Cr().query is always the same and not bringing the latest updates.
I have looked at this similar issue but you can see that my code does the solution recommended there, yet I still get the old data rather than the refreshed.
Took me a while to figure out this is the problem, but I now see it clearly in the output of the log statement Log.d("scanLocalFiles", "" + fileInfos.size() + "|" + data + ":" + size );
that a file that I know that has been changed, still appears with its original size.
(I know it has been changed because I see the updated one on ES-FileExplorer...)
Any suggestion of what might be going on?
Thanks.
Code below:
private List<FileInfo> scanLocalFiles(Uri... uris)
{
List<FileInfo> fileInfos = new ArrayList<FileInfo>();
String[] projection = new String[]{
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.MIME_TYPE
};
for (int u=0; u < uris.length; u++)
{
Uri uri = uris[u];
//Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cur = Cr().query(uri,
projection, // Which columns to return
"", // Which rows to return (all rows)
null, // Selection arguments (none)
"" // Ordering
);
if(cur!=null && cur.moveToFirst())
{
try
{
int idColumn = cur.getColumnIndex(projection[0]);
int dataColumn = cur.getColumnIndex(projection[1]);
int sizeColumn = cur.getColumnIndex(projection[2]);
int mimeColumn = cur.getColumnIndex(projection[3]);
do
{
Uri id = Uri.withAppendedPath(uri, cur.getString(idColumn));
String data = cur.getString(dataColumn);
long size = cur.getLong(sizeColumn);
String mime = cur.getString(mimeColumn);
Log.d("scanLocalFiles", "" + fileInfos.size() + "|" + data + ":" + size );
fileInfos.add(new FileInfo(id, data, size, mime));
} while (cur.moveToNext());
}
catch (Exception ex)
{
ex.printStackTrace();
}
finally
{
cur.close();
}
}
}
return fileInfos;
}

Related

Retrieving images that I manually copied to phone/emulator from windows desktop

I'm using MediaStore.Images.Media.EXTERNAL_CONTENT_URI with a filter on MediaStore.MediaColumns.DATA and a Cursor to retrieve the images. I need to keep the image path where the images are stored, (which are in sub-folders) because I am using the image path to find the correct images to display them with their parent inventory item. I've tried many different ways with no success except using the MediaStore. This is working. The problem is; the MediaStore.MediaColumn.DATA is deprecated (sigh). So I need to refactor out the deprecated field use.
This is an inventory application where each inventory item has a "Source", "Order Number", "OrderItemNumber" plus other data. I stored the images on the sdCard in a folder I created under "downloads" called "InventoryImages". Under this folder are sub-folders (actual source name), (actual Order Number), (actual Order Item Number). Within the "OrderItemNumber" folder are several images, all for this specific combination.
The Inventory Item data is stored in a sqlite db. ((preloaded)(working on the maintenance pieces)). But I'm really trying to avoid storing the image (blob) in the db. But I am open to any and all suggestions for an alternative. I'd rather not have the images deleted if the application is uninstalled. I currently have 104 inventory items, each with average 4 images.
Again, the included fragment is working, I just need an alternative to the DATA column which the value is the image path.
Fragment:
public class ListViewFragment extends Fragment
implements IAdapterCallback
{
public ArrayList<ImageInfo> getAllMediaStoreImages()
{
ArrayList<ImageInfo> allImages = new ArrayList<>();
String inventoryDirectory;
if(Build.FINGERPRINT.contains("generic"))
inventoryDirectory = "Inventory"; // <-- use this for the emulator
else
inventoryDirectory = "InventoryImages"; // <-- use this for my phone
Uri uri =
MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String selection =
MediaStore.MediaColumns.DATA + " LIKE ? ";
String[] selectionArgs = new String[]{"%" +
inventoryDirectory + "%"};
Cursor cursor =
this.getActivity().getContentResolver().query(uri,
null, selection, selectionArgs, null);
String[] names = cursor.getColumnNames();
try {
if (cursor != null) {
while (cursor.moveToNext())
{
String filePath =
cursor.getString(cursor.getColumnIndex((MediaStore.MediaColumns.*DATA*)));
String fileName =
cursor.getString(cursor.getColumnIndex((MediaStore.MediaColumns.TITLE)));
int inventoryDirectoryLoc =
filePath.indexOf(inventoryDirectory) +
inventoryDirectory.length() + 1;
String matchPath =
filePath.substring(inventoryDirectoryLoc,
filePath.lastIndexOf('/'));
allImages.add(new
ImageInfo(filePath, fileName, matchPath));
}
}
return allImages;
}
catch (Exception x)
{
x.printStackTrace();
return null;
}
finally
{
if(cursor != null)
cursor.close();
}
}
This worked:
public ArrayList<File> getItemInventoryImages(String sourceSpecificPath)
{
String sourcePath = Environment.getExternalStorageDirectory().toString() + "/Download/" + sourceSpecificPath;
File file = new File(sourcePath);
if(!file.isDirectory()) {return null;}
if(!file.canRead() {return null;}
File[] files = file.listFiles();
return files;
}
Now I can feed the File array to the Adapter and use Glide to display them.

recyclerView custom adapter binditem gets called when inserting into database

My app displays playlists where for each playlist I show albumart in recyclerview.
A custom adapter displays the rows. It all works fine but when I add another playlist, using an asynctask, the display gets redrawn several times. I have checked that there are no adapter.notifydatasetchanged() calls. Stepping through the code I have discovered that it happens when there is a resolver.insert
In this example, the routine creates a new playlist, either for whole albums or tracks.
public void addTracksToPlaylist(Context context, String music_id,
long playlist_id, String nmode, int base) {
// feed it the album_id or track_id
Uri mediauri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = context.getContentResolver();
Uri exturi = MediaStore.Audio.Playlists.Members.getContentUri(
"external", playlist_id);
String[] projection = {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TRACK};
String where = MediaStore.Audio.Media.ALBUM_ID + " =?";
String orderBy = null;
if (nmode.equals(context.getString(R.string.track_mode))) {
where = MediaStore.Audio.Media._ID + "=?";
}
orderBy = MediaStore.Audio.Media.ARTIST + " ASC, "
+ MediaStore.Audio.Media.ALBUM + " ASC , " +
"CAST(" + (MediaStore.Audio.Media.TRACK) + " AS INTEGER) ASC";
String[] whereVal = {music_id};
Cursor c = resolver.query(mediauri, projection, where, whereVal,
orderBy);
if (c != null && c.moveToFirst()) {
int idColumn = c.getColumnIndex(MediaStore.Audio.Media._ID);
ContentValues values = new ContentValues();
boolean stamp = prefs.getstampSelected(context);
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String audio_id = c.getString(idColumn);
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base);
values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audio_id);
try {
resolver.insert(exturi, values);
} catch (Exception e) {
e.printStackTrace();
}
// String thistrack= track.getfullPathfromAudioId(context,audio_id);
// Log.i(TAG,"position : "+base +" Inserted track = "+ audio_id + " track "+thistrack+ " Album_id = "+ music_id);
base++;
if (stamp) {
try {
track.updateTrackModifiedDate(audio_id, context);
} catch (Exception e) {
e.printStackTrace();
}
}
}
c.close();
}
}
What can I do to prevent these actions, which happen in the background, affect the display of playlists. Or in other words, why/how is my recyclerView "active" as it appears to be aware of changes in the database.
Update:
From the Android Developers website:
The Loader will monitor for changes to the data, and report them to you through new calls here. You should not monitor the data yourself. For example, if the data is a Cursor and you place it in a CursorAdapter, use the CursorAdapter(android.content.Context, android.database.Cursor, int) constructor without passing in either FLAG_AUTO_REQUERY or FLAG_REGISTER_CONTENT_OBSERVER (that is, use 0 for the flags argument). This prevents the CursorAdapter from doing its own observing of the Cursor, which is not needed since when a change happens you will get a new Cursor throw another call here.
the monitoring and reporting back by the loader manifests itself in onLoadFinished calls which in my case reset the adapter.
As was stated in the comments, the Loader monitors changes in the underlying data. The solution was simple in the end.
I declare a boolean variable
private boolean processing=false;
Set the variable to true in the onPreExecute() and to false in onPostExecute() of the AsyncTask.
As a change fires the OnLoadFinished call I test for the value of processing and skip the code. Once completed, I call getLoaderManager().restartLoader etc

How to query all contacts that belong to an account?

Background
Contacts on the address book can have an account data that's attached to them.
Each app can have an account, and then add its own information for the contact.
Apps such as Telegram, WhatsApp, Viber,... - all create an account that adds information and/or actions to contacts.
Here's an example of a contact that has both WhatsApp and Viber accounts for it:
The problem
I'm trying to figure out how to fetch all contacts that have a specified account.
Since WhatsApp is the most popular that I know of, my tests focus on it.
My problem is that some users claim what I did barely returns contacts, and some claim it doesn't show even a single one. It seems to usually work, and in my case it always worked, but something is probably not good on the code.
What I've tried
I got to make the next code, which to me seems to work, getting a map of phone-to-contact-info, of all WhatsApp contacts.
The idea is to get all possible information of WhatsApp contacts, vs all basic contacts data, and merge those that match the same lookup-key.
I tried to use a better query of joining, but I failed. Maybe it is possible too, and might be more efficient.
Here's the code:
/**
* returns a map of lookup-key to contact-info, of all WhatsApp contacts
*/
#NonNull
public HashMap<String, ContactInfo> getAllWhatsAppPhones(Context context) {
ContentResolver cr = context.getContentResolver();
final HashMap<String, ContactInfo> phoneToContactInfoMap = new HashMap<>();
final HashMap<String, String> whatsAppLookupKeyToPhoneMap = new HashMap<>();
final String phoneMimeType = Phone.CONTENT_ITEM_TYPE;
final Cursor whatsAppCursor;
whatsAppCursor = cr.query(Data.CONTENT_URI,
new String[]{Phone.NUMBER, Phone.LOOKUP_KEY},
Phone.MIMETYPE + " = ?", new String[]{WhatsAppStuff.WHATS_APP_MIME_TYPE}, null);
if (whatsAppCursor == null)
return phoneToContactInfoMap;
Cursor contactCursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
//null,
new String[]{
Contacts.LOOKUP_KEY, Contacts._ID, Contacts.PHOTO_THUMBNAIL_URI,
ContactsContract.Contacts.DISPLAY_NAME, // ContactsContract.CommonDataKinds.Phone.NUMBER,
},
"(" + Phone.MIMETYPE + " IS NULL OR " + Phone.MIMETYPE + " = '" + phoneMimeType + "') AND ("
+ ContactsContract.RawContacts.ACCOUNT_TYPE + " = 'com.google' OR " + ContactsContract.RawContacts.ACCOUNT_TYPE + " IS NULL)",
null, null);
if (contactCursor == null) {
whatsAppCursor.close();
return phoneToContactInfoMap;
}
int progress = 0;
final int phoneNumberIdx = whatsAppCursor.getColumnIndex(Phone.NUMBER);
final int lookupKeyIdx = whatsAppCursor.getColumnIndex(Phone.LOOKUP_KEY);
while (whatsAppCursor.moveToNext()) {
final String phoneNumberValue = whatsAppCursor.getString(phoneNumberIdx);
final int endIndex = phoneNumberValue.indexOf("#");
if (endIndex < 0)
continue;
String lookupKey = whatsAppCursor.getString(lookupKeyIdx);
final String phone = phoneNumberValue.substring(0, endIndex);
if (!phone.isEmpty() && StringUtil.isAllDigits(phone)) {
//Log.d("AppLog", "whatsApp phone:" + phone + " " + lookupKey);
whatsAppLookupKeyToPhoneMap.put(lookupKey, phone);
}
if (markedToCancel != null && markedToCancel.get()) {
whatsAppCursor.close();
contactCursor.close();
return phoneToContactInfoMap;
}
if (progressListener != null)
progressListener.onProgressUpdate(progress++, maxProgress);
}
whatsAppCursor.close();
if (whatsAppLookupKeyToPhoneMap.isEmpty())
return phoneToContactInfoMap;
//Log.d("AppLog", "getting info about whatsapp contacts");
final int idColIdx = contactCursor.getColumnIndex(Contacts._ID);
final int displayNameColIdx = contactCursor.getColumnIndex(Contacts.DISPLAY_NAME);
final int lookupKeyColIdx = contactCursor.getColumnIndex(Contacts.LOOKUP_KEY);
final int photoColIdx = contactCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
while (contactCursor.moveToNext()) {
String lookupKey = contactCursor.getString(lookupKeyColIdx);
String phoneNumber = whatsAppLookupKeyToPhoneMap.get(lookupKey);
if (phoneNumber == null)
continue;
ContactInfo contactInfo = new ContactInfo();
contactInfo.lookupKey = lookupKey;
contactInfo.displayName = contactCursor.getString(displayNameColIdx);
contactInfo.photoThumbUriStr = contactCursor.getString(photoColIdx);
contactInfo.whatsAppPhoneNumber = phoneNumber;
contactInfo.contactId = contactCursor.getLong(idColIdx);
phoneToContactInfoMap.put(phoneNumber, contactInfo);
if (markedToCancel != null && markedToCancel.get()) {
contactCursor.close();
return phoneToContactInfoMap;
}
if (progressListener != null)
progressListener.onProgressUpdate(progress++, maxProgress);
}
contactCursor.close();
return phoneToContactInfoMap;
}
The question
As I wrote, the above code only usually works.
How come it only usually works? What's missing to fix it?
Should I use Contacts.getLookupUri instead of lookup key? If so, how should I change the code above to use it instead?
I tried to use a URI instead of a lookup-key, but then it didn't find any of them inside the normal contacts.
The main issue I see that can explain why users won't see results from your code, is that you're assuming all the contacts are stored on a Google account.
While this is the default behavior in some devices, it's not the default on all devices, also, users can freely change their contacts storage to any other location (yahoo contacts, MS exchange, phone-only (unsynced), etc.)
Having that said, if your only requirement is to
fetch all contacts that have a specified account.
I think that's a much better alternative then your 2 queries (one of which runs over all contacts, not just the required ones):
// Uri to query contacts that have a RawContact in the desired account
final Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, whatsappAccountName);
builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, whatsappAccountType);
Uri uri = builder.build();
String[] projection = new String[]{ Contacts.LOOKUP_KEY, Contacts._ID Contacts.DISPLAY_NAME }; // add more if needed
// boo-yaa!
Cursor cur = cr.query(uri, projection, null, null, null);

Getting all audio files in a directory on android?

I'm using the following code to get all audio files (actually their ids so I can play them later with MediaPlayer) from a directory on my android device:
ContentResolver cr = context.getContentResolver();
Uri audioUri = MediaStore.Audio.Media.getContentUriForPath(dir.getPath()); //dir is a File object representing the dir I'm looking in
Cursor audioCursor = cr.query(audioUri, null, null, null, null);
if (audioCursor != null && audioCursor.moveToFirst()) {
int idColumn = audioCursor.getColumnIndex(MediaStore.Audio.Media._ID);
List<Long> fileIds = new ArrayList<>();
do {
long id = audioCursor.getLong(idColumn);
fileIds.add(id);
} while (audioCursor.moveToNext());
return fileIds;
} else {
return null;
}
For some reason however, it returns an array with 2904 ids for any dir I give it. I'm guessing there are 2904 audio files on my device (when I use Uri audioUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI it also returns 2904 ids). So it seems I'm using getContentUriForPath incorrectly, however the android reference doesn't give any information.
How should this work?
Some more info about what I'm trying to do (if it makes any difference in your answer): I'm still working on the code, trying out different things. Ultimately I want to play the files with MediaPlayer. I only need the audio files in that folder directly, ignoring any subfolders. They should also be ordered by filename.
You can't use getContentUriForPath with a folder, there isn't any content registered at this path. In this case getContentUriForPath return the standard EXTERNAL_CONTENT_URI, for this reason you get all the ids available.
This return all the files contained in a particular folder (e.g. /storage/emulated/0/Music/), ignoring any subfolders and ordered by filename:
Cursor audioCursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,
MediaStore.Audio.Media.DATA + " LIKE ? AND " + MediaStore.Audio.Media.DATA + " NOT LIKE ?",
new String[]{path + "%", path + "%/%"},
MediaStore.Audio.Media.DISPLAY_NAME + " ASC");
if (audioCursor != null && audioCursor.moveToFirst()) {
int idColumn = audioCursor.getColumnIndex(MediaStore.Audio.Media._ID);
List<Long> fileIds = new ArrayList<>();
do {
long id = audioCursor.getLong(idColumn);
fileIds.add(id);
} while (audioCursor.moveToNext());
return fileIds;
} else {
return null;
}

Retrieving a contacts notes

I'm still very new to android app development, and I have hit a problem I hope u can help me with...
I am trying to retrieve any "Notes" stored against a contact within my phone. I want to check if a contact (current caller) has any notes associated to them, and then either display the contents or do some action depending on the content of them etc...
I have tried the code below as a test to see if the data is retrieved anywhere within my cursor, but although it retrieves some data, I can't see the content of a note - so I guess I'm in the wrong place!
Where I have declared contentID, this is a result of doing a lookup with the code below, and using the id that is recieved.
Uri lookupUri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(inCommingNumber));
String[] mPhoneNumberProjection = { Phone._ID, Phone.NUMBER, Phone.DISPLAY_NAME};
Cursor cur = getContentResolver().query(lookupUri , mPhoneNumberProjection, null, null, null);
private boolean hasNote(String contactID){
Boolean noteFound = false;
Cursor noteCur = getContentResolver().query(ContactsContract.Data.CONTENT_URI, null, null, null, null);
if(noteCur.moveToFirst()) {
int numCols = noteCur.getColumnCount();
if(numCols > 0){
for(int x = 0; x < numCols; x++){
Log.d(APP_TAG, " column " + x + " contains: " + noteCur.getString(x));
}
}
noteFound = true;
} else{
Log.d(APP_TAG, "No Note retrieved");
}
noteCur.close();
return noteFound;
}
Apologies, I cant seem to get the code to display properly in this post!!
Any help would be great
I have tried various things, but can't seem to be able to get this data. I have created the note by simply adding it through the normal contact manager. I'm using Android 2.2.
The following code would display the first note for all contacts on your phone:
Cursor contactsCursor = null;
try {
contactsCursor = getContentResolver().query(RawContacts.CONTENT_URI,
new String [] { RawContacts._ID },
null, null, null);
if (contactsCursor != null && contactsCursor.moveToFirst()) {
do {
String rawContactId = contactsCursor.getString(0);
Cursor noteCursor = null;
try {
noteCursor = getContentResolver().query(Data.CONTENT_URI,
new String[] {Data._ID, Note.NOTE},
Data.RAW_CONTACT_ID + "=?" + " AND "
+ Data.MIMETYPE + "='" + Note.CONTENT_ITEM_TYPE + "'",
new String[] {rawContactId}, null);
if (noteCursor != null && noteCursor.moveToFirst()) {
String note = noteCursor.getString(noteCursor.getColumnIndex(Note.NOTE));
Log.d(APP_TAG, "Note: " + note);
}
} finally {
if (noteCursor != null) {
noteCursor.close();
}
}
} while (contactsCursor.moveToNext());
}
} finally {
if (contactsCursor != null) {
contactsCursor.close();
}
}
If you are storing some sort of structured data on the note to take actions on using the free form note field may not be the best approach. With the new contacts contract model you are able to attach your own data fields (in ContactsContract.Data) to a contact just give them your own unique mimetype.

Categories

Resources