ContentResolver.query() method throws "Invalid token limit" error - android

The following error occurs on Pixel devices with build number RQ1A.201205.003 or later.
I would like to know the cause of the error and how to deal with it.
Is this a bug or a spec change?
■code
ContentResolver resolver = getContentResolver();
String order = "date ASC limit 100";
Cursor cursor = resolver.query(
CallLog.Calls.CONTENT_URI,
null,
null,
null,
order);
■error
"Invalid token limit,LINE:142,Method:readExceptionFromParcel Exception:Invalid token limit"
■Build number where the error occurs
https://support.google.com/pixelphone/thread/87641266
・RQ1A.201205.003
・RQ1A.201205.008
・RQ1A.201205.011
https://support.google.com/pixelphone/thread/93232095
・RQ1A.210105.002
・RQ1A.210105.003
https://support.google.com/pixelphone/thread/96244000
・RQ1A.210205.004
■If you replace it with the following code, no error will occur.
buildUpon().appendQueryParameter("limit", "100")
■Additional Information
When implemented using the official documentation method, no error occurred, but the LIMIT clause did not work (all records were retrieved).
ContentProvider - query
// Request 20 records starting at row index 30.
Bundle queryArgs = new Bundle();
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 30);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 20);
Cursor cursor = getContentResolver().query(
contentUri, // Content Uri is specific to individual content providers.
projection, // String[] describing which columns to return.
queryArgs, // Query arguments.
null); // Cancellation signal.

From Android 11, LIMIT and OFFSET should be retrieved using Bundle by
public Cursor query (Uri uri,
String[] projection,
Bundle queryArgs,
CancellationSignal cancellationSignal)
I use such solution and it works for me:
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
data class MediaItem(
val id: Long,
val contentUri: Uri,
val data: String?,
val mimeType: String?,
val duration: Long?
)
private fun fetchGalleryImages(
context: Context,
orderBy: String,
orderAscending: Boolean,
limit: Int = 20,
offset: Int = 0
): List<MediaItem> {
val galleryImageUrls = mutableListOf<MediaItem>()
val collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.MEDIA_TYPE,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Video.Media.DURATION
)
val whereCondition = "${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?"
val selectionArgs = arrayOf(
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
)
createCursor(
contentResolver = context.contentResolver,
collection = collection,
projection = projection,
whereCondition = whereCondition,
selectionArgs = selectionArgs,
orderBy = orderBy,
orderAscending = orderAscending,
limit = limit,
offset = offset
)?.use { cursor ->
while (cursor.moveToNext()) {
val idIndex = cursor.getColumnIndex(MediaStore.Audio.Media._ID)
if (idIndex < 0) continue
val id = cursor.getLong(idIndex)
galleryImageUrls.add(
MediaItem(
id = id,
contentUri = ContentUris.withAppendedId(collection, id),
data = cursor.getStringOrNull(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)),
mimeType = cursor.getStringOrNull(cursor.getColumnIndex(MediaStore.Files.FileColumns.MIME_TYPE)),
duration = cursor.getLongOrNull(cursor.getColumnIndex(MediaStore.Video.Media.DURATION))
)
)
}
}
return galleryImageUrls
}
private fun createCursor(
contentResolver: ContentResolver,
collection: Uri,
projection: Array<String>,
whereCondition: String,
selectionArgs: Array<String>,
orderBy: String,
orderAscending: Boolean,
limit: Int = 20,
offset: Int = 0
): Cursor? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
val selection = createSelectionBundle(whereCondition, selectionArgs, orderBy, orderAscending, limit, offset)
contentResolver.query(collection, projection, selection, null)
}
else -> {
val orderDirection = if (orderAscending) "ASC" else "DESC"
var order = when (orderBy) {
"ALPHABET" -> "${MediaStore.Audio.Media.TITLE}, ${MediaStore.Audio.Media.ARTIST} $orderDirection"
else -> "${MediaStore.Audio.Media.DATE_ADDED} $orderDirection"
}
order += " LIMIT $limit OFFSET $offset"
contentResolver.query(collection, projection, whereCondition, selectionArgs, order)
}
}
#RequiresApi(Build.VERSION_CODES.O)
fun createSelectionBundle(
whereCondition: String,
selectionArgs: Array<String>,
orderBy: String,
orderAscending: Boolean,
limit: Int = 20,
offset: Int = 0
): Bundle = Bundle().apply {
// Limit & Offset
putInt(ContentResolver.QUERY_ARG_LIMIT, limit)
putInt(ContentResolver.QUERY_ARG_OFFSET, offset)
// Sort function
when (orderBy) {
"ALPHABET" -> putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(MediaStore.Files.FileColumns.TITLE))
else -> putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(MediaStore.Files.FileColumns.DATE_ADDED))
}
// Sorting direction
val orderDirection =
if (orderAscending) ContentResolver.QUERY_SORT_DIRECTION_ASCENDING else ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, orderDirection)
// Selection
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, whereCondition)
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
}

Related

Getting photo dimension of locally stored photo

I'd like to know photos width and height before loading a bitmap. I'm using latest approach of ContentResolver to query locally stored photos.
contentResolver.query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)!!.use { cursor ->
while (cursor.moveToNext()) {
// Use an ID column from the projection to get
// a URI representing the media item itself.
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH)
val id: Long = cursor.getLong(idColumn)
val width: Int = cursor.getInt(widthColumn) //CRASH
}
Cursor is crashing on the selected line width:
IllegalStateException: Couldn't read row 0, col -1 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
How am I supposed to do that then?
Try This
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT
)
val images: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
contentResolver.query(
images,
projection,
null,
null,
null
)!!.use { cursor ->
if (cursor.count > 0) {
val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
val widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH)
val heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT)
while (cursor.moveToNext()) {
val id: Long = cursor.getLong(idColumn)
val width: Int = cursor.getInt(widthColumn)
val height: Int = cursor.getInt(heightColumn)
Log.e("tag", " width :: $width - height :: $height");
}
}
}

List all music files in all android devices with where, limit and offset clauses

I am trying to implement a recyclerview which will show all the music files in my android device with a 10 per page pagination and contains a search text. I am using content resolver for this. My application is targeting till android 12. So my existing code is like:
fun getAllMusicFilesInDevice(offset: Int, searchText: String){
val tempAudioList: MutableList<Album> = ArrayList()
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
else
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Audio.AudioColumns.DATA,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.ArtistColumns.ARTIST,
)
val selection = MediaStore.Audio.Media.DISPLAY_NAME + " LIKE ?"
val selectionArgs = arrayOf(searchText)
val sortOrder =
"${MediaStore.Audio.Media.DISPLAY_NAME} ASC LIMIT $PAGE_SIZE OFFSET $offset"
val c = mContext.contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder
)
if (c != null) {
while (c.moveToNext()) {
try {
val path: String = c.getString(0)
val songName = c.getString(1)
val album: String = c.getString(2)
val artist: String = c.getString(3)
val extension = File(path).extension
if (isMusicFile(extension)) {
isMusicPresentInPlaylist(path, playlistDb) {
val audioModel =
Album(
name = songName,
path = path,
artist = artist,
album = album
)
tempAudioList.add(audioModel)
}
}
} catch (ex: Exception) {
}
}
c.close()
}}
This code does not give back any result and makes the app behave like its stuck. Please help me out on the mistakes I am making and anything I am doing wrong out here so that I get this function working.
UPDATE CURSOR CODE
val c = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val limit = Bundle().apply {
// Limit & Offset
putInt(ContentResolver.QUERY_ARG_LIMIT, AUDIO_PAGE_SIZE)
putInt(ContentResolver.QUERY_ARG_OFFSET, offset)
}
mContext.contentResolver.query(uri, projection, limit, null)
} else
mContext.contentResolver.query(
uri,
projection,
null,
null,
"${MediaStore.Audio.Media.DISPLAY_NAME} ASC LIMIT $AUDIO_PAGE_SIZE OFFSET $offset"
)
Note: this code is running in a background thread.

code 2067 SQLITE_CONSTRAINT_UNIQUE when working with Android MediaStorage

The following code is used to save files from the app to downloads:
Uri collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, filename);
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
ContentResolver contentResolver = context.getApplicationContext().getContentResolver();
Uri uri = contentResolver.insert(collection, values);
OutputStream outputStream = context.getApplicationContext().getContentResolver().openOutputStream(uri, "w");
Everything is saved, however, if you delete the file from downloads manually, and then try to download it again from the application, an error appears:
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE[2067])
Is there a way to fix this or will I have to use unique names for each download?
This error happens because the machanism of OS: if we delete manually a file (media), its database will not be deleted immediately. Until we restart device.
Have a approach for this problem (still not be optimized - hope receiving sharing from people), such as:
Step 1: Get info of file via its name
Step 2: Ask OS to update its database via MediaScannerConnection.scanFile
Step 3: Use current code that has above problem
Codes for steps (collected on internet)
Step 1:
fun findByFileName(fileName: String): MutableList<FileInfo> {
val files = mutableListOf<FileInfo>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE,
MediaStore.Video.Media.DATA
)
val selection = "${MediaStore.Video.Media.DISPLAY_NAME} LIKE ?"
val selectionArgs = arrayOf(
fileName
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"
val query = context.contentResolver.query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
while (cursor.moveToNext()) {
// Get values of columns for a given video.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val duration = cursor.getInt(durationColumn)
val size = cursor.getInt(sizeColumn)
val data = cursor.getStringOrNull(dataColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
files += FileInfo(contentUri, name, duration, size, data)
}
}
return files
}
data class FileInfo(
val uri: Uri,
val name: String,
val duration: Int,
val size: Int,
val data: String? = null
)
Step 2 + Step 3:
val exitedData = findByFileName(fileName = name)
if (exitedData != null) {
MediaScannerConnection.scanFile(context, arrayOf(exitedData.first().data.toString()), null, object: MediaScannerConnection.OnScanCompletedListener {
override fun onScanCompleted(path: String?, uri: Uri?) {
// Step 3
// Use current code have this problem
...
}
}
} else {
// Save file normally
// Use current code have this problem
...
}

Android contentResolver query with MediaStore returns empty cursor

I'm trying to get all the images on the device, for now my code is:
val imageProjection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
)
val cursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageProjection,
null,
null,
"${MediaStore.Images.Media.DATE_MODIFIED} DESC"
)
cursor?.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val dateColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val name = it.getString(nameColumn)
val size = it.getString(sizeColumn)
val date = it.getString(dateColumn)
Log.d("IMAGE", "$id $name $size $date")
}
}
The query does give me a Cursor, but the Cursor appears to be empty as the code never enter the while.
I've checked that i have the READ_EXTERNAL_STORAGE permission, and i do have it, but it still doesn't work, i also tried using MediaStore.Images.Media.INTERNAL_CONTENT_URI as the query uri, but that didn't change anything, the result is always an empty cursor.
Can someone give me an hint on what i'm doing wrong?

Contact book read time on Android

I have written the following query to read contacts from a device.
private fun getContactPhoneNumbers(resolver: ContentResolver): Map<String, NameAndPhoneList> {
val startTime = System.nanoTime()
val map = hashMapOf<String, NameAndPhoneList>()
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
)
val selection =
"${ContactsContract.Contacts.DISPLAY_NAME} NOT LIKE '' and ${ContactsContract.Contacts.DISPLAY_NAME} NOT NULL"
resolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
selection,
null,
null,
null
)?.let { cursor ->
if (cursor.count > 0) {
while (cursor.moveToNext()) {
val id: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val name: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
val photoUri =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI))
val phone =
if (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val pCur: Cursor = resolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
arrayOf(id),
null
)!!
val numbers = mutableListOf<String>()
while (pCur.moveToNext()) {
val phoneNo: String = pCur.getString(
pCur.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
numbers.add(phoneNo)
}
pCur.close()
numbers
} else
null
// take contacts which either have email or phone numbers
if (id != null && name != null) {
map[id] = NameAndPhoneList(name, phone, photoUri?.let { Uri.parse(it) })
}
}
}
cursor.close()
}
val endTime = System.nanoTime() - startTime
Timber.i("$CONTACT_SYNC_PHONE_MAP_QUERY_TIME = $endTime")
return map
}
private data class NameAndPhoneList(
val name: String,
val phoneList: List<String>?,
val imageUri: Uri?
)
this is taking 112877872699 ns (~2 min) for a phonebook of length 6,300 contacts. Is this expected or can we optimize further?
for 6,300 contacts (assuming all have a phone) you're making 6,301 queries, which is why it's so slow...
Instead you can benefit from ContactsContract's "implicit join" feature, which allows you to get Contacts.CONTENT_URI fields when querying over the Data.CONTENT_URI table.
So, just query directly over the Data table, get all the phones including the CONTACT_ID, DISPLAY_NAME, etc. fields, and put it in some map contact-id => data.
Here's sample code in Java that can help - https://stackoverflow.com/a/44383937/819355

Categories

Resources