I fetch contacts from the phone as below. I would like to ignore accent for exemple the name "Jérome" would be return either for search "jero" or "jéro".
var contacts = listOf<Contact>()
val displayNameProjection = arrayOf(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
val whereName = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?"
val whereNameParams = arrayOf( "%" + search + "%")
val contactCursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
displayNameProjection, whereName, whereNameParams,ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
contactCursor?.let { cursor ->
contacts = generateSequence { if (cursor.moveToNext()) cursor else null }
.take(10)
.map {
Contact( it.getString(it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)),
it.getString(it.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)))
}
.toList()
cursor.close()
}
Use the CONTENT_FILTER_URI API to quickly search contacts by name, this should handle accents automatically for you:
String name = "jero";
Uri searchUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(name));
Cursor cur = getContentResolver().query(searchUri, null, null, null, null);
DatabaseUtils.dumpCursor(cur);
if (cur != null) cur.close();
Related
I'm trying to query for every file in an android device using the mediastore API. Is there a shorter way of writing the selection string to include all the files in the device without having to write each for the Audio,Video,Images and NON_MEDIA? Also,how can I make the query,in addition,to get only the files of above a particular file size.
ContentResolver cr = context.getContentResolver();
Uri uri = MediaStore.Files.getContentUri("external");
// every column, although that is huge waste, you probably need
// BaseColumns.DATA (the path) only.
String[] projection = null;
// exclude media files, they would be here also.
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE + "OR" + MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO;
String[] selectionArgs = null; // there is no ? in selection so null here
String sortOrder = null; // unordered
Cursor allFiles = cr.query(uri, projection, selection, selectionArgs, sortOrder);
Is there a shorter way of writing the selection string to include all
the files in the device without having to write each for the
Audio,Video,Images and NON_MEDIA?
yes, MediaStore.Files
This is how I achieved (below code is in kotlin)
private suspend fun getAllFiles(context : Context) : List<AllFiles>{
return withContext(Dispatchers.IO){
val fileCollection = mutableListOf<AllFiles>()
val contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
val projections = arrayOf(MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.MIME_TYPE)
context.contentResolver.query(contentUri,projections,null,null,null).use {
cursor->
if(cursor!=null && cursor.count > 0) {
val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
val typeIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE)
fileCollection.clear()
while (cursor.moveToNext()){
val id = cursor.getLong(idIndex)
val displayName = cursor.getString(displayNameIndex)
val type = cursor.getString(typeIndex)
fileCollection.add(AllFiles(id,displayName,type))
}
}
else if(cursor==null)
Log.d("myTag","cursor is nUll")
else
Log.d("myTag","Cursor count is ${cursor.count}")
}
fileCollection
}
}
To run above method on android 12 , we need
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
outside of onCreate
val intent = Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:" + BuildConfig.APPLICATION_ID))
val storagePermissionResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()
) {
if (Environment.isExternalStorageManager()) {
// Permission granted.
getData()
}
}
and inside onCreate
if(!Environment.isExternalStorageManager())
storagePermissionResultLauncher.launch(intent)
else
getData()
where getData() is
fun getData(){
val textview = binding.textviewFirst
lifecycleScope.launchWhenResumed {
textview.append(getAllFiles(requireContext()).toString())
}
}
and AllFile is
data class AllFiles(
val fileId : Long,
val displayName : String?,
val type : String?)
you can then filter returned list on type
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.
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?
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
I am creating an app in which I list the recent images and videos from the user's gallery and display them on a RecyclerView using the file path. The code below is working on Android 7, but Android Studio is warning that MediaStore.Files.FileColumns.DATA has been deprecated. What to use instead?
// Get relevant columns for use later.
val projection = arrayOf(
MediaStore.Files.FileColumns.DATA
)
// Return only video and image metadata.
val selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
val queryUri: Uri = MediaStore.Files.getContentUri("external")
val cursorLoader = CursorLoader(
requireContext(),
queryUri,
projection,
selection,
null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC" // Sort order.
)
val cursor: Cursor? = cursorLoader.loadInBackground()
if (cursor!!.moveToFirst()) {
do {
val columnIndex = cursor.getColumnIndex(projection[0])
val filePath = cursor.getString(columnIndex)
val mimeType = URLConnection.guessContentTypeFromName(filePath)
if (mimeType != null && mimeType.startsWith("image")) {
mediaList.add(GalleryMedia(filePath, "image"))
} else if (mimeType != null && mimeType.startsWith("video")) {
mediaList.add(GalleryMedia(filePath, "video"))
}
} while (cursor.moveToNext())
}
cursor.close()