Following This Retrieving a List of Contacts Tutorial in the android developers site, I managed to implement contacts search functionality. Here is my code so far
private void retrieveContactRecord(String phoneNo) {
try {
Log.e("Info", "Input: " + phoneNo);
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phoneNo));
String[] projection = new String[]{ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME};
String sortOrder = ContactsContract.PhoneLookup.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
ContentResolver cr = getContentResolver();
if (cr != null) {
Cursor resultCur = cr.query(uri, projection, null, null, sortOrder);
if (resultCur != null) {
while (resultCur.moveToNext()) {
String contactId = resultCur.getString(resultCur.getColumnIndex(ContactsContract.PhoneLookup._ID));
String contactName = resultCur.getString(resultCur.getColumnIndexOrThrow(ContactsContract.PhoneLookup.DISPLAY_NAME));
Log.e("Info", "Contact Id : " + contactId);
Log.e("Info", "Contact Display Name : " + contactName);
break;
}
resultCur.close();
}
}
} catch (Exception sfg) {
Log.e("Error", "Error in loadContactRecord : " + sfg.toString());
}
}
Here is the catch, this code works pretty great, but I need to implement a smart search here. I want 26268 to match Amanu as well as 094 526 2684. I believe it is called T9 dictionary.
I tried looking at other projects for clue, but I couldn't find anything. Any pointers would be appreciated!
T9 search can be implemented using trie data structure. You can see an example here - Trie dict.
After implementing something similar you will be able to convert your search input into its possible T9 decoded variant and compare if it matches with name.
Dump all contacts to a HashSet
Set<String> contacts = new HashSet<String>();
Then search:
List<List<String>> results = new ArrayList<List<String>>();
// start the search, pass empty stack to represent words found so far
search(input, dictionary, new Stack<String>(), results);
Search method (from #WhiteFang34)
public static void search(String input, Set<String> contacts,
Stack<String> words, List<List<String>> results) {
for (int i = 0; i < input.length(); i++) {
// take the first i characters of the input and see if it is a word
String substring = input.substring(0, i + 1);
if (contacts.contains(substring)) {
// the beginning of the input matches a word, store on stack
words.push(substring);
if (i == input.length() - 1) {
// there's no input left, copy the words stack to results
results.add(new ArrayList<String>(words));
} else {
// there's more input left, search the remaining part
search(input.substring(i + 1), contacts, words, results);
}
// pop the matched word back off so we can move onto the next i
words.pop();
}
}
}
The ContentProvider for contacts doesn't support it. So what I did was to dump all of the contacts in a List then use a RegEx to match for the name.
public static String[] values = new String[]{" 0", "1", "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9"};
/**
* Get the possible pattern
* You'll get something like ["2ABC","4GHI"] for input "14"
*/
public static List<String> possibleValues(String in) {
if (in.length() >= 1) {
List<String> p = possibleValues(in.substring(1));
String s = "" + in.charAt(0);
if (s.matches("[0-9]")) {
int n = Integer.parseInt(s);
p.add(0, values[n]);
} else {
// It is a character, use it as it is
p.add(s);
}
return p;
}
return new ArrayList<>();
}
.... Then compile the pattern. I used (?i) to make it case insensitive
List<String> values = Utils.possibleValues(query);
StringBuilder sb = new StringBuilder();
for (String value : values) {
sb.append("[");
sb.append(value);
sb.append("]");
if (values.get(values.size() - 1) != value) {
sb.append("\\s*");
}
}
Log.e("Utils", "Pattern = " + sb.toString());
Pattern queryPattern = Pattern.compile("(?i)(" + sb.toString() + ")");
You'll know what to do after this.
Related
How to read filenames from the download directory on android 10 and higher?
After filtering the names I want to let users select from this list to open
the file.
On Android versions 8 (API 28) the FILE api is quite simple to use.
Filenames are simple to read with the method DirListOld. With these names
I can read the content of the files.
I tried to make a method to do the same on Android 10 (API 30) and higher.
But documentation is not very clear. I did some experimenting with
MediaStore methods, but I could not get the filenames only got directory
names on external storage.
How to filter the results is not very well documented and examples of the
MediaStore.Downlaods are totally absent.
My experiment is shown in method DirListNew.
Also I had to ask for a permission for MANAGE_EXTERNAL_STORAGE. Without this
permission even DirListNew results in an empty string. As I read in several
comments Google-Play is not generous in giving this permission. Why not
special permission for only downloaded files. I don't have to read all external
files.
I don't understand why Google-Android developers made such a mess for retrieving
simple downloaded files.
public String DirListOld()
{
String sName;
File oDownloadDir;
String sDownloadDir;
StringBuilder dirContent = new StringBuilder();
oDownloadDir = this.getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
try {
sDownloadDir = oDownloadDir.getName();
if (!sDownloadDir.equals("") )
{
for (File f : Objects.requireNonNull(oDownloadDir.listFiles()))
{
if (f.isFile())
{
sName = f.getName();
dirContent.append(sName);
dirContent.append("\n");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return dirContent.toString();
} // DirListOld
#RequiresApi(api = Build.VERSION_CODES.Q)
public String DirListNew()
{
EditText editText = findViewById(R.id.editTextMultiLine2);
StringBuilder dirContent = new StringBuilder();
String[] projection = new String[] {
MediaStore.Downloads.DATA
};
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;
Cursor cursor = getApplicationContext().getContentResolver().query(
MediaStore.Files.getContentUri("external"),
projection,
selection,
selectionArgs,
sortOrder
);
if (cursor != null) {
cursor.moveToFirst();
//iterate over rows
for (int i = 0; i < cursor.getCount(); i++) {
//iterate over the columns
for(int j = 0; j < cursor.getColumnNames().length; j++){
//append the column value to the string builder and delimit by \n
dirContent.append(cursor.getString(j));
dirContent.append("\n");
}
//add a new line carriage return
dirContent.append("\n");
//move to the next row
cursor.moveToNext();
}
//close the cursor
cursor.close();
}
return dirContent.toString();
} // DirListNew
The android default Email is filter by subject, sender or receiver. But how to filter by content? The message body is not saved to database, which is saved to the file in after Android 5.0. Should I put the message body to the database, which do like before Android 5.0? And then filter the content according the keyword? Please give me some advice, Thanks!
case BODY:
final ContentValues dbValues = new ContentValues(values);
// Prune out the content we don't want in the DB
dbValues.remove(BodyColumns.HTML_CONTENT);
dbValues.remove(BodyColumns.TEXT_CONTENT);
// TODO: move this to the message table
longId = db.insert(Body.TABLE_NAME, "foo", dbValues);
resultUri = ContentUris.withAppendedId(uri, longId);
// Write content to the filesystem where appropriate
// This will look less ugly once the body table is folded into the message table
// and we can just use longId instead
if (!values.containsKey(BodyColumns.MESSAGE_KEY)) {
throw new IllegalArgumentException(
"Cannot insert body without MESSAGE_KEY");
}
final long messageId = values.getAsLong(BodyColumns.MESSAGE_KEY);
// Ensure that no pre-existing body files contaminate the message
deleteBodyFiles(context, messageId);
writeBodyFiles(getContext(), messageId, values);
break;
public static String buildLocalSearchSelection(Context context, long mailboxId,
String queryFilter, String queryFactor) {
StringBuilder selection = new StringBuilder();
selection.append(" (");
queryFilter = queryFilter.replaceAll("\\\\", "\\\\\\\\")
.replaceAll("%", "\\\\%")
.replaceAll("_", "\\\\_")
.replaceAll("'", "''");
String[] queryFilters = queryFilter.split(" +");
boolean isAll = false;
if (queryFactor.contains(SearchParams.SEARCH_FACTOR_ALL)) {
isAll = true;
}
if (queryFactor.contains(SearchParams.SEARCH_FACTOR_SUBJECT) || isAll) {
selection.append(buildSelectionClause(queryFilters, MessageColumns.SUBJECT));
}
if (queryFactor.contains(SearchParams.SEARCH_FACTOR_SENDER) || isAll) {
selection.append(buildSelectionClause(queryFilters, MessageColumns.FROM_LIST));
}
if (queryFactor.contains(SearchParams.SEARCH_FACTOR_RECEIVER) || isAll) {
selection.append(buildSelectionClause(queryFilters, null));
}
selection.delete(selection.length() - " or ".length(), selection.length());
selection.append(")");
return selection.toString();
}
it can use the ' MessageColumns.SNIPPET' to filter the email content.
I want to load several contacts via Xamarin.Contacts.AddressBook, at the moment I have something like:
var loookupIDs = /* load 10 saved contact IDs */
var addressBook = new AddressBook(context) { PreferContactAggregation = true };
foreach(var id in loookupIDs)
{
var contact = addressBook.Load(id);
names.Add(contact.DisplayName);
}
However, this is really slow (tested on Android device) - even just loading 10 contacts. Is there a way to batch up the loading so it's faster? Or is the only option to use platform specific APIs instead of the Xamarin wrapper.
Yes, Xamarin.Mobile is kind of slow. It combines all possible contacts (phones, mails, etc) and all possible fields, which is not recommended by Android reference manual.
I recommend you to use native way to query your contacts with Cursor and filter it for your needs. Sadly, Xamarin dev mixed up all constants, so it is not trivial task.
Here is complete example
public class PhoneContactInfo
{
public string PhoneContactID { get; set; }
public string ContactName { get; set; }
public string ContactNumber { get; set; }
}
public IEnumerable<PhoneContactInfo> GetAllPhoneContacts(IEnumerable<int> filterIds = null)
{
Log.Debug("GetAllPhoneContacts", "Getting all Contacts");
var arrContacts = new System.Collections.Generic.List<PhoneContactInfo>();
PhoneContactInfo phoneContactInfo = null;
var uri = ContactsContract.CommonDataKinds.Phone.ContentUri;
string[] projection = { ContactsContract.Contacts.InterfaceConsts.Id,
ContactsContract.Contacts.InterfaceConsts.DisplayName,
ContactsContract.CommonDataKinds.Phone.Number
};
//String[] strings = filterIds.Select(k => Convert.ToString(k)).ToArray();
//string whereClause = ContactsContract.Contacts.InterfaceConsts.Id + " = ? ";
var cursor = MainActivity.ContextHolder.ContentResolver.Query(uri, projection,
null,
null,
null);
cursor.MoveToFirst();
while (cursor.IsAfterLast == false)
{
int phoneContactID = cursor.GetInt(cursor.GetColumnIndex(ContactsContract.Contacts.InterfaceConsts.Id));
if (filterIds.Contains(phoneContactID))
{
String contactNumber = cursor.GetString(cursor.GetColumnIndex(ContactsContract.CommonDataKinds.Phone.Number));
String contactName = cursor.GetString(cursor.GetColumnIndex(ContactsContract.Contacts.InterfaceConsts.DisplayName));
phoneContactInfo = new PhoneContactInfo()
{
PhoneContactID = Convert.ToString(phoneContactID),
ContactName = contactName,
ContactNumber = contactNumber
};
arrContacts.Add(phoneContactInfo);
}
cursor.MoveToNext();
}
cursor.Close();
cursor = null;
Log.Debug("GetAllPhoneContacts", "Got all Contacts");
return arrContacts;
}
If you wish to add some fancy async
public Task<IEnumerable<PhoneContactInfo>> GetAllPhoneContactsAsync(IEnumerable<int> filterIds)
{
return Task.FromResult(GetAllPhoneContacts(filterIds));
}
Also take a look at commented whereClause. You possibly can construct 'SQL like' where clause to make this query even more faster. Just build a string with several '=' and 'or'
P.S.
I didn't measure performance differences, if anyone has decent statistics i will be grateful
It looks like you access AdressBook for each loookupID, this might cause your speed issue.
Try:
1) Fetch all contacts, or only those you might be interested in. (Use Linq)
2) Do further work with found contacts
Example from Xamarin docs:
http://blog.xamarin.com/introducing-xamarin-contacts/
var book = new AddressBook (this) {
PreferContactAggregation = true
};
foreach (Contact c in book.Where (c => c.LastName == "Smith")) {
print (c.DisplayName);
foreach (Phone p in c.Phones)
print ("Phone: " + p.Number);
foreach (Email e in c.Emails)
print ("Email: " + e.Address);
}
I am implementing an application in which I have to display alarm info (day, time..) if it is available. Is there anybody know how to access to alarm info?
Thanks so much
Result: Finally, I can retrieve alarm information by using Curious's answer as above. Moreover, when I explored Deskclock app in /packages/apps/ I found one more way to get next alarm format (I used this way to display alarm info on my LockScreen because it is more simple than Curious's one :) )
String nextAlarm = Settings.System.getString(getContentResolver(),
Settings.System.NEXT_ALARM_FORMATTED);
if(TextUtils.isEmpty(nextAlarm) {
Log.v(TAG, "nextAlarm is empty");
} else {
Log.v(TAG, "nextAlarm is :" + nextAlarm);
}
The next alarm format like this:
Sat 09:00
The disadvantage point is that it can only run in system environment (you have to embed them to Android source -> build it and run by image file).
Try something like this
final String tag_alarm = "tag_alarm";
Uri uri = Uri.parse("content://com.android.alarmclock/alarm")
Cursor c = getContentResolver().query(uri, null, null, null, null);
Log.i(tag_alarm, "no of records are" + c.getCount());
Log.i(tag_alarm, "no of columns are" + c.getColumnCount());
if (c != null) {
String names[] = c.getColumnNames();
for (String temp : names) {
System.out.println(temp);
}
if (c.moveToFirst()) {
do {
for (int j = 0; j < c.getColumnCount(); j++) {
Log.i(tag_alarm, c.getColumnName(j);
+ " which has value " + c.getString(j));
}
} while (c.moveToNext());
}
}
I have a List View that Displays songs in alphebetical order being populated by this method
public void updatelist(){
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,null);
int j =0;
if(cursor.moveToFirst()){
do{
int ALBUM_ID = cursor.getInt((cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.ALBUM_ID)));
int pathcolumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
String path1 = cursor.getString(pathcolumn);
String album_url = null;
Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, ALBUM_ID);
album_url = uri.toString();
ContentResolver res = this.getContentResolver();
// Album
String album_name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.ALBUM));
String year = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.YEAR));
// String year = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS));
// artist
String artist_name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.ArtistColumns.ARTIST));
// display name
String DisplayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
//title
String Title = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.TITLE));
songtitle.add(Title);
Collections.sort(songtitle);
artistname.add(songtitle.indexOf(Title), artist_name);
albumname.add(songtitle.indexOf(Title), album_name);
path.add(songtitle.indexOf(Title),path1);
albumartwork.add(songtitle.indexOf(Title),album_url);
j++;
}while(cursor.moveToNext());
}
Collections.sort(songtitle);
adapter = new ArrayAdapter<String>(this,R.layout.song,songtitle);
setListAdapter(adapter);
}
My Question is i want to insert Dividers whenever the first letter of the SongName Changes.
I have this method to get the first letter of the songname if it is different than the previous..
private void alphebetdividers(ArrayList<String> songtitle2) {
String newString = null;
// TODO Auto-generated method stub
ArrayList<Character> letters = new ArrayList<Character>();
int i = 0;
int j = 0;
while( j < songtitle.size()-1){
if(songtitle2.get(i).charAt(0) == songtitle2.get(i+1).charAt(0)){
Toast.makeText(getApplication(), songtitle2.get(i).charAt(0) + "== " + songtitle2.get(i+1).charAt(0), Toast.LENGTH_LONG).show();
}else{
// Display Char with TextView
String songName = songtitle2.get(i);
newString = songName.substring(0, 1);
}
j++;
i++;
}
How would i display this in the list view at the appropriate spots. Thank you and i will give u a good rating if u know the answer.
You probably want an Adapter that implements SectionIndexer, specifically AlphabetIndexer. See this or this.
You really should be using an Adapter... there are libraries that do all of this work for you, you shouldn't have to do it yourself in a massive loop!
Here is some sample code to help you get started... note that it is a little out-dated in that it makes use of some deprecated methods (such as managedQuery, etc.). If you wanted to be entirely 100% correct you would want to make use of the LoaderManager.LoaderCallbacks<Cursor interface introduced in the Honeycomb release, but it's a good start.
To sort the displayed views, you can make use of the sortOrder argument in the ContentProvider's query method.