I am trying to use a cursor with a managed query to filter media content on the device
String[] dirs = new String[] {"%"+ dir + "%"};
String[] musicdata = { BaseColumns._ID,
MediaColumns.DATA,
MediaColumns.DISPLAY_NAME,
MediaColumns.SIZE };
musiccursor = getActivity().getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
musicdata,
MediaColumns.DATA + " like ? ",
dirs,
MediaColumns.DATA + " asc");
The where clause of the query uses a directory that is passed to it in order to filter for music within that folder and sub folders.
What I would like to do is also include a "not like" within the same query.
The reason for doing this is that the user has the ability to exclude a folder from a list view, and that is stored in an array and written to a file in order to retain the selection. I would like the cursor query to take these exclusions into account whilst still being linked to the folder that they pass through to it.
Thanks in advance!
In answer to my own query, here is the code:
//Build the where clause
StringBuilder where = new StringBuilder();
//first add in all values from the exclusion array
for (int i = 0; i < excludedFolders.size(); i++) {
where.append(MediaColumns.DATA + " not like ? ");
where.append(" AND ");
}
//then add in the final like clause, e.g. the folder the user selected
where.append(MediaColumns.DATA + " like ? ");
//convert it to a string
String selection = where.toString();
System.out.println(selection);
////////////////////////////////////////////////////////////////////////////////////////////
//Build the arguments. the array is set to the size of the exlcusion list, plus 1 for the fodler selected
String[] dirs = new String[excludedFolders.size()+1];
System.out.println(excludedFolders.size()+1);
//first add in a value for each excluded folder
for (int i = 0; i < excludedFolders.size(); i++) {
dirs[i] = "%" + excludedFolders.get(i) + "%";
System.out.println(i + " " + dirs[i]);
}
//add the argument value for the like element of the query
dirs[excludedFolders.size()]="%"+ dir + "%";
System.out.println("excludedFolders.size() " + dirs[excludedFolders.size()]);
//start building the cursor
String[] musicdata = { BaseColumns._ID,
MediaColumns.DATA,
MediaColumns.DISPLAY_NAME,
MediaColumns.SIZE };
//run the query
musiccursor = getActivity().getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
musicdata,
selection,
dirs,
MediaColumns.DATA+ " asc");
//done
Related
I'm trying to get the information of my contacts using ContactsContract and what I need to do, is to get only the first name of the contact. I used ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME but this get the name and the last name too, and I only want the name.
I tried using ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME but instead of getting the name, this get a number.
I have not found an exact way to get only the first name of the contact. Any idea?
You haven't shared your code, but it sounds like you're querying over table Phone.CONTENT_URI and trying to get a field via StructuredName.GIVEN_NAME.
That's not possible, as Phone.CONTENT_URI will only return phone rows, not StructuredName rows.
Here's code snippet to get all given-names from the Contacts DB:
String[] projection = new String[]{StructuredName.CONTACT_ID, StructuredName.GIVEN_NAME};
String selection = Data.MIMETYPE + "='" + StructuredName.CONTENT_ITEM_TYPE + "'";
Cursor c = getContentResolver().query(Data.CONTENT_URI, projection, selection, null, null);
DatabaseUtils.dumpCursor(c);
c.close();
UPDATE
Here's some sample code on how to query for multiple mimetypes in a single query.
In this example I created a mapping from to <given-name, phone> for each contact in the DB:
Map<Long, List<String>> contacts = new HashMap<Long, List<String>>();
String[] projection = {Data.CONTACT_ID, Data.DISPLAY_NAME, Data.MIMETYPE, StructuredName.GIVEN_NAME, Phone.NUMBER };
// select all rows of type "name" or "phone"
String selection = Data.MIMETYPE + " IN ('" + Phone.CONTENT_ITEM_TYPE + "', '" + StructuredName.CONTENT_ITEM_TYPE + "')";
Cursor cur = cr.query(Data.CONTENT_URI, projection, selection, null, null);
while (cur != null && cur.moveToNext()) {
long id = cur.getLong(0);
String name = cur.getString(1);
String mime = cur.getString(2); // type of row: phone / name
// get the existing <Given-name, Phone> list from the Map, or create a new one
List<String> infos;
if (contacts.containsKey(id)) {
infos = contacts.get(id);
} else {
infos = new ArrayList<String>(2);
contacts.put(id, infos);
}
// add either given-name or phone to the infos list
switch (mime) {
case Phone.CONTENT_ITEM_TYPE:
infos.set(1, cur.getString(4));
break;
case StructuredName.CONTENT_ITEM_TYPE:
infos.set(0, cur.getString(3));
break;
}
}
I want to create scanner that is showing only folders that have music in them. i know only how to create query for getting music from path using MediaStore
public static QueryTask buildFileQuery(String path, String[] projection)
{
// It would be better to use selectionArgs to pass path here, but there
// doesn't appear to be any way to pass the * when using it.
StringBuilder selection = new StringBuilder();
selection.append("_data GLOB ");
DatabaseUtils.appendEscapedSQLString(selection, path);
// delete the quotation mark added by the escape method
selection.deleteCharAt(selection.length() - 1);
selection.append("*' AND is_music!=0");
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
QueryTask result = new QueryTask(media, projection, selection.toString(), null, DEFAULT_SORT);
result.type = TYPE_FILE;
return result;
}
MediaStore.Audio.Media.DATA column contains filepath
Cursor c = getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DATA},
MediaStore.Audio.Media.IS_MUSIC + "=1",
null,
MediaStore.Audio.Media.DATA
);
if (c.moveToFirst()) {
do {
Log.d("T", c.getString(0) + " -- " + c.getString(1));
} while (c.moveToNext());
}
This should output the following:
Come On -- /storage/emulated/0/Music/The Rolling Stones/More Hot Rocks Big Hits & Fazed Cookies (Disc 2)/10 Come On.mp3
...
Once you have paths you can restore folder hierarchy
I basically can not accomplish this for over 2 weeks now.
I have posted a numerous amount of code over a few questions yet most of them got ignored so I won't flood this question with more code of my own that won't even be read.
How do I search the MediaStore with "LIKE" attributes using a String?
E.G. I type in Shoot To Thrill, I receive song ID with this code:
if(cursor.moveToFirst()){
while(cursor.moveToNext()){
String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String id = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media._ID));
test.setText(title +" " + artist + " " + id);
}
}
Here is something to begin with:
String[] projection = {
BaseColumns._ID,
MediaStore.Audio.Artists.ARTIST,
MediaStore.Audio.Media.TITLE
}
Cursor cursor = this.managedQuery(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, //uri
projection, //projection
//i dont know what to enter,
//i dont know what to enter,
MediaStore.Audio.Media.TITLE);
It's just normal SQL hiding behind the scenes. Normal LIKE operations should work just fine. You can use MediaStore.Audio.Media.TITLE + " LIKE \"%thrill%\"" just like any other SQL query.
String[] projection = { BaseColumns._ID,
MediaStore.Audio.Artists.ARTIST, MediaStore.Audio.Media.TITLE };
String where = MediaStore.Audio.Media.TITLE + " LIKE ?";
String[] params = new String[] { "%life%" };
Cursor q = managedQuery(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
projection, where, params, MediaStore.Audio.Media.TITLE);
try {
while (q.moveToNext()) {
Log.e("song", q.getString(1) + " " + q.getString(2));
}
} finally {
q.close();
}
I want to fetch All Contacts atleast with one phone Number, also I want all Phone Numbers and All emails for every Contact.
Current code :
// To get All Contacts having atleast one phone number.
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " > ?";
String[] selectionArgs = new String[] {"0"};
Cursor cu = applicationContext.getContentResolver().query(uri,
null, selection, selectionArgs, null);
// For getting All Phone Numbers and Emails further queries :
while(cu.moveToNext()){
String id = cu.getString(cu.getColumnIndex(ContactsContract.Contacts._ID));
// To get Phone Numbers of Contact
Cursor pCur = context.getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
new String[]{id}, null);
// To get Email ids of Contact
Cursor emailCur = context.getContentResolver().query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
new String[]{id}, null);
// Iterate through these cursors to get Phone numbers and Emails
}
If there are more than 1000 contacts in my Device, it is taking too much time. How can I get All Data in single query, rather than doing two additional queries for each contact?
Or is there any other way to optimize?
Thank you in Advance.
ICS: When you query from Data.CONTENT_URI you have all the rows from the associated Contact already joined - i.e. this would work:
ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
Data.HAS_PHONE_NUMBER + "!=0 AND (" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?)",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID);
while (c.moveToNext()) {
long id = c.getLong(c.getColumnIndex(Data.CONTACT_ID));
String name = c.getString(c.getColumnIndex(Data.DISPLAY_NAME));
String data1 = c.getString(c.getColumnIndex(Data.DATA1));
System.out.println(id + ", name=" + name + ", data1=" + data1);
}
If you are targeting 2.3 you need to account for the fact that HAS_PHONE_NUMBER is not available through the joins used when querying Data.
Fun.
This could, for instance, be solved either by skipping your requirement that the contact must have a phone number and instead settle for "any contact with at least a phone number or an e-mail address":
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID);
If that is not an option you can always go for a horribly hacky sub-select:
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
"(" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?) AND " +
Data.CONTACT_ID + " IN (SELECT " + Contacts._ID + " FROM contacts WHERE " + Contacts.HAS_PHONE_NUMBER + "!=0)",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE}, Data.CONTACT_ID);
or solve it by using two Cursors:
Cursor contacts = resolver.query(Contacts.CONTENT_URI,
null, Contacts.HAS_PHONE_NUMBER + " != 0", null, Contacts._ID + " ASC");
Cursor data = resolver.query(Data.CONTENT_URI, null,
Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID + " ASC");
int idIndex = contacts.getColumnIndexOrThrow(Contacts._ID);
int nameIndex = contacts.getColumnIndexOrThrow(Contacts.DISPLAY_NAME);
int cidIndex = data.getColumnIndexOrThrow(Data.CONTACT_ID);
int data1Index = data.getColumnIndexOrThrow(Data.DATA1);
boolean hasData = data.moveToNext();
while (contacts.moveToNext()) {
long id = contacts.getLong(idIndex);
System.out.println("Contact(" + id + "): " + contacts.getString(nameIndex));
if (hasData) {
long cid = data.getLong(cidIndex);
while (cid <= id && hasData) {
if (cid == id) {
System.out.println("\t(" + cid + "/" + id + ").data1:" +
data.getString(data1Index));
}
hasData = data.moveToNext();
if (hasData) {
cid = data.getLong(cidIndex);
}
}
}
}
I went through the exact same problem. Since then I build my own solution which is inspired from this post yet a bit different. Now I'd like to share it as my first StackOverFlow answer :-)
Its quite similar to the double cursor approach suggested by Jens. The idea is to
1- fetch relevant contact from the Contacts table
2- fetch relevant Contacts information (mail, phone, ...)
3- combine these results
The "relevant" is up to you of course but the important point is the performance !
Besides, I'm sure other solutions using well suited SQL query might as well do the job but here I only want to use the Android ContentProvider
Here is the code :
Some constants
public static String CONTACT_ID_URI = ContactsContract.Contacts._ID;
public static String DATA_CONTACT_ID_URI = ContactsContract.Data.CONTACT_ID;
public static String MIMETYPE_URI = ContactsContract.Data.MIMETYPE;
public static String EMAIL_URI = ContactsContract.CommonDataKinds.Email.DATA;
public static String PHONE_URI = ContactsContract.CommonDataKinds.Phone.DATA;
public static String NAME_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Data.DISPLAY_NAME_PRIMARY : ContactsContract.Data.DISPLAY_NAME;
public static String PICTURE_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Contacts.PHOTO_THUMBNAIL_URI : ContactsContract.Contacts.PHOTO_ID;
public static String MAIL_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
public static String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
1 Contact
Here I require that the Contacts must have DISPLAY_NAME free of "#" and that their informations match a given string (these requirement can of course be modified). The result of the following method is the first cursor :
public Cursor getContactCursor(String stringQuery, String sortOrder) {
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
Logger.e(TAG, "ContactCursor search has started...");
Long t0 = System.currentTimeMillis();
Uri CONTENT_URI;
if (stringQuery == null)
CONTENT_URI = ContactsContract.Contacts.CONTENT_URI;
else
CONTENT_URI = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(stringQuery));
String[] PROJECTION = new String[]{
CONTACT_ID_URI,
NAME_URI,
PICTURE_URI
};
String SELECTION = NAME_URI + " NOT LIKE ?";
String[] SELECTION_ARGS = new String[]{"%" + "#" + "%"};
Cursor cursor = sContext.getContentResolver().query(CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, sortOrder);
Long t1 = System.currentTimeMillis();
Logger.e(TAG, "ContactCursor finished in " + (t1 - t0) / 1000 + " secs");
Logger.e(TAG, "ContactCursor found " + cursor.getCount() + " contacts");
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
return cursor;
}
This query is quite performant as you'll see !
2 Contact Details
Now let's fetch Contact informations. At this point, I dont make any link between the already fetched Contact and the retrieved information : I just fetch all informations form the Data table... Yet, to avoid useless info I still require DISPLAY_NAMES free of "#" and since I'm interested in email and phone I require that the data MIMETYPE to be either MAIL_TYPE or PHONE_TYPE (see Constants). Here is the code :
public Cursor getContactDetailsCursor() {
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
Logger.e(TAG, "ContactDetailsCursor search has started...");
Long t0 = System.currentTimeMillis();
String[] PROJECTION = new String[]{
DATA_CONTACT_ID_URI,
MIMETYPE_URI,
EMAIL_URI,
PHONE_URI
};
String SELECTION = ContactManager.NAME_URI + " NOT LIKE ?" + " AND " + "(" + MIMETYPE_URI + "=? " + " OR " + MIMETYPE_URI + "=? " + ")";
String[] SELECTION_ARGS = new String[]{"%" + "#" + "%", ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE};
Cursor cursor = sContext.getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
SELECTION,
SELECTION_ARGS,
null);
Long t1 = System.currentTimeMillis();
Logger.e(TAG, "ContactDetailsCursor finished in " + (t1 - t0) / 1000 + " secs");
Logger.e(TAG, "ContactDetailsCursor found " + cursor.getCount() + " contacts");
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
return cursor;
}
Once again you will see that this query is quite fast...
3 Combining
Now let's combine both Contact and their respective informations. The idea is to use HashMap(Key, String) where Key is the Contact id and String is whatever you like (name, email, ...).
First, I run through the Contact cursor (which is alphabetically ordered) and store names and picture uri in two different HashMap. Note also that I store all Contact id's in a List in the very same order that Contacts appear in the cursor. Lets call this list contactListId
I do the same for the Contact informations (mail and email). But now I take care of the correlation between the two cursor : if the CONTACT_ID of an email or phone does not appear in contactListId it is put aside. I check also if the email has already been encountered. Notice that this further selection can introduce asymmetries between the Name/Picture content and the Email/Phone HashMap content but don't worry.
Eventually, I run over the contactListId list and build a list of Contact object taking care of the fact that : a contact must have information (keySet condition) and that the contact must have at least a mail or an email (the case where mail == null && phone == null may appear if the contact is a Skype contact for instance).
And here is the code :
public List<Contact> getDetailedContactList(String queryString) {
/**
* First we fetch the contacts name and picture uri in alphabetical order for
* display purpose and store these data in HashMap.
*/
Cursor contactCursor = getContactCursor(queryString, NAME_URI);
List<Integer> contactIds = new ArrayList<>();
if (contactCursor.moveToFirst()) {
do {
contactIds.add(contactCursor.getInt(contactCursor.getColumnIndex(CONTACT_ID_URI)));
} while (contactCursor.moveToNext());
}
HashMap<Integer, String> nameMap = new HashMap<>();
HashMap<Integer, String> pictureMap = new HashMap<>();
int idIdx = contactCursor.getColumnIndex(CONTACT_ID_URI);
int nameIdx = contactCursor.getColumnIndex(NAME_URI);
int pictureIdx = contactCursor.getColumnIndex(PICTURE_URI);
if (contactCursor.moveToFirst()) {
do {
nameMap.put(contactCursor.getInt(idIdx), contactCursor.getString(nameIdx));
pictureMap.put(contactCursor.getInt(idIdx), contactCursor.getString(pictureIdx));
} while (contactCursor.moveToNext());
}
/**
* Then we get the remaining contact information. Here email and phone
*/
Cursor detailsCursor = getContactDetailsCursor();
HashMap<Integer, String> emailMap = new HashMap<>();
HashMap<Integer, String> phoneMap = new HashMap<>();
idIdx = detailsCursor.getColumnIndex(DATA_CONTACT_ID_URI);
int mimeIdx = detailsCursor.getColumnIndex(MIMETYPE_URI);
int mailIdx = detailsCursor.getColumnIndex(EMAIL_URI);
int phoneIdx = detailsCursor.getColumnIndex(PHONE_URI);
String mailString;
String phoneString;
if (detailsCursor.moveToFirst()) {
do {
/**
* We forget all details which are not correlated with the contact list
*/
if (!contactIds.contains(detailsCursor.getInt(idIdx))) {
continue;
}
if(detailsCursor.getString(mimeIdx).equals(MAIL_TYPE)){
mailString = detailsCursor.getString(mailIdx);
/**
* We remove all double contact having the same email address
*/
if(!emailMap.containsValue(mailString.toLowerCase()))
emailMap.put(detailsCursor.getInt(idIdx), mailString.toLowerCase());
} else {
phoneString = detailsCursor.getString(phoneIdx);
phoneMap.put(detailsCursor.getInt(idIdx), phoneString);
}
} while (detailsCursor.moveToNext());
}
contactCursor.close();
detailsCursor.close();
/**
* Finally the contact list is build up
*/
List<Contact> contacts = new ArrayList<>();
Set<Integer> detailsKeySet = emailMap.keySet();
for (Integer key : contactIds) {
if(!detailsKeySet.contains(key) || (emailMap.get(key) == null && phoneMap.get(key) == null))
continue;
contacts.add(new Contact(String.valueOf(key), pictureMap.get(key), nameMap.get(key), emailMap.get(key), phoneMap.get(key)));
}
return contacts;
}
The Contact object definition is up to you.
Hope this will help and thanks for the previous post.
Correction/Improvement
I forgot to check the phone key set : it should rather looks like
!mailKeySet.contains(key)
replaced by
(!mailKeySet.contains(key) && !phoneKeySet.contains(key))
with the phone keySet
Set<Integer> phoneKeySet = phoneMap.keySet();
I why not add an empty contact cursor check like :
if(contactCursor.getCount() == 0){
contactCursor.close();
return new ArrayList<>();
}
right after the getContactCursor call
I'm trying to make a query where I get data from two tables but it won't show me any result. I know there is a result because in SQLite3 it displays at least one, e.g.
sqlite> select eventthemename, eventtypename from event_type, event_theme
...> where event_type.eventthemes = event_theme._id
...> and event_type._id = '2';
Tribal | Dance
I'm using a content provider. Does anyone have an idea on how to make this?
Your question isn't really clear (you should include some of your code!), so I might not answer your question as you hoped. But following is a simple example I have where I join two tables in one query:
private static final String QUERY_ALL_COURSES_WITH_SEMESTER =
"SELECT * FROM Course JOIN Semester ON semesterId = courseSemesterId";
public Course getCourseByCodeAndSemester(String courseCode, Semester semester)
{
Course result = null;
String whereClause = " WHERE courseCode = '" + courseCode.toUpperCase() + "'"
+ " AND semesterId = " + semester.getInternalId();
Cursor cursor = db.rawQuery(QUERY_ALL_COURSES_WITH_SEMESTER + whereClause, null);
if (cursor.moveToFirst())
{
result = Course.createFromCursor(cursor);
}
cursor.close();
return result;
}
Note that there are probably many things that could be optimized in this function, but it illustrates how it can be done.
As I'm using content providers I ended doing something not quite fast but that works:
themes = managedQuery(
EventTheme.CONTENT_URI,
PROJECTIONTHEMES,
EventTheme.EVENTTYPE + "= ?",
new String[] {eventType},
EventTheme.DEFAULT_SORT_ORDER);
String[] from = new String[] {
Event._ID,
Event.NAME,
Event.STARTDATE,
Event.ENDDATE,
PointOfInterest.POINTOFINTERESTNAME
};
int[] to = new int[] {
R.id.event_id,
R.id.name,
R.id.start_date,
R.id.end_date,
R.id.location_name
};
while (themes.getPosition() < themes.getCount() - 1) {
themes.moveToNext();
eventTheme = themes.getString(themes.getColumnIndexOrThrow(EventTheme._ID));
events = managedQuery(
Event.CONTENT_URI,
PROJECTIONEVENTS,
Event.EVENTTHEME + "= ?",
new String [] { eventTheme } ,
Event.DEFAULT_SORT_ORDER);
}