1

I am trying to a build an Android Dialer App (MinSDK 23).

I'd like to implement a contacts search (usually available in default dialer apps and other third party dialer apps) like below:

As I type digits on dial pad e.g. 245

I should be able to search contacts having phone numbers containing '245' as well as their names containing matches for alphabetical combinations of the digits 245, that as per my calculation should be 27 unique combinations.

While I do know it's a permutations problem and I might be able to search by applying OR 'LIKE' filters on Contacts Name, this doesn't seem efficient enough and it will cause more performance issues as I keep typing e.g. 245658.

How do we do this efficiently? Is there any built-in search mechanism for searching android contacts this way or any better custom solution exists?

ask-dev
  • 606
  • 2
  • 13
  • 30

2 Answers2

3

I can Filter 1500+ Contact in only 0.5 second ,

You can create same as List< String> or you can go with Object like List< Contact> and just change condition

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

class Scratch {
    public static void main(String[] args) {
        List<String> contactList= new ArrayList<String>();
        contactList.add("Jason  Arnold 9875454454");
        contactList.add("Joshua Ferguson 789455482121");
        contactList.add("Sarah  Churchill 78452165982");
        contactList.add("Brandon    Walsh 78452121");
        contactList.add("Justin Cameron 452197821");
        contactList.add("Rebecca    Howard 784521983232");
        contactList.add("Trevor Smith 372753221");
        contactList.add("Heather    Fraser 9884562145");
        contactList.add("Caroline   Peters 6598421");
        contactList.add("Chloe  Lyman");


        HashMap<String, String> hm = new HashMap<String, String>();
        hm.put("2", "[2abc]");
        hm.put("3", "[3def]");
        hm.put("4", "[4ghi]");
        hm.put("5", "[5jkl]");
        hm.put("6", "[6mno]");
        hm.put("7", "[7pqrs]");
        hm.put("8", "[8tuv]");
        hm.put("9", "[9wxyz]");
        hm.put("1", "[1]");
        hm.put("0", "[0]");

        Instant startTime = Instant.now();
        String findContact = "3727"; // todo
        String regEx = "";
        for (int i = 0; i < findContact.length(); i++) {
            regEx = regEx + hm.get(findContact.charAt(i) + "");
        }

        String finalRegEx = "^"+regEx+".*$";
        System.out.println("!_!_" + finalRegEx);

        List<String> ll = contactList.stream().filter(s -> isMatched(s, finalRegEx))
                .collect(Collectors.toList());
                
        for (int i = 0; i < ll.size(); i++) {
            System.out.println("-->: " + ll.get(i));
        }

        Instant current = Instant.now();
        System.out.println("\nFound List size: " + ll.size());
        long milliseconds = (current.toEpochMilli() - startTime.toEpochMilli());
        System.out.println("\nmilliseconds: " + milliseconds);
    }

    private static boolean isMatched(String str, String finalRegEx) {
        String[] arrOfStr = str.split("\\s");
        for (String a : arrOfStr) {
            if (a.toUpperCase().matches(finalRegEx.toUpperCase()))
                return true;
        }
        return false;
    }
}

Result :

  1. Trevor Smith 372753221 (as Phonenumber start with 3727)

  2. Heather Fraser 9884562145 (as Last name match with 3727 dial pad keys)

Github code here

Rajesh Satvara
  • 3,842
  • 2
  • 30
  • 50
0

That's a classic interview question in our company.

First of all, don't hit the ContactsContract APIs for every digit typed, you'll need to run a single query during launch to get all names from the Contacts DB so you'll be able to quickly search over them in memory.

Something like:

Map<String, Long> namesMap = new HashMap<>();
String[] projection = new String[]{Contacts._ID, Contacts.DISPLAY_NAME};
Cursor c = getContentResolver().query(Contacts.CONTENT_URI, projection, null, null, null);
while (c != null && c.moveToNext()) {
    namesMap.put(c.getString(1), c.getLong(0));
}
c.close();

Next, the nicest way to filter the above NamesMap to get only names to fit the search digits is to generate a regex pattern.

So a search query for 245 will generate the regex: .*[2abc][4ghi][5jkl].*, that shouldn't be too hard to build, every digit is mapped to a fixed string: 2 -> [2abc].

Once you have the regex pattern ready, you iterate through the namesMap and return only entries with keys matching the pattern. Make sure you do a case-insensitive match, or instead, lowercase all names while building namesMap.

marmor
  • 27,641
  • 11
  • 107
  • 150
  • So how many contacts we assume are safe to load in to memory? Is it going to be fast enough operation to perform on every launch of dialer activity? People tend to open the dialer and expect to dial in numbers immediately. If I use some database then it opens a whole new Pandora box of syncing the contacts on every launch. – ask-dev Sep 16 '19 at 08:58
  • if you're loading contact names only, i think there's no problem here, you can load as many as you want. – marmor Sep 22 '19 at 06:13