I can't seem to find any good example code of how to query the UserDictionary content provider given a word. My query looks like:
Cursor cur = getContentResolver().query(
UserDictionary.Words.CONTENT_URI,
new String[] {Words._ID, Words.WORD},
Words.WORD + "=?",
new String[] {"test"},
null);
I have also tried not specifying a query as well as not specifying a projection and the cursor is always empty. I have included android.permission.READ_USER_DICTIONARY in my manifest.
Try this
final String[] QUERY_PROJECTION = {
UserDictionary.Words._ID,
UserDictionary.Words.WORD
};
Cursor cursor = getContentResolver()
.query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, "(locale IS NULL) or (locale=?)",
new String[] { Locale.getDefault().toString() }, null);
I have not tested this, just a suggestion
Precondition: Make sure you have appropriate words in UserDictionary at
Settings ->Language & Input -> Personal dictionary.
Sample sql query which searches for words containing SO in user dictionary and it's equivalent Android code sample. Notice the usage of ? to be replaced by args.
SQL query:
SELECT UserDictionary.Words._ID, UserDictionary.Words.WORD FROM UserDictionary.Words.CONTENT_URI WHERE UserDictionary.Words.WORD LIKE "%SO%
Equivalent Code:
String[] columns = {UserDictionary.Words._ID, UserDictionary.Words.WORD};
String condition = UserDictionary.Words.WORD + " LIKE ? ";
// ? in condition will be replaced by `args` in order.
String[] args = {"%SO%"};
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(UserDictionary.Words.CONTENT_URI, columns, condition, args, null);
//Cursor cursor = resolver.query(UserDictionary.Words.CONTENT_URI, projection, null, null, null); - get all words from dictionary
if ( cursor != null ) {
int index = cursor.getColumnIndex(UserDictionary.Words.WORD);
//iterate over all words found
while (cursor.moveToNext()) {
//gets the value from the column.
String word = cursor.getString(index);
Log.i(TAG, "Word found: " + word);
}
}
Permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
Starting on API 23, the user dictionary is only accessible through IME
and spellchecker.
https://developer.android.com/reference/android/provider/UserDictionary.html
android.permission.READ_USER_DICTIONARY permission is no longer available in M
Related
I am working on a project using Content Provider for DB.
I am able to fetch all rows using the query mentioned below.
My problem is I want to sum up a column for all rows fetched.
I am querying as :
String query = WorkTable.ENTRY_TIME + " = ?"
String projection = "new String[]{WorkoutLogTable.STEPS}";
Cursor cursor = getContentResolver()
.query(
LogProvider.WORK_LOG,
projection,
query,
new String[]{dateString},
null
);
I want the sum of WorkoutLogTable.STEPS. Projection needs string[] as parameter, so how can I sum up the STEPS value?
Edit
I used a projection:
String projection = new String[]{"sum(WorkoutLogTable.STEPS}) as total"};
But it's also not working.
Solution:
I was doing a mistake by making the whole part as string.
So I have resolved so:
String projection = new String[]{"sum(steps) as total"}; // steps is my column name and I was fetching it by WorkoutLogTable.STEPS which was wrong
Or another solution can be using Dynamic string (from cricket_007 answer)
Projection needs string[] as parameter,
Right, so why is your projection variable a String? This statement won't even compile.
String projection = "new String[]{"sum(WorkoutLogTable.STEPS}) as total"};
Maybe you meant this?
String[] projection = new String[] { "sum(" + WorkoutLogTable.STEPS + ") as total" };
You need to use an actual String[] object, not a String that has the content of "String[] { ... }"
String selection = WorkTable.ENTRY_TIME + " = ?"
String[] projection = new String[] { "sum(" + WorkoutLogTable.STEPS + ")" };
String[] selectionArgs = new String[] { dateString };
Cursor cur = getContentResolver().query(
LogProvider.WORK_LOG,
projection,
selection,
null, null);
Is this a content provider from another app? If you are writing the content provider, I would recommend that you add another URL specifically for the summary query and do your sum function in the query inside the content provider. Then just use the alternate URL when you go through the content resolver.
I'm trying to filer out city names and return matching cities based on the selection parameter being passed.
However the query call still returns all the rows. Even tried putting in a fake city name that doesn't already exist in the database and it still spits out the same original rows.
public void addLocation(String cityName){
Cursor cursor = mContext.getResolver().query(
WeatherContract.LocationEntry.CONTENT_URI,
null,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING +
" = " + cityName,
null,
null);
I've read the Android docs over and over, ContentResolver Query, and passing the selection parameter in doesn't seem to be doing anything.
public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
Assuming the provider you're using is androidx.core.content.FileProvider (according to what you've declared in your manifest), the selection, selectionArgs and sortOrder parameters of FileProvider.query are... ignored in AndroidX's implementation !
See the v1.2.0 source here (line 409)
The only solution I found is to filter and sort the results post-query, in your own code.
And yeah, that's kinda sad...
Instead of passing the value in the selection parameter, put it in selectionArgs.
public void addLocation(String cityName){
String[] args = { cityName };
Cursor cursor = mContext.getResolver().query(
WeatherContract.LocationEntry.CONTENT_URI,
null,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING +
" = ?",
args,
null);
What would be the correct way to add DISTINCT and/or GROUPBY to ContentResolver-based queries?
Right now I have to create custom URI for each special case.
Is there a better way?
(I still program for 1.5 as lowest common denominator)
You can do nice hack when querying contentResolver, use:
String selection = Models.SOMETHING + "=" + something + ") GROUP BY (" + Models.TYPE;
If you want to use DISTINCT with SELECT more then one column, You need to use GROUP BY.
Mini Hack over ContentResolver.query for use this:
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri,
new String[]{"DISTINCT address","body"}, //DISTINCT
"address IS NOT NULL) GROUP BY (address", //GROUP BY
null, null);
if(c.moveToFirst()){
do{
Log.v("from", "\""+c.getString(c.getColumnIndex("address"))+"\"");
Log.v("text", "\""+c.getString(c.getColumnIndex("body"))+"\"");
} while(c.moveToNext());
}
This code select one last sms for each of senders from device inbox.
Note: before GROUP BY we always need to write at least one condition.
Result SQL query string inside ContentResolver.query method will:
SELECT DISTINCT address, body FROM sms WHERE (type=1) AND (address IS NOT NULL) GROUP BY (address)
Since no one came to answer I'm just going to tell how I solved this. Basically I would create custom URI for each case and pass the criteria in selection parameter. Then inside ContentProvider#query I would identify the case and construct raw query based on table name and selection parameter.
Here's quick example:
switch (URI_MATCHER.match(uri)) {
case TYPES:
table = TYPES_TABLE;
break;
case TYPES_DISTINCT:
return db.rawQuery("SELECT DISTINCT type FROM types", null);
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return db.query(table, null, selection, selectionArgs, null, null, null);
In your overridden ContentProvider query method have a specific URI mapping to using distinct.
Then use SQLiteQueryBuilder and call the setDistinct(boolean) method.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
boolean useDistinct = false;
switch (sUriMatcher.match(uri))
{
case YOUR_URI_DISTINCT:
useDistinct = true;
case YOUR_URI:
qb.setTables(YOUR_TABLE_NAME);
qb.setProjectionMap(sYourProjectionMap);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sortOrder))
{
orderBy = DEFAULT_SORT_ORDER;
}
else
{
orderBy = sortOrder;
}
// Get the database and run the query
SQLiteDatabase db = mDBHelper.getReadableDatabase();
// THIS IS THE IMPORTANT PART!
qb.setDistinct(useDistinct);
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
if (c != null)
{
// Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
Though I have not used Group By, I have used Distinct in content resolver query.
Cursor cursor = contentResolver
.query(YOUR_URI,
new String[] {"Distinct "+ YOUR_COLUMN_NAME},
null,
null, null);
Adding the Distinct keyword in the projection worked for me too, however, it only worked when the distinct keyword was the first argument:
String[] projection = new String[]{"DISTINCT " + DBConstants.COLUMN_UUID, ... };
In some condition, we can use "distinct(COLUMN_NAME)" as the selection,
and it work perfect.
but in some condition, it will cause a exception.
when it cause a exception, i will use a HashSet to store the column values....
// getting sender list from messages into spinner View
Spinner phoneListView = (Spinner) findViewById(R.id.phone_list);
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri, new String[]{"Distinct address"}, null, null, null);
List <String> list;
list= new ArrayList<String>();
list.clear();
int msgCount=c.getCount();
if(c.moveToFirst()) {
for(int ii=0; ii < msgCount; ii++) {
list.add(c.getString(c.getColumnIndexOrThrow("address")).toString());
c.moveToNext();
}
}
phoneListView.setAdapter(new ArrayAdapter<String>(BankActivity.this, android.R.layout.simple_dropdown_item_1line, list));
When you have multiple columns in your projection you should do like this:
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
"DISTINCT " + MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.MediaColumns.DATA
)
val groupBySelection = " 1) GROUP BY (${MediaStore.Images.Media.BUCKET_ID}"
contentResolver.query(
uri,
projection,
null,
groupBySelection,
null,
null
)
groupBySelection with closing bracket and number "1" inside is a tiny hack, but it works absolutely fine
I created a utility method for using group by and distinct.
Usage
Here is an example of selecting unseen thread_id with the last message date from the MMS database.
query(contentResolver= contentResolver,
select = arrayOf(Mms.THREAD_ID, "max(${Mms.DATE}) as date"),
from = Mms.CONTENT_URI,
where = "${Mms.SEEN} = 0",
groupBy = "1",
orderBy = "2 desc"
).use {
while (it?.moveToNext() == true){
val threadId = it.getInt(0)
val date = it.getLong(1)
}
}
Source
fun query(
contentResolver: ContentResolver,
from: Uri,
select: Array<String>,
where: String? = null,
groupBy: Array<out String>? = null,
distinct: Boolean = false,
selectionArgs: Array<out String>? = null,
orderBy: String? = null,
): Cursor? {
val tmpSelect = select[0]
val localWhere =
if (groupBy == null) where
else "${where ?: "1"}) group by (${groupBy.joinToString()}"
if (distinct) {
select[0] = "distinct $tmpSelect"
}
val query = contentResolver.query(from, select, localWhere, selectionArgs, orderBy)
select[0] = tmpSelect
return query
}
Maybe its more simple to get distinct values,
try to add the DISTINCT word before the column name you want into the projection table
String[] projection = new String[]{
BaseColumns._ID,
"DISTINCT "+ Mediastore.anything.you.want
};
and use it as an argument to query method of the content resolver!
I hope to help you, cause I have the same question before some days
I have a Cursor points to a Contact. How can I get an url built from that Cursor?
I need to know that because from here
http://developer.android.com/guide/topics/providers/content-providers.html
I need to have an url so that I can build a phone uri, like this:
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
and I can query all the phone numbers for that contact.
You can use the following query to retrieve all numbers for a certain contact:
/* the following line assumes that the contactCursor you described
* has the People._ID column at index 0 in its projection. */
int contactId = contactCursor.getInt(0);
Cursor numberCursor = getContentResolver().query(Phones.CONTENT_URI,
new String[] {Phones.NUMBER}, Phones.PERSON_ID + "=" + contactId, null, null);
while(cursor.moveToNext()) {
String number = cursor.getString(0);
}
cursor.close();
I would like to get user contacts and then append some kind of regular expression and append them to a list view. I am currently able to get all the contacts via
getContentResolver().query(People.CONTENT_URI, null, null, null, null);
and then pass them to a custom class that extends SimpleCursorAdapter.
So I would like to know how to get only the contacts that match a regular expression and not all of users contacts.
Instead of
getContentResolver().query(People.CONTENT_URI, null, null, null, null);
you should use something like
final ContentResolver resolver = getContentResolver();
final String[] projection = { People._ID, People.NAME, People.NUMBER };
final String sa1 = "%A%"; // contains an "A"
cursor = resolver.query(People.CONTENT_URI, projection, People.NAME + " LIKE ?",
new String[] { sa1 }, null);
this uses a parameterized request (using ?) and provides the actual values as a different argument, this avoids concatenation and prevents SQL injection mainly if you are requesting the filter from the user. For example if you are using
cursor = resolver.query(People.CONTENT_URI, projection,
People.NAME + " = '" + name + "'",
new String[] { sa1 }, null);
imagine if
name = "Donald Duck' OR name = 'Mickey Mouse") // notice the " and '
and you are concatenating the strings.
You can query the content provider with sql type input, the Query method is just a wrapper for an sql command.
Here is an example where I query for a Contacts name given a particular number
String [] requestedColumns = {
Contacts.Phones.NAME,
Contacts.Phones.TYPE
};
Cursor contacts = context.getContentResolver().query(
Contacts.Phones.CONTENT_URI,
requestedColumns,
Contacts.Phones.NUMBER + "='" + phoneNumber + "'",
null, null);
Note that instead of null I have parameters that build up the sql statement.
The requestColumns are the data I want to get back and Contacts.Phones.NUMBER + "='" + phoneNumber + "'" is the Where clause, so I retrieve the Name and Type where the Phone Number matches
You should be able to put a legal SQLite WHERE clause as the third argument to the query() method, including a LIKE, but there's no native REGEXP function in SQLite and Android doesn't seem to let you define your own. So depending how complex your needs are, a set of other SQLite conditions and LIKE expressions might do the trick.
See the documentation on the query method under ContentResolver and SQLite expressions.
Actually REGEXP with Calllog Content Provider works (means that regexp() function is defined for that content provider's Database https://sqlite.org/lang_expr.html#regexp)! But it is very slow: ~15 sec across ~1750 records.
String regexp = "([\\s\\S]{0,}" +
TextUtils.join("||[\\s\\S]{0,}", numbers) +
")";
cursor = context.getContentResolver().query(
CallLog.Calls.CONTENT_URI,
null,
CallLog.Calls.NUMBER + " REGEXP ?",
new String[]{regexp},
CallLog.Calls.DATE + " DESC"
);