I'm implementing my own SearchRecentSuggestionsProvider and everything's working except one thing: I can't get the device search to display icons for the results. I'd like to display images from my application's data folder (located at /{sdcard}/Android/data/package_name/files/)
According to the documentation, it's achievable by using SearchManager.SUGGEST_COLUMN_ICON_1, and it apparently supports a number of schemes, including ContentResolver.SCHEME_FILE, which is file. Here's a quote from the official docs:
Column name for suggestions cursor. Optional. If your cursor includes this column, then all suggestions will be provided in a format that includes space for two small icons, one at the left and one at the right of each suggestion. The data in the column must be a resource ID of a drawable, or a URI in one of the following formats:
content (SCHEME_CONTENT)
android.resource (SCHEME_ANDROID_RESOURCE)
file (SCHEME_FILE)
I've tried a number of obvious things, including manual creation of the file URI and automated creation using Uri.Builder(). None of this worked.
I also found someone else asking about the same thing on Google Groups, and it's sadly unanswered: https://groups.google.com/forum/#!topic/android-developers/MJj7GIaONjc
Does anyone have any experience in getting the device search to display local images from the device?
UPDATE (December 15):
I've just tried using the ContentProvider with a SearchView as the searchable info, and it works exactly as expected - including the cover art images. Still, global search doesn't show it...
I had a similar issue and could not make the other solutions work. I finally substituted the cursor with a new one containing the data I needed in query().
public class RecentQueriesProvider extends SearchRecentSuggestionsProvider {
public final static String AUTHORITY = "com.package.RecentQueriesProvider";
public final static int MODE = DATABASE_MODE_QUERIES;
public RecentQueriesProvider() {
setupSuggestions(AUTHORITY, MODE);
}
// We override query() to replace the history icon in the recent searches suggestions. We create a new cursor
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor superCursor = super.query(uri, projection, selection, selectionArgs, sortOrder);
Uri iconUri = Uri.parse("android.resource://" + getContext().getPackageName() + "/drawable/ic_action_time");
MatrixCursor newCursor = new MatrixCursor(superCursor.getColumnNames());
superCursor.moveToFirst();
while (superCursor.moveToNext()){
newCursor.addRow(new Object[]{
superCursor.getInt(superCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT)),
iconUri,
superCursor.getString(superCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)),
superCursor.getString(superCursor.getColumnIndex("suggest_intent_query")),
superCursor.getInt(superCursor.getColumnIndex("_id"))
});
}
return newCursor;
}
}
One way is to copy source code from android.content.SearchRecentSuggestionsProvider, place it in your class that extends ContentProvider, and customize setupSuggestions(String, int). Specifically, you would be changing this:
Uri uriFile = Uri.fromFile(new File("path/to/file"));
mSuggestionProjection = new String [] {
"0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
// Here, you would return a file uri: 'uriFile'
"'android.resource://system/"
+ com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+ SearchManager.SUGGEST_COLUMN_ICON_1,
"display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
"query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
"_id"
};
I prefer the following though. Extend SearchRecentSuggestionsProvider and override the query(...) method. Here, intercept SearchManager.SUGGEST_URI_PATH_QUERY and return a cursor.
public class SearchSuggestionProvider extends SearchRecentSuggestionsProvider {
private UriMatcher matcher;
private static final int URI_MATCH_SUGGEST = 1;
public SearchSuggestionProvider() {
super();
matcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add uri to return URI_MATCH_SUGGEST
matcher.addURI(SearchSuggestionProvider.class.getName(),
SearchManager.SUGGEST_URI_PATH_QUERY, URI_MATCH_SUGGEST);
setupSuggestions(SearchSuggestionProvider.class.getName(),
DATABASE_MODE_QUERIES);
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// special case for actual suggestions (from search manager)
if (matcher.match(uri) == URI_MATCH_SUGGEST) {
// File to use
File f = new File("/path/to/file");
Uri uriFile = Uri.fromFile(f);
final String[] PROJECTION = new String[] {
"_id",
"display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
"query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
"'" + uriFile + "'" + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1,
};
final Uri URI = Uri.parse("content://" +
SearchSuggestionProvider.class.getName() + "/suggestions");
// return cursor
return getContext().getContentResolver().query(
URI,
PROJECTION,
"display1 LIKE ?",
new String[] {selectionArgs[0] + "%"},
"date DESC");
}
// Let super method handle the query if the check fails
return super.query(uri, projection, selection, selectionArgs, sortOrder);
}
}
Related
I want to fetch file data from specific folder using MediaStore Query but returned cursor result is always null.
I have tried using MediaStore query
File file = new File(Environment.getExternalStorageDirectory() + "/myplayer/");
if (file.isDirectory())
{
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Video.Media._ID
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.DATA,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.SIZE, MediaStore.Video.Media.ALBUM
};
String selection = MediaStore.Video.Media.DATA + " like?";
String[] selectionArgs = new String[]{Environment.getExternalStorageDirectory() + "/myplayer/"};
Cursor cursor = ctx.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if ((cursor != null))
{
Log.d("trace cursor", "not null");
Log.d("trace count", "" + cursor.getCount());
}
}
According to following code, the count of cursor is always 0, but there are some video files in myplayer directory.
The problem is in the selection arguments. Append the ‘%’ character at the end, so it is as “myplayer/%”:
new String[]{Environment.getExternalStorageDirectory() + "/myplayer/%"};
This means to select anything that starts with the prefixed path, and has any string after “myplayer/”, which in this case are the file names.
Said that, consider that both the DATA field, and Environment.getExternalStorageDirectory() have been deprecated in Android Q (API 29). Hence, they will not work as expected in such version, and you should already try to find alternatives if you want your code to work in future Android versions, for example to use the BUCKET_DISPLAY_NAME instead.
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 have implemented update() of ContentProvider and notifying to observer using getContext().getContentResolver().notifyChange(uri, null);
my obvious need is that whenever just one row is effected i want to notify with row specific uri, but could not find way to do so.
an additional query like "select id where selectionArgs" can do this but this will be a foolish way.
onchange(boolean, uri) get complete uri instead of specific row, easy to understand that this is because ContentProvider.update() is sending the same.
some code for more clarity
update() method of MyContentProvider
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d("TAG", "update " + uri.getPath());
int count = 0;
switch (uriMatcher.match(uri)) {
case BOOKS:
count = booksDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(DATABASE_TABLE, values,
_ID + " = " + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count == 1) {
Cursor c = query(uri, new String[] { _ID }, selection, selectionArgs, null);
long rowId = Long.valueOf(c.getString(c.getColumnIndex(_ID)));
uri = ContentUris.withAppendedId(CONTENT_URI, rowId);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
i will update table some how like
getContentResolver().update(MyContentProvider.CONTENT_URI, values1, MyContentProvider._ID+"<?", new String[]{"3"}));
frankly saying, code has barely related to question, just trying to give you some context
In your provider method, just return the uri with the id appended
#Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "insert " + uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = URI_MATCHER.match(uri);
Uri returnUri;
switch (match) {
case MESSAGE: {
long _id = db.insert(MessageContract.MessageEntry.TABLE_NAME, null, values);
if (_id > 0)
returnUri = ContentUris.withAppendedId(MessageContract.MessageEntry.CONTENT_URI, _id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(returnUri, null);
return returnUri;
}
And register your observer with true for descendents.
getContentResolver().registerContentObserver(MessageContract.MessageEntry.CONTENT_URI, true, mContentObserver);
To get the id from a Uri you can use ContentUris.parseId(uri)
Unfortunately I'm not able to suggest easy solution (because I'm not aware of full code and updates You need to run), there's some ways we You could try (some of them I've implemented in mine applications):
Provide ids in ContentValues - this way looks not applicable for Your case and it needs loop with calls to notifyChange();
Provide specific Uri for requests with queries (only some specific apps needs many various queries in selection, usually it's much easier to include query parameter in Uri). After another part of the program get notification with that specific Uri it will be able to check if it's 'current item' was updated and act appropriately (e.g. simplest case with list of articles and one article open in separate activity; then You update list of articles in the background from server You might need to update currently open article also and so, need to know if it was updated). You should be able to check particular item on the side of the observer using just received Uri, because it (Uri) will contain parameter(s) You've used for query;
You can pass the ID via ContentValues, and append it to the notification url. This way you don't have to make a separate query.
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int rows = _database.update(getTableName(), values, selection, selectionArgs);
if (rows > 0) {
Uri itemUri = ContentUris.withAppendedId(uri, values.getAsLong(DatabaseModel.COLUMN_ID)); // DatabaseModel.COLUMN_ID is "_id"
getContext().getContentResolver().notifyChange(itemUri, null);
}
return rows;
}
I am trying to figure out how to get the photo for a merged contact, to display in a QuickContactBadge. I've been searching and googling, and all the things I can find online say this is not possible if the contact's default image comes from a Facebook sync. However all the examples I find also reference Froyo or Gingerbread.
Is there still no way to do this in the ICS/JB age?
This answer seemed the most promising, but the comments seem to say it is hit or miss.
None of the things I've found online have worked for me.
Here is the code I currently have:
public static Uri getContactPhotoUri(long ContactId) {
Uri person = ContentUris.withAppendedId(Contacts.CONTENT_URI, ContactId);
Uri photo = Uri.withAppendedPath(person, Contacts.Photo.CONTENT_DIRECTORY);
Cursor cur = App.ContentResolver().query(
Data.CONTENT_URI,
new String[] { Data._ID },
ContactsContract.Data.CONTACT_ID
+ "="
+ ContactId
+ " AND "
+ Data.MIMETYPE
+ "='"
+ Photo.CONTENT_ITEM_TYPE
+ "'", null, Data.IS_PRIMARY + " DESC");
Uri rv = null;
rv = (cur == null || !cur.moveToFirst())? null: photo;
if (cur != null) cur.close();
return rv;
}
It shows the image properly for contacts where the image comes from the google contact.
The image does not show properly for contacts where the primary image comes from Facebook.
Is there REALLY, still, no reliable way to get the default image for a contact regardless of where the image comes from?
EDIT (01/18/2013): I've also tried querying the PHOTO_URI and PHOTO_THUMBNAIL_URI as follows, with the same results.
public static String[] GroupMembersProjection = new String[] {
Contacts._ID,
Contacts.LOOKUP_KEY,
Contacts.DISPLAY_NAME_PRIMARY,
Contacts.PHOTO_THUMBNAIL_URI
};
public static Cursor getGroupMembers(int groupid, String sort) {
String ord;
if (sort.equals("A")) { ord = Contacts.DISPLAY_NAME_PRIMARY; }
else { ord = Contacts.TIMES_CONTACTED + " DESC"; /* SORT = "U"; DEFAULT */ }
ContentResolver cr = App.ContentResolver();
Cursor contacts = cr.query(Data.CONTENT_URI,
GroupMembersProjection,
GroupMembership.GROUP_ROW_ID + "=" + groupid, null, ord);
return contacts;
}
Additionally, I tried querying PHOTO_ID instead of the PHOTO_URI fields, and then using the following code to get the URI manually and use that for the image, but this also yields the same results, showing google images, but not Facebook ones.
Uri puri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, photoid);
Alright, I would like to make a class that can kinda make working with Content Providers a little easier, especially while working with contacts. I have something of a base layout, but its erroring out when I try to initiate cr. How would I be able to get something like this working?
Also, how does it look in general? From a design and efficiency perspective, as well as being an easy to use utility, would this be a good way to go about doing what I'd like to achieve?
public class ContactUtils {
private Uri uri = ContactsContract.Contacts.CONTENT_URI;
private ContentResolver cr = new ContentResolver(this);
public String getDisplayName(int id) {
String name = null;
String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME};
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + ("1") + "'";
Cursor contact = cr.query(this.uri, projection, selection, null, null);
while (contact.moveToFirst()) {
name = contact.getString(contact.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
}
return name;
}
}
The constructor of ContentResolver takes a Context a its single parameter. Your ContactUtils class does not extend Context and can therefore not be used as one.