Add SmartDial database for the Dialer app.

- Creates a database helper to create a smartdial database for the Dialer app.
- Queries all rows in the Contact database and copies related columns to
  the smart dial database.
- Create another prefix database to contain all prefixes of a contact.
- During keypad input, the prefix databse is queried to find contact
  suggestions, and suggestions are ranked by the usage data and contact
  status (starred, primary contact, etc.)
- Created unit test for the SmartDial database insertion and prefix
  computing functions.

Change-Id: I4d7c3b3bcc52dd6efa4d6e69d3f1687c3abaeb69
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
new file mode 100644
index 0000000..fd402c9
--- /dev/null
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.database;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import com.android.contacts.common.test.NeededForTesting;
+import android.util.Log;
+
+import com.android.contacts.common.util.StopWatch;
+import com.android.dialer.dialpad.SmartDialNameMatcher;
+import com.android.dialer.dialpad.SmartDialPrefix;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Database helper for smart dial. Designed as a singleton to make sure there is
+ * only one access point to the database. Provides methods to maintain, update,
+ * and query the database.
+ */
+public class DialerDatabaseHelper extends SQLiteOpenHelper {
+    private static final String TAG = "DialerDatabaseHelper";
+    private static final boolean DEBUG = false;
+
+    private static DialerDatabaseHelper sSingleton = null;
+
+    private static final Object mLock = new Object();
+    private static final AtomicBoolean sInUpdate = new AtomicBoolean(false);
+    private final Context mContext;
+
+    /**
+     * SmartDial DB version ranges:
+     * <pre>
+     *   0-98   KeyLimePie
+     * </pre>
+     */
+    private static final int DATABASE_VERSION = 1;
+    private static final String SMARTDIAL_DATABASE_NAME = "dialer.db";
+
+    /**
+     * Saves the last update time of smart dial databases to shared preferences.
+     */
+    private static final String DATABASE_LAST_CREATED_SHARED_PREF = "com.android.dialer_smartdial";
+    private static final String LAST_UPDATED_MILLIS = "last_updated_millis";
+
+    private static final int MAX_ENTRIES = 3;
+
+    public interface Tables {
+        /** Saves the necessary smart dial information of all contacts. */
+        static final String SMARTDIAL_TABLE = "smartdial_table";
+        /** Saves all possible prefixes to refer to a contacts.*/
+        static final String PREFIX_TABLE = "prefix_table";
+    }
+
+    public interface SmartDialDbColumns {
+        static final String _ID = "id";
+        static final String NUMBER = "phone_number";
+        static final String CONTACT_ID = "contact_id";
+        static final String LOOKUP_KEY = "lookup_key";
+        static final String DISPLAY_NAME_PRIMARY = "display_name";
+        static final String LAST_TIME_USED = "last_time_used";
+        static final String TIMES_USED = "times_used";
+        static final String STARRED = "starred";
+        static final String IS_SUPER_PRIMARY = "is_super_primary";
+        static final String IN_VISIBLE_GROUP = "in_visible_group";
+        static final String IS_PRIMARY = "is_primary";
+        static final String LAST_SMARTDIAL_UPDATE_TIME = "last_smartdial_update_time";
+    }
+
+    public static interface PrefixColumns extends BaseColumns {
+        static final String PREFIX = "prefix";
+        static final String CONTACT_ID = "contact_id";
+    }
+
+    /** Query options for querying the contact database.*/
+    public static interface PhoneQuery {
+       static final Uri URI = Phone.CONTENT_URI.buildUpon().
+               appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                       String.valueOf(Directory.DEFAULT)).
+               appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").
+               build();
+
+       static final String[] PROJECTION = new String[] {
+            Phone._ID,                          // 0
+            Phone.TYPE,                         // 1
+            Phone.LABEL,                        // 2
+            Phone.NUMBER,                       // 3
+            Phone.CONTACT_ID,                   // 4
+            Phone.LOOKUP_KEY,                   // 5
+            Phone.DISPLAY_NAME_PRIMARY,         // 6
+            Data.LAST_TIME_USED,                // 7
+            Data.TIMES_USED,                    // 8
+            Contacts.STARRED,                   // 9
+            Data.IS_SUPER_PRIMARY,              // 10
+            Contacts.IN_VISIBLE_GROUP,          // 11
+            Data.IS_PRIMARY,                    // 12
+        };
+
+        static final int PHONE_ID = 0;
+        static final int PHONE_TYPE = 1;
+        static final int PHONE_LABEL = 2;
+        static final int PHONE_NUMBER = 3;
+        static final int PHONE_CONTACT_ID = 4;
+        static final int PHONE_LOOKUP_KEY = 5;
+        static final int PHONE_DISPLAY_NAME = 6;
+        static final int PHONE_LAST_TIME_USED = 7;
+        static final int PHONE_TIMES_USED = 8;
+        static final int PHONE_STARRED = 9;
+        static final int PHONE_IS_SUPER_PRIMARY = 10;
+        static final int PHONE_IN_VISIBLE_GROUP = 11;
+        static final int PHONE_IS_PRIMARY = 12;
+
+        /** Selects only rows that have been updated after a certain time stamp.*/
+        static final String SELECT_UPDATED_CLAUSE =
+                Phone.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?";
+    }
+
+    /** Query options for querying the deleted contact database.*/
+    public static interface DeleteContactQuery {
+       static final Uri URI = ContactsContract.DeletedContacts.CONTENT_URI;
+
+       static final String[] PROJECTION = new String[] {
+            ContactsContract.DeletedContacts.CONTACT_ID,                          // 0
+            ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP,           // 1
+        };
+
+        static final int DELETED_CONTACT_ID = 0;
+        static final int DELECTED_TIMESTAMP = 1;
+
+        /** Selects only rows that have been deleted after a certain time stamp.*/
+        public static final String SELECT_UPDATED_CLAUSE =
+                ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?";
+    }
+
+    /**
+     * Gets the sorting order for the smartdial table. This computes a SQL "ORDER BY" argument by
+     * composing contact status and recent contact details together.
+     */
+    private static interface SmartDialSortingOrder {
+        /** Current contacts - those contacted within the last 3 days (in milliseconds) */
+        static final long LAST_TIME_USED_CURRENT_MS = 3L * 24 * 60 * 60 * 1000;
+        /** Recent contacts - those contacted within the last 30 days (in milliseconds) */
+        static final long LAST_TIME_USED_RECENT_MS = 30L * 24 * 60 * 60 * 1000;
+
+        /** Time since last contact. */
+        static final String TIME_SINCE_LAST_USED_MS = "( ?1 - " +
+                Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.LAST_TIME_USED + ")";
+
+        /** Contacts that have been used in the past 3 days rank higher than contacts that have
+         * been used in the past 30 days, which rank higher than contacts that have not been used
+         * in recent 30 days.
+         */
+        static final String SORT_BY_DATA_USAGE =
+                "(CASE WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_CURRENT_MS +
+                " THEN 0 " +
+                " WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_RECENT_MS +
+                " THEN 1 " +
+                " ELSE 2 END)";
+
+        /** This sort order is similar to that used by the ContactsProvider when returning a list
+         * of frequently called contacts.
+         */
+        static final String SORT_ORDER =
+                Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.STARRED + " DESC, "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IS_SUPER_PRIMARY + " DESC, "
+                + SORT_BY_DATA_USAGE + ", "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.TIMES_USED + " DESC, "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IN_VISIBLE_GROUP + " DESC, "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.CONTACT_ID + ", "
+                + Tables.SMARTDIAL_TABLE + "." + SmartDialDbColumns.IS_PRIMARY + " DESC";
+    }
+
+    /**
+     * Simple data format for a contact, containing only information needed for showing up in
+     * smart dial interface.
+     */
+    public static class ContactNumber {
+        public final String displayName;
+        public final String lookupKey;
+        public final long id;
+        public final String phoneNumber;
+
+        public ContactNumber(long id, String displayName, String phoneNumber, String lookupKey) {
+            this.displayName = displayName;
+            this.lookupKey = lookupKey;
+            this.id = id;
+            this.phoneNumber = phoneNumber;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(displayName, id, lookupKey, phoneNumber);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object instanceof ContactNumber) {
+                final ContactNumber that = (ContactNumber) object;
+                return Objects.equal(this.displayName, that.displayName)
+                        && Objects.equal(this.id, that.id)
+                        && Objects.equal(this.lookupKey, that.lookupKey)
+                        && Objects.equal(this.phoneNumber, that.phoneNumber);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Data format for finding duplicated contacts.
+     */
+    private class ContactMatch {
+        private final String lookupKey;
+        private final long id;
+
+        public ContactMatch(String lookupKey, long id) {
+            this.lookupKey = lookupKey;
+            this.id = id;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(lookupKey, id);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object instanceof ContactMatch) {
+                final ContactMatch that = (ContactMatch) object;
+                return Objects.equal(this.lookupKey, that.lookupKey)
+                        && Objects.equal(this.id, that.id);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Access function to get the singleton instance of DialerDatabaseHelper.
+     */
+    public static synchronized DialerDatabaseHelper getInstance(Context context) {
+        if (DEBUG) {
+            Log.v(TAG, "Getting Instance");
+        }
+        if (sSingleton == null) {
+            sSingleton = new DialerDatabaseHelper(context, SMARTDIAL_DATABASE_NAME);
+        }
+        return sSingleton;
+    }
+
+    /**
+     * Returns a new instance for unit tests. The database will be created in memory.
+     */
+    @NeededForTesting
+    static DialerDatabaseHelper getNewInstanceForTest(Context context) {
+        return new DialerDatabaseHelper(context, null);
+    }
+
+    protected DialerDatabaseHelper(Context context, String databaseName) {
+        super(context, databaseName, null, DATABASE_VERSION);
+        mContext = Preconditions.checkNotNull(context, "Context must not be null");
+    }
+
+    /**
+     * Creates tables in the database when database is created for the first time.
+     *
+     * @param db The database.
+     */
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL("CREATE TABLE " + Tables.SMARTDIAL_TABLE + " (" +
+                SmartDialDbColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                SmartDialDbColumns.NUMBER + " TEXT," +
+                SmartDialDbColumns.CONTACT_ID + " INTEGER," +
+                SmartDialDbColumns.LOOKUP_KEY + " TEXT," +
+                SmartDialDbColumns.DISPLAY_NAME_PRIMARY + " TEXT, " +
+                SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " LONG, " +
+                SmartDialDbColumns.LAST_TIME_USED + " LONG, " +
+                SmartDialDbColumns.TIMES_USED + " INTEGER, " +
+                SmartDialDbColumns.STARRED + " INTEGER, " +
+                SmartDialDbColumns.IS_SUPER_PRIMARY + " INTEGER, " +
+                SmartDialDbColumns.IN_VISIBLE_GROUP + " INTEGER, " +
+                SmartDialDbColumns.IS_PRIMARY + " INTEGER" +
+        ");");
+
+        db.execSQL("CREATE TABLE " + Tables.PREFIX_TABLE + " (" +
+                PrefixColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                PrefixColumns.PREFIX + " TEXT COLLATE NOCASE, " +
+                PrefixColumns.CONTACT_ID + " INTEGER" +
+                ");");
+    }
+
+    /**
+     * Starts the database upgrade process in the background.
+     */
+    public void startSmartDialUpdateThread() {
+       new SmartDialUpdateAsyncTask().execute();
+    }
+
+    private class SmartDialUpdateAsyncTask extends AsyncTask {
+        @Override
+        protected Object doInBackground(Object[] objects) {
+            if (DEBUG) {
+                Log.v(TAG, "Updating database");
+            }
+            updateSmartDialDatabase();
+            return null;
+        }
+
+        @Override
+        protected void onCancelled() {
+            if (DEBUG) {
+                Log.v(TAG, "Updating Cancelled");
+            }
+            super.onCancelled();
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            if (DEBUG) {
+                Log.v(TAG, "Updating Finished");
+            }
+            super.onPostExecute(o);
+        }
+    }
+    /**
+     * Removes rows in the smartdial database that matches the contacts that have been deleted
+     * by other apps since last update.
+     *
+     * @param db Database pointer to the dialer database.
+     * @param last_update_time Time stamp of last update on the smartdial database
+     */
+    private void removeDeletedContacts(SQLiteDatabase db, String last_update_time) {
+        final Cursor deletedContactCursor = mContext.getContentResolver().query(
+                DeleteContactQuery.URI,
+                DeleteContactQuery.PROJECTION,
+                DeleteContactQuery.SELECT_UPDATED_CLAUSE,
+                new String[] {last_update_time}, null);
+
+        db.beginTransaction();
+        try {
+            while (deletedContactCursor.moveToNext()) {
+                final Long deleteContactId =
+                        deletedContactCursor.getLong(DeleteContactQuery.DELETED_CONTACT_ID);
+                db.delete(Tables.SMARTDIAL_TABLE,
+                        SmartDialDbColumns.CONTACT_ID + "=" + deleteContactId, null);
+                db.delete(Tables.PREFIX_TABLE,
+                        PrefixColumns.CONTACT_ID + "=" + deleteContactId, null);
+            }
+
+            db.setTransactionSuccessful();
+        } finally {
+            deletedContactCursor.close();
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Removes potentially corrupted entries in the database. These contacts may be added before
+     * the previous instance of the dialer was destroyed for some reason. For data integrity, we
+     * delete all of them.
+
+     * @param db Database pointer to the dialer database.
+     * @param last_update_time Time stamp of last successful update of the dialer database.
+     */
+    private void removePotentiallyCorruptedContacts(SQLiteDatabase db, String last_update_time) {
+        db.delete(Tables.PREFIX_TABLE,
+                PrefixColumns.CONTACT_ID + " IN " +
+                "(SELECT " + SmartDialDbColumns.CONTACT_ID + " FROM " + Tables.SMARTDIAL_TABLE +
+                " WHERE " + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " > " +
+                last_update_time + ")",
+                null);
+        db.delete(Tables.SMARTDIAL_TABLE,
+                SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + " > " + last_update_time, null);
+    }
+
+    /**
+     * Removes all entries in the smartdial contact database.
+     */
+    @VisibleForTesting
+    void removeAllContacts(SQLiteDatabase db) {
+        db.delete(Tables.SMARTDIAL_TABLE, null, null);
+        db.delete(Tables.PREFIX_TABLE, null, null);
+    }
+
+    /**
+     * Counts number of rows of the prefix table.
+     */
+    @VisibleForTesting
+    int countPrefixTableRows(SQLiteDatabase db) {
+        return (int)DatabaseUtils.longForQuery(db, "SELECT COUNT(1) FROM " + Tables.PREFIX_TABLE,
+                null);
+    }
+
+    /**
+     * Removes rows in the smartdial database that matches updated contacts.
+     *
+     * @param db Database pointer to the smartdial database
+     * @param updatedContactCursor Cursor pointing to the list of recently updated contacts.
+     */
+    private void removeUpdatedContacts(SQLiteDatabase db, Cursor updatedContactCursor) {
+        db.beginTransaction();
+        try {
+            while (updatedContactCursor.moveToNext()) {
+                final Long contactId = updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID);
+
+                db.delete(Tables.SMARTDIAL_TABLE, SmartDialDbColumns.CONTACT_ID + "=" +
+                        contactId, null);
+                db.delete(Tables.PREFIX_TABLE, PrefixColumns.CONTACT_ID + "=" +
+                        contactId, null);
+            }
+
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Inserts updated contacts as rows to the smartdial table.
+     *
+     * @param db Database pointer to the smartdial database.
+     * @param updatedContactCursor Cursor pointing to the list of recently updated contacts.
+     * @param currentMillis Current time to be recorded in the smartdial table as update timestamp.
+     */
+    @VisibleForTesting
+    protected void insertUpdatedContactsAndNumberPrefix(SQLiteDatabase db,
+            Cursor updatedContactCursor, Long currentMillis) {
+        db.beginTransaction();
+        try {
+            final String sqlInsert = "INSERT INTO " + Tables.SMARTDIAL_TABLE + " (" +
+                    SmartDialDbColumns.NUMBER + ", " +
+                    SmartDialDbColumns.CONTACT_ID + ", " +
+                    SmartDialDbColumns.LOOKUP_KEY + ", " +
+                    SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " +
+                    SmartDialDbColumns.LAST_TIME_USED + ", " +
+                    SmartDialDbColumns.TIMES_USED + ", " +
+                    SmartDialDbColumns.STARRED + ", " +
+                    SmartDialDbColumns.IS_SUPER_PRIMARY + ", " +
+                    SmartDialDbColumns.IN_VISIBLE_GROUP+ ", " +
+                    SmartDialDbColumns.IS_PRIMARY + ", " +
+                    SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ") " +
+                    " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+            final SQLiteStatement insert = db.compileStatement(sqlInsert);
+
+            final String numberSqlInsert = "INSERT INTO " + Tables.PREFIX_TABLE + " (" +
+                    PrefixColumns.CONTACT_ID + ", " +
+                    PrefixColumns.PREFIX  + ") " +
+                    " VALUES (?, ?)";
+            final SQLiteStatement numberInsert = db.compileStatement(numberSqlInsert);
+
+            updatedContactCursor.moveToPosition(-1);
+            while (updatedContactCursor.moveToNext()) {
+                insert.bindString(1, updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER));
+                insert.bindLong(2, updatedContactCursor.getLong(PhoneQuery.PHONE_CONTACT_ID));
+                insert.bindString(3, updatedContactCursor.getString(PhoneQuery.PHONE_LOOKUP_KEY));
+                insert.bindString(4, updatedContactCursor.getString(PhoneQuery.PHONE_DISPLAY_NAME));
+                insert.bindLong(5, updatedContactCursor.getLong(PhoneQuery.PHONE_LAST_TIME_USED));
+                insert.bindLong(6, updatedContactCursor.getInt(PhoneQuery.PHONE_TIMES_USED));
+                insert.bindLong(7, updatedContactCursor.getInt(PhoneQuery.PHONE_STARRED));
+                insert.bindLong(8, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_SUPER_PRIMARY));
+                insert.bindLong(9, updatedContactCursor.getInt(PhoneQuery.PHONE_IN_VISIBLE_GROUP));
+                insert.bindLong(10, updatedContactCursor.getInt(PhoneQuery.PHONE_IS_PRIMARY));
+                insert.bindLong(11, currentMillis);
+                insert.executeInsert();
+                insert.clearBindings();
+
+                final String contactPhoneNumber =
+                        updatedContactCursor.getString(PhoneQuery.PHONE_NUMBER);
+                final ArrayList<String> numberPrefixes =
+                        SmartDialPrefix.parseToNumberTokens(contactPhoneNumber);
+
+                for (String numberPrefix : numberPrefixes) {
+                    numberInsert.bindLong(1, updatedContactCursor.getLong(
+                            PhoneQuery.PHONE_CONTACT_ID));
+                    numberInsert.bindString(2, numberPrefix);
+                    numberInsert.executeInsert();
+                    numberInsert.clearBindings();
+                }
+            }
+
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Inserts prefixes of contact names to the prefix table.
+     *
+     * @param db Database pointer to the smartdial database.
+     * @param nameCursor Cursor pointing to the list of distinct updated contacts.
+     */
+    @VisibleForTesting
+    void insertNamePrefixes(SQLiteDatabase db, Cursor nameCursor) {
+        final int columnIndexName = nameCursor.getColumnIndex(
+                SmartDialDbColumns.DISPLAY_NAME_PRIMARY);
+        final int columnIndexContactId = nameCursor.getColumnIndex(SmartDialDbColumns.CONTACT_ID);
+
+        db.beginTransaction();
+        try {
+            final String sqlInsert = "INSERT INTO " + Tables.PREFIX_TABLE + " (" +
+                    PrefixColumns.CONTACT_ID + ", " +
+                    PrefixColumns.PREFIX  + ") " +
+                    " VALUES (?, ?)";
+            final SQLiteStatement insert = db.compileStatement(sqlInsert);
+
+            while (nameCursor.moveToNext()) {
+                /** Computes a list of prefixes of a given contact name. */
+                final ArrayList<String> namePrefixes =
+                        SmartDialPrefix.generateNamePrefixes(nameCursor.getString(columnIndexName));
+
+                for (String namePrefix : namePrefixes) {
+                    insert.bindLong(1, nameCursor.getLong(columnIndexContactId));
+                    insert.bindString(2, namePrefix);
+                    insert.executeInsert();
+                    insert.clearBindings();
+                }
+            }
+
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    /**
+     * Updates the smart dial and prefix database.
+     * This method queries the Delta API to get changed contacts since last update, and updates the
+     * records in smartdial database and prefix database accordingly.
+     * It also queries the deleted contact database to remove newly deleted contacts since last
+     * update.
+     */
+    public void updateSmartDialDatabase() {
+        final SQLiteDatabase db = getWritableDatabase();
+
+        synchronized(mLock) {
+            if (DEBUG) {
+                Log.v(TAG, "Starting to update database");
+            }
+            final StopWatch stopWatch = DEBUG ? StopWatch.start("Updating databases") : null;
+
+            /** Gets the last update time on the database. */
+            final SharedPreferences databaseLastUpdateSharedPref = mContext.getSharedPreferences(
+                    DATABASE_LAST_CREATED_SHARED_PREF, Context.MODE_PRIVATE);
+            final String lastUpdateMillis = String.valueOf(
+                    databaseLastUpdateSharedPref.getLong(LAST_UPDATED_MILLIS, 0));
+
+            if (DEBUG) {
+                Log.v(TAG, "Last updated at " + lastUpdateMillis);
+            }
+            /** Queries the contact database to get contacts that have been updated since the last
+             * update time.
+             */
+            final Cursor updatedContactCursor = mContext.getContentResolver().query(PhoneQuery.URI,
+                    PhoneQuery.PROJECTION, PhoneQuery.SELECT_UPDATED_CLAUSE,
+                    new String[]{lastUpdateMillis}, null);
+
+            /** Sets the time after querying the database as the current update time. */
+            final Long currentMillis = System.currentTimeMillis();
+
+            if (DEBUG) {
+                stopWatch.lap("Queried the Contacts database");
+            }
+
+            if (updatedContactCursor == null) {
+                if (DEBUG) {
+                    Log.e(TAG, "SmartDial query received null for cursor");
+                }
+                return;
+            }
+
+            /** Prevents the app from reading the dialer database when updating. */
+            sInUpdate.getAndSet(true);
+
+            /** Removes contacts that have been deleted. */
+            removeDeletedContacts(db, lastUpdateMillis);
+            removePotentiallyCorruptedContacts(db, lastUpdateMillis);
+
+            if (DEBUG) {
+                stopWatch.lap("Finished deleting deleted entries");
+            }
+
+            try {
+                /** If the database did not exist before, jump through deletion as there is nothing
+                 * to delete.
+                 */
+                if (!lastUpdateMillis.equals("0")) {
+                    /** Removes contacts that have been updated. Updated contact information will be
+                     * inserted later.
+                     */
+                    removeUpdatedContacts(db, updatedContactCursor);
+                    if (DEBUG) {
+                        stopWatch.lap("Finished deleting updated entries");
+                    }
+                }
+
+                /** Inserts recently updated contacts to the smartdial database.*/
+                insertUpdatedContactsAndNumberPrefix(db, updatedContactCursor, currentMillis);
+                if (DEBUG) {
+                    stopWatch.lap("Finished building the smart dial table");
+                }
+            } finally {
+                /** Inserts prefixes of phone numbers into the prefix table.*/
+                updatedContactCursor.close();
+            }
+
+            /** Gets a list of distinct contacts which have been updated, and adds the name prefixes
+             * of these contacts to the prefix table.
+             */
+            final Cursor nameCursor = db.rawQuery(
+                    "SELECT DISTINCT " +
+                    SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " + SmartDialDbColumns.CONTACT_ID +
+                    " FROM " + Tables.SMARTDIAL_TABLE +
+                    " WHERE " + SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME +
+                    " = " + Long.toString(currentMillis),
+                    new String[] {});
+            if (DEBUG) {
+                stopWatch.lap("Queried the smart dial table for contact names");
+            }
+
+            if (nameCursor != null) {
+                try {
+                    /** Inserts prefixes of names into the prefix table.*/
+                    insertNamePrefixes(db, nameCursor);
+                    if (DEBUG) {
+                        stopWatch.lap("Finished building the name prefix table");
+                    }
+                } finally {
+                    nameCursor.close();
+                }
+            }
+
+            /** Creates index on contact_id for fast JOIN operation. */
+            db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_contact_id_index ON " +
+                    Tables.SMARTDIAL_TABLE + " (" + SmartDialDbColumns.CONTACT_ID  + ");");
+            /** Creates index on last_smartdial_update_time for fast SELECT operation. */
+            db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_last_update_index ON " +
+                    Tables.SMARTDIAL_TABLE + " (" +
+                    SmartDialDbColumns.LAST_SMARTDIAL_UPDATE_TIME + ");");
+            /** Creates index on sorting fields for fast sort operation. */
+            db.execSQL("CREATE INDEX IF NOT EXISTS smartdial_sort_index ON " +
+                    Tables.SMARTDIAL_TABLE + " (" +
+                    SmartDialDbColumns.STARRED + ", " +
+                    SmartDialDbColumns.IS_SUPER_PRIMARY + ", " +
+                    SmartDialDbColumns.LAST_TIME_USED + ", " +
+                    SmartDialDbColumns.TIMES_USED + ", " +
+                    SmartDialDbColumns.IN_VISIBLE_GROUP +  ", " +
+                    SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " +
+                    SmartDialDbColumns.CONTACT_ID + ", " +
+                    SmartDialDbColumns.IS_PRIMARY +
+                    ");");
+            /** Creates index on prefix for fast SELECT operation. */
+            db.execSQL("CREATE INDEX IF NOT EXISTS nameprefix_index ON " +
+                    Tables.PREFIX_TABLE + " (" + PrefixColumns.PREFIX + ");");
+            /** Creates index on contact_id for fast JOIN operation. */
+            db.execSQL("CREATE INDEX IF NOT EXISTS nameprefix_contact_id_index ON " +
+                    Tables.PREFIX_TABLE + " (" + PrefixColumns.CONTACT_ID + ");");
+
+            if (DEBUG) {
+                stopWatch.lap(TAG + "Finished recreating index");
+            }
+
+            /** Updates the database index statistics.*/
+            db.execSQL("ANALYZE " + Tables.SMARTDIAL_TABLE);
+            db.execSQL("ANALYZE " + Tables.PREFIX_TABLE);
+            db.execSQL("ANALYZE smartdial_contact_id_index");
+            db.execSQL("ANALYZE smartdial_last_update_index");
+            db.execSQL("ANALYZE nameprefix_index");
+            db.execSQL("ANALYZE nameprefix_contact_id_index");
+            if (DEBUG) {
+                stopWatch.stopAndLog(TAG + "Finished updating index stats", 0);
+            }
+
+            sInUpdate.getAndSet(false);
+
+            final SharedPreferences.Editor editor = databaseLastUpdateSharedPref.edit();
+            editor.putLong(LAST_UPDATED_MILLIS, currentMillis);
+            editor.commit();
+        }
+    }
+
+
+    /**
+     * Returns a list of candidate contacts where the query is a prefix of the dialpad index of
+     * the contact's name or phone number.
+     *
+     * @param query The prefix of a contact's dialpad index.
+     * @return A list of top candidate contacts that will be suggested to user to match their input.
+     */
+    public ArrayList<ContactNumber>  getLooseMatches(String query,
+            SmartDialNameMatcher nameMatcher) {
+        final boolean inUpdate = sInUpdate.get();
+        if (inUpdate) {
+            return Lists.newArrayList();
+        }
+
+        final SQLiteDatabase db = getReadableDatabase();
+
+        /** Uses SQL query wildcard '%' to represent prefix matching.*/
+        final String looseQuery = query + "%";
+
+        final ArrayList<ContactNumber> result = Lists.newArrayList();
+
+        final StopWatch stopWatch = DEBUG ? StopWatch.start(":Name Prefix query") : null;
+
+        final String currentTimeStamp = Long.toString(System.currentTimeMillis());
+
+        /** Queries the database to find contacts that have an index matching the query prefix. */
+        final Cursor cursor = db.rawQuery("SELECT " +
+                SmartDialDbColumns.DISPLAY_NAME_PRIMARY + ", " +
+                SmartDialDbColumns.NUMBER + ", " +
+                SmartDialDbColumns.CONTACT_ID + ", " +
+                SmartDialDbColumns.LOOKUP_KEY +
+                " FROM " + Tables.SMARTDIAL_TABLE + " WHERE " +
+                SmartDialDbColumns.CONTACT_ID + " IN " +
+                    " (SELECT " + PrefixColumns.CONTACT_ID +
+                    " FROM " + Tables.PREFIX_TABLE +
+                    " WHERE " + Tables.PREFIX_TABLE + "." + PrefixColumns.PREFIX +
+                    " LIKE '" + looseQuery + "')" +
+                " ORDER BY " + SmartDialSortingOrder.SORT_ORDER,
+                new String[] {currentTimeStamp});
+
+        if (DEBUG) {
+            stopWatch.lap("Prefix query completed");
+        }
+
+        /** Gets the column ID from the cursor.*/
+        final int columnDisplayNamePrimary = 0;
+        final int columnNumber = 1;
+        final int columnId = 2;
+        final int columnLookupKey = 3;
+        if (DEBUG) {
+            stopWatch.lap("Found column IDs");
+        }
+
+        final Set<ContactMatch> duplicates = new HashSet<ContactMatch>();
+        int counter = 0;
+        try {
+            if (DEBUG) {
+                stopWatch.lap("Moved cursor to start");
+            }
+            /** Iterates the cursor to find top contact suggestions without duplication.*/
+            while ((cursor.moveToNext()) && (counter < MAX_ENTRIES)) {
+                final String displayName = cursor.getString(columnDisplayNamePrimary);
+                final String phoneNumber = cursor.getString(columnNumber);
+                final long id = cursor.getLong(columnId);
+                final String lookupKey = cursor.getString(columnLookupKey);
+
+                /** If a contact already exists and another phone number of the contact is being
+                 * processed, skip the second instance.
+                 */
+                final ContactMatch contactMatch = new ContactMatch(lookupKey, id);
+                if (duplicates.contains(contactMatch)) {
+                    continue;
+                }
+
+                /**
+                 * If the contact has either the name or number that matches the query, add to the
+                 * result.
+                 */
+                if (nameMatcher.matches(displayName) ||
+                        nameMatcher.matchesNumber(phoneNumber, query) != null) {
+                    /** If a contact has not been added, add it to the result and the hash set.*/
+                    duplicates.add(contactMatch);
+                    result.add(new ContactNumber(id, displayName, phoneNumber, lookupKey));
+                    counter++;
+                    if (DEBUG) {
+                        stopWatch.lap("Added one result");
+                    }
+                }
+            }
+
+            if (DEBUG) {
+                stopWatch.stopAndLog(TAG + "Finished loading cursor", 0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return result;
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+    }
+}
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index a8984bd..f783d27 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -26,7 +26,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -35,7 +34,6 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -62,12 +60,10 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.PopupMenu;
 import android.widget.RelativeLayout;
@@ -82,6 +78,7 @@
 import com.android.dialer.DialtactsActivity;
 import com.android.dialer.R;
 import com.android.dialer.SpecialCharSequenceMgr;
+import com.android.dialer.database.DialerDatabaseHelper;
 import com.android.dialer.interactions.PhoneNumberInteraction;
 import com.android.dialer.util.OrientationUtil;
 import com.android.internal.telephony.ITelephony;
@@ -156,8 +153,6 @@
      */
     private SmartDialController mSmartDialAdapter;
 
-    private SmartDialCache mSmartDialCache;
-
     /**
      * Use latin character map by default
      */
@@ -169,6 +164,8 @@
      */
     private boolean mSmartDialEnabled = false;
 
+    private DialerDatabaseHelper mDialerDatabaseHelper;
+
     /**
      * Regular expression prohibiting manual phone call. Can be empty, which means "no rule".
      */
@@ -300,6 +297,10 @@
         mFirstLaunch = true;
         mContactsPrefs = new ContactsPreferences(getActivity());
         mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
+
+        mDialerDatabaseHelper = DialerDatabaseHelper.getInstance(getActivity());
+        SmartDialPrefix.initializeNanpSettings(getActivity());
+
         try {
             mHaptic.init(getActivity(),
                          getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
@@ -1653,20 +1654,6 @@
         return intent;
     }
 
-    @Override
-    public void setUserVisibleHint(boolean isVisibleToUser) {
-        super.setUserVisibleHint(isVisibleToUser);
-        if (mSmartDialEnabled && isVisibleToUser && mSmartDialCache != null) {
-            // This is called every time the dialpad fragment comes into view. The first
-            // time the dialer is launched, mSmartDialEnabled is always false as it has not been
-            // read from settings(in onResume) yet at the point where setUserVisibleHint is called
-            // for the first time, so the caching on first launch will happen in onResume instead.
-            // This covers only the case where the dialer is launched in the call log or
-            // contacts tab, and then the user swipes to the dialpad.
-            mSmartDialCache.cacheIfNeeded(false);
-        }
-    }
-
     private String mLastDigitsForSmartDial;
 
     private void loadSmartDialEntries() {
@@ -1675,11 +1662,6 @@
             return;
         }
 
-        if (mSmartDialCache == null) {
-            Log.e(TAG, "Trying to load smart dialing entries from a null cache");
-            return;
-        }
-
         // Update only when the digits have changed.
         final String digits = SmartDialNameMatcher.normalizeNumber(mDigits.getText().toString(),
                 mSmartDialMap);
@@ -1691,7 +1673,7 @@
         if (digits.length() < 1) {
             mSmartDialAdapter.clear();
         } else {
-            final SmartDialLoaderTask task = new SmartDialLoaderTask(this, digits, mSmartDialCache);
+            final SmartDialLoaderTask task = new SmartDialLoaderTask(this, digits, getActivity());
             task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new String[] {});
         }
     }
@@ -1708,24 +1690,15 @@
         // Handle smart dialing related state
         if (mSmartDialEnabled) {
             mSmartDialContainer.setVisibility(View.VISIBLE);
-            mSmartDialCache = SmartDialCache.getInstance(getActivity(),
-                    mContactsPrefs.getDisplayOrder(), mSmartDialMap);
-            // Don't force recache if this is the first time onResume is being called, since
-            // caching should already happen in setUserVisibleHint.
-            if (!mFirstLaunch || getUserVisibleHint()) {
-                // This forced recache covers the cases where the dialer was running before and
-                // was brought back into the foreground, or the dialer was launched for the first
-                // time and displays the dialpad fragment immediately. If the dialpad fragment
-                // hasn't actually become visible throughout the entire activity's lifecycle, it
-                // is possible that caching hasn't happened yet. In this case, we can force a
-                // recache anyway, since we are not worried about startup performance anymore.
-                mSmartDialCache.cacheIfNeeded(true);
+
+            if (DEBUG) {
+                Log.w(TAG, "Creating smart dial database");
             }
+            mDialerDatabaseHelper.startSmartDialUpdateThread();
         } else {
             if (mSmartDialContainer != null) {
                 mSmartDialContainer.setVisibility(View.GONE);
             }
-            mSmartDialCache = null;
         }
     }
 
diff --git a/src/com/android/dialer/dialpad/SmartDialCache.java b/src/com/android/dialer/dialpad/SmartDialCache.java
deleted file mode 100644
index 3d4a563..0000000
--- a/src/com/android/dialer/dialpad/SmartDialCache.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.dialpad;
-
-import static com.android.dialer.dialpad.SmartDialController.LOG_TAG;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.preference.PreferenceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Directory;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.contacts.common.util.StopWatch;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Cache object used to cache Smart Dial contacts that handles various states of the cache at the
- * point in time when getContacts() is called
- * 1) Cache is currently empty and there is no caching thread running - getContacts() starts a
- * caching thread and returns the cache when completed
- * 2) The cache is currently empty, but a caching thread has been started - getContacts() waits
- * till the existing caching thread is completed before immediately returning the cache
- * 3) The cache has already been populated, and there is no caching thread running - getContacts()
- * returns the existing cache immediately
- * 4) The cache has already been populated, but there is another caching thread running (due to
- * a forced cache refresh due to content updates - getContacts() returns the existing cache
- * immediately
- */
-public class SmartDialCache {
-
-    public static class ContactNumber {
-        public final String displayName;
-        public final String lookupKey;
-        public final long id;
-        public final int affinity;
-        public final String phoneNumber;
-
-        public ContactNumber(long id, String displayName, String phoneNumber, String lookupKey,
-                int affinity) {
-            this.displayName = displayName;
-            this.lookupKey = lookupKey;
-            this.id = id;
-            this.affinity = affinity;
-            this.phoneNumber = phoneNumber;
-        }
-    }
-
-    public static interface PhoneQuery {
-
-       Uri URI = Phone.CONTENT_URI.buildUpon().
-               appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
-               String.valueOf(Directory.DEFAULT)).
-               appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").
-               build();
-
-       final String[] PROJECTION_PRIMARY = new String[] {
-            Phone._ID,                          // 0
-            Phone.TYPE,                         // 1
-            Phone.LABEL,                        // 2
-            Phone.NUMBER,                       // 3
-            Phone.CONTACT_ID,                   // 4
-            Phone.LOOKUP_KEY,                   // 5
-            Phone.DISPLAY_NAME_PRIMARY,         // 6
-        };
-
-        final String[] PROJECTION_ALTERNATIVE = new String[] {
-            Phone._ID,                          // 0
-            Phone.TYPE,                         // 1
-            Phone.LABEL,                        // 2
-            Phone.NUMBER,                       // 3
-            Phone.CONTACT_ID,                   // 4
-            Phone.LOOKUP_KEY,                   // 5
-            Phone.DISPLAY_NAME_ALTERNATIVE,     // 6
-        };
-
-        public static final int PHONE_ID           = 0;
-        public static final int PHONE_TYPE         = 1;
-        public static final int PHONE_LABEL        = 2;
-        public static final int PHONE_NUMBER       = 3;
-        public static final int PHONE_CONTACT_ID   = 4;
-        public static final int PHONE_LOOKUP_KEY   = 5;
-        public static final int PHONE_DISPLAY_NAME = 6;
-
-        // Current contacts - those contacted within the last 3 days (in milliseconds)
-        final static long LAST_TIME_USED_CURRENT_MS = 3L * 24 * 60 * 60 * 1000;
-
-        // Recent contacts - those contacted within the last 30 days (in milliseconds)
-        final static long LAST_TIME_USED_RECENT_MS = 30L * 24 * 60 * 60 * 1000;
-
-        final static String TIME_SINCE_LAST_USED_MS =
-                "(? - " + Data.LAST_TIME_USED + ")";
-
-        final static String SORT_BY_DATA_USAGE =
-                "(CASE WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_CURRENT_MS +
-                " THEN 0 " +
-                " WHEN " + TIME_SINCE_LAST_USED_MS + " < " + LAST_TIME_USED_RECENT_MS +
-                " THEN 1 " +
-                " ELSE 2 END), " +
-                Data.TIMES_USED + " DESC";
-
-        // This sort order is similar to that used by the ContactsProvider when returning a list
-        // of frequently called contacts.
-        public static final String SORT_ORDER =
-                Contacts.STARRED + " DESC, "
-                + Data.IS_SUPER_PRIMARY + " DESC, "
-                + SORT_BY_DATA_USAGE + ", "
-                + Contacts.IN_VISIBLE_GROUP + " DESC, "
-                + Contacts.DISPLAY_NAME + ", "
-                + Data.CONTACT_ID + ", "
-                + Data.IS_PRIMARY + " DESC";
-    }
-
-    // Static set used to determine which countries use NANP numbers
-    public static Set<String> sNanpCountries = null;
-
-    private SmartDialTrie mContactsCache;
-    private static AtomicInteger mCacheStatus;
-    private final SmartDialMap mMap;
-    private final int mNameDisplayOrder;
-    private final Context mContext;
-    private final static Object mLock = new Object();
-
-    /** The country code of the user's sim card obtained by calling getSimCountryIso*/
-    private static final String PREF_USER_SIM_COUNTRY_CODE =
-            "DialtactsActivity_user_sim_country_code";
-    private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null;
-
-    private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT;
-    private static boolean sUserInNanpRegion = false;
-
-    public static final int CACHE_NEEDS_RECACHE = 1;
-    public static final int CACHE_IN_PROGRESS = 2;
-    public static final int CACHE_COMPLETED = 3;
-
-    private static final boolean DEBUG = false;
-
-    private SmartDialCache(Context context, int nameDisplayOrder, SmartDialMap map) {
-        mNameDisplayOrder = nameDisplayOrder;
-        mMap = map;
-        Preconditions.checkNotNull(context, "Context must not be null");
-        mContext = context.getApplicationContext();
-        mCacheStatus = new AtomicInteger(CACHE_NEEDS_RECACHE);
-
-        final TelephonyManager manager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        if (manager != null) {
-            sUserSimCountryCode = manager.getSimCountryIso();
-        }
-
-        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
-        if (sUserSimCountryCode != null) {
-            // Update shared preferences with the latest country obtained from getSimCountryIso
-            prefs.edit().putString(PREF_USER_SIM_COUNTRY_CODE, sUserSimCountryCode).apply();
-        } else {
-            // Couldn't get the country from getSimCountryIso. Maybe we are in airplane mode.
-            // Try to load the settings, if any from SharedPreferences.
-            sUserSimCountryCode = prefs.getString(PREF_USER_SIM_COUNTRY_CODE,
-                    PREF_USER_SIM_COUNTRY_CODE_DEFAULT);
-        }
-
-        sUserInNanpRegion = isCountryNanp(sUserSimCountryCode);
-
-    }
-
-    private static SmartDialCache instance;
-
-    /**
-     * Returns an instance of SmartDialCache.
-     *
-     * @param context A context that provides a valid ContentResolver.
-     * @param nameDisplayOrder One of the two name display order integer constants (1 or 2) as saved
-     *        in settings under the key
-     *        {@link android.provider.ContactsContract.Preferences#DISPLAY_ORDER}.
-     * @return An instance of SmartDialCache
-     */
-    public static synchronized SmartDialCache getInstance(Context context, int nameDisplayOrder,
-            SmartDialMap map) {
-        if (instance == null) {
-            instance = new SmartDialCache(context, nameDisplayOrder, map);
-        }
-        return instance;
-    }
-
-    /**
-     * Performs a database query, iterates through the returned cursor and saves the retrieved
-     * contacts to a local cache.
-     */
-    private void cacheContacts(Context context) {
-        mCacheStatus.set(CACHE_IN_PROGRESS);
-        synchronized(mLock) {
-            if (DEBUG) {
-                Log.d(LOG_TAG, "Starting caching thread");
-            }
-            final StopWatch stopWatch = DEBUG ? StopWatch.start("SmartDial Cache") : null;
-            final String millis = String.valueOf(System.currentTimeMillis());
-            final Cursor c = context.getContentResolver().query(PhoneQuery.URI,
-                    (mNameDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY)
-                        ? PhoneQuery.PROJECTION_PRIMARY : PhoneQuery.PROJECTION_ALTERNATIVE,
-                    null, new String[] {millis, millis},
-                    PhoneQuery.SORT_ORDER);
-            if (DEBUG) {
-                stopWatch.lap("SmartDial query complete");
-            }
-            if (c == null) {
-                Log.w(LOG_TAG, "SmartDial query received null for cursor");
-                if (DEBUG) {
-                    stopWatch.stopAndLog("SmartDial query received null for cursor", 0);
-                }
-                mCacheStatus.getAndSet(CACHE_NEEDS_RECACHE);
-                return;
-            }
-            final SmartDialTrie cache = new SmartDialTrie(mMap, sUserInNanpRegion);
-            try {
-                c.moveToPosition(-1);
-                int affinityCount = 0;
-                while (c.moveToNext()) {
-                    final String displayName = c.getString(PhoneQuery.PHONE_DISPLAY_NAME);
-                    final String phoneNumber = c.getString(PhoneQuery.PHONE_NUMBER);
-                    final long id = c.getLong(PhoneQuery.PHONE_CONTACT_ID);
-                    final String lookupKey = c.getString(PhoneQuery.PHONE_LOOKUP_KEY);
-                    cache.put(new ContactNumber(id, displayName, phoneNumber, lookupKey,
-                            affinityCount));
-                    affinityCount++;
-                }
-            } finally {
-                c.close();
-                mContactsCache = cache;
-                if (DEBUG) {
-                    stopWatch.stopAndLog("SmartDial caching completed", 0);
-                }
-            }
-        }
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Caching thread completed");
-        }
-        mCacheStatus.getAndSet(CACHE_COMPLETED);
-    }
-
-    /**
-     * Returns the list of cached contacts. This is blocking so it should not be called from the UI
-     * thread. There are 4 possible scenarios:
-     *
-     * 1) Cache is currently empty and there is no caching thread running - getContacts() starts a
-     * caching thread and returns the cache when completed
-     * 2) The cache is currently empty, but a caching thread has been started - getContacts() waits
-     * till the existing caching thread is completed before immediately returning the cache
-     * 3) The cache has already been populated, and there is no caching thread running -
-     * getContacts() returns the existing cache immediately
-     * 4) The cache has already been populated, but there is another caching thread running (due to
-     * a forced cache refresh due to content updates - getContacts() returns the existing cache
-     * immediately
-     *
-     * @return List of already cached contacts, or an empty list if the caching failed for any
-     * reason.
-     */
-    public SmartDialTrie getContacts() {
-        // Either scenario 3 or 4 - This means just go ahead and return the existing cache
-        // immediately even if there is a caching thread currently running. We are guaranteed to
-        // have the newest value of mContactsCache at this point because it is volatile.
-        if (mContactsCache != null) {
-            return mContactsCache;
-        }
-        // At this point we are forced to wait for cacheContacts to complete in another thread(if
-        // one currently exists) because of mLock.
-        synchronized(mLock) {
-            // If mContactsCache is still null at this point, either there was never any caching
-            // process running, or it failed (Scenario 1). If so, just go ahead and try to cache
-            // the contacts again.
-            if (mContactsCache == null) {
-                cacheContacts(mContext);
-                return (mContactsCache == null) ? new SmartDialTrie() : mContactsCache;
-            } else {
-                // After waiting for the lock on mLock to be released, mContactsCache is now
-                // non-null due to the completion of the caching thread (Scenario 2). Go ahead
-                // and return the existing cache.
-                return mContactsCache;
-            }
-        }
-    }
-
-    /**
-     * Cache contacts only if there is a need to (forced cache refresh or no attempt to cache yet).
-     * This method is called in 2 places: whenever the DialpadFragment comes into view, and in
-     * onResume.
-     *
-     * @param forceRecache If true, force a cache refresh.
-     */
-
-    public void cacheIfNeeded(boolean forceRecache) {
-        if (DEBUG) {
-            Log.d("SmartDial", "cacheIfNeeded called with " + String.valueOf(forceRecache));
-        }
-        if (mCacheStatus.get() == CACHE_IN_PROGRESS) {
-            return;
-        }
-        if (forceRecache || mCacheStatus.get() == CACHE_NEEDS_RECACHE) {
-            // Because this method can be possibly be called multiple times in rapid succession,
-            // set the cache status even before starting a caching thread to avoid unnecessarily
-            // spawning extra threads.
-            mCacheStatus.set(CACHE_IN_PROGRESS);
-            startCachingThread();
-        }
-    }
-
-    private void startCachingThread() {
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                cacheContacts(mContext);
-            }
-        }).start();
-    }
-
-    public static class ContactAffinityComparator implements Comparator<ContactNumber> {
-        @Override
-        public int compare(ContactNumber lhs, ContactNumber rhs) {
-            // Smaller affinity is better because they are numbered in ascending order in
-            // the order the contacts were returned from the ContactsProvider (sorted by
-            // frequency of use and time last used
-            return Integer.compare(lhs.affinity, rhs.affinity);
-        }
-
-    }
-
-    public SmartDialMap getMap() {
-        return mMap;
-    }
-
-    public boolean getUserInNanpRegion() {
-        return sUserInNanpRegion;
-    }
-
-    /**
-     * Indicates whether the given country uses NANP numbers
-     *
-     * @param country ISO 3166 country code (case doesn't matter)
-     * @return True if country uses NANP numbers (e.g. US, Canada), false otherwise
-     */
-    @VisibleForTesting
-    static boolean isCountryNanp(String country) {
-        if (TextUtils.isEmpty(country)) {
-            return false;
-        }
-        if (sNanpCountries == null) {
-            sNanpCountries = initNanpCountries();
-        }
-        return sNanpCountries.contains(country.toUpperCase());
-    }
-
-    private static Set<String> initNanpCountries() {
-        final HashSet<String> result = new HashSet<String>();
-        result.add("US"); // United States
-        result.add("CA"); // Canada
-        result.add("AS"); // American Samoa
-        result.add("AI"); // Anguilla
-        result.add("AG"); // Antigua and Barbuda
-        result.add("BS"); // Bahamas
-        result.add("BB"); // Barbados
-        result.add("BM"); // Bermuda
-        result.add("VG"); // British Virgin Islands
-        result.add("KY"); // Cayman Islands
-        result.add("DM"); // Dominica
-        result.add("DO"); // Dominican Republic
-        result.add("GD"); // Grenada
-        result.add("GU"); // Guam
-        result.add("JM"); // Jamaica
-        result.add("PR"); // Puerto Rico
-        result.add("MS"); // Montserrat
-        result.add("MP"); // Northern Mariana Islands
-        result.add("KN"); // Saint Kitts and Nevis
-        result.add("LC"); // Saint Lucia
-        result.add("VC"); // Saint Vincent and the Grenadines
-        result.add("TT"); // Trinidad and Tobago
-        result.add("TC"); // Turks and Caicos Islands
-        result.add("VI"); // U.S. Virgin Islands
-        return result;
-    }
-}
diff --git a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
index d584c17..71cbfa2 100644
--- a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
+++ b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
@@ -18,24 +18,21 @@
 
 import static com.android.dialer.dialpad.SmartDialController.LOG_TAG;
 
+import android.content.Context;
 import android.os.AsyncTask;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.telephony.PhoneNumberUtils;
-import android.util.Log;
 
 import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.common.util.StopWatch;
-import com.android.dialer.dialpad.SmartDialCache.ContactNumber;
+import com.android.dialer.database.DialerDatabaseHelper;
+import com.android.dialer.database.DialerDatabaseHelper.ContactNumber;
 
-import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * This task searches through the provided cache to return the top 3 contacts(ranked by confidence)
@@ -50,28 +47,20 @@
 
     static private final boolean DEBUG = false;
 
-    private static final int MAX_ENTRIES = 3;
-
-    private final SmartDialCache mContactsCache;
-
     private final SmartDialLoaderCallback mCallback;
 
+    private final DialerDatabaseHelper mDialerDatabaseHelper;
+
     private final String mQuery;
 
-    /**
-     * See {@link ContactsPreferences#getDisplayOrder()}.
-     * {@link ContactsContract.Preferences#DISPLAY_ORDER_PRIMARY} (first name first)
-     * {@link ContactsContract.Preferences#DISPLAY_ORDER_ALTERNATIVE} (last name first)
-     */
     private final SmartDialNameMatcher mNameMatcher;
 
-    public SmartDialLoaderTask(SmartDialLoaderCallback callback, String query,
-            SmartDialCache cache) {
+    public SmartDialLoaderTask(SmartDialLoaderCallback callback, String query, Context context) {
         this.mCallback = callback;
-        this.mContactsCache = cache;
-        this.mNameMatcher = new SmartDialNameMatcher(PhoneNumberUtils.normalizeNumber(query),
-                cache.getMap());
+        mDialerDatabaseHelper = DialerDatabaseHelper.getInstance(context);
         this.mQuery = query;
+        this.mNameMatcher = new SmartDialNameMatcher(PhoneNumberUtils.normalizeNumber(query),
+                SmartDialPrefix.getMap());
     }
 
     @Override
@@ -87,85 +76,33 @@
     }
 
     /**
-     * Loads all visible contacts with phone numbers and check if their display names match the
-     * query.  Return at most {@link #MAX_ENTRIES} {@link SmartDialEntry}'s for the matching
-     * contacts.
+     * Loads top visible contacts with phone numbers and check if their display names match the
+     * query.
      */
     private ArrayList<SmartDialEntry> getContactMatches() {
 
-        final SmartDialTrie trie = mContactsCache.getContacts();
-        final boolean matchNanp = mContactsCache.getUserInNanpRegion();
-
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Size of cache: " + trie.size());
-        }
-
         final StopWatch stopWatch = DEBUG ? StopWatch.start("Start Match") : null;
-        final ArrayList<ContactNumber> allMatches = trie.getAllWithPrefix(mNameMatcher.getQuery());
+
+        final ArrayList<ContactNumber> allMatches = mDialerDatabaseHelper.getLooseMatches(mQuery,
+                mNameMatcher);
         if (DEBUG) {
             stopWatch.lap("Find matches");
         }
-        // Sort matches in order of ascending contact affinity (lower is better)
-        Collections.sort(allMatches, new SmartDialCache.ContactAffinityComparator());
-        if (DEBUG) {
-            stopWatch.lap("Sort");
-        }
-        final Set<ContactMatch> duplicates = new HashSet<ContactMatch>();
+
         final ArrayList<SmartDialEntry> candidates = Lists.newArrayList();
         for (ContactNumber contact : allMatches) {
-            final ContactMatch contactMatch = new ContactMatch(contact.lookupKey, contact.id);
-            // Don't add multiple contact numbers from the same contact into suggestions if
-            // there are multiple matches. Instead, just keep the highest priority number
-            // instead.
-            if (duplicates.contains(contactMatch)) {
-                continue;
-            }
-            duplicates.add(contactMatch);
             final boolean matches = mNameMatcher.matches(contact.displayName);
-
             candidates.add(new SmartDialEntry(
                     contact.displayName,
                     Contacts.getLookupUri(contact.id, contact.lookupKey),
                     contact.phoneNumber,
                     mNameMatcher.getMatchPositions(),
-                    mNameMatcher.matchesNumber(contact.phoneNumber,
-                            mNameMatcher.getQuery(), matchNanp)
+                    mNameMatcher.matchesNumber(contact.phoneNumber, mNameMatcher.getQuery())
                     ));
-            if (candidates.size() >= MAX_ENTRIES) {
-                break;
-            }
         }
         if (DEBUG) {
             stopWatch.stopAndLog(LOG_TAG + " Match Complete", 0);
         }
         return candidates;
     }
-
-    private class ContactMatch {
-        public final String lookupKey;
-        public final long id;
-
-        public ContactMatch(String lookupKey, long id) {
-            this.lookupKey = lookupKey;
-            this.id = id;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(lookupKey, id);
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (this == object) {
-                return true;
-            }
-            if (object instanceof ContactMatch) {
-                ContactMatch that = (ContactMatch) object;
-                return Objects.equal(this.lookupKey, that.lookupKey)
-                        && Objects.equal(this.id, that.id);
-            }
-            return false;
-        }
-    }
 }
diff --git a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
index d7d5ad5..fe88e93 100644
--- a/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
+++ b/src/com/android/dialer/dialpad/SmartDialNameMatcher.java
@@ -18,7 +18,7 @@
 
 import android.text.TextUtils;
 
-import com.android.dialer.dialpad.SmartDialTrie.CountryCodeWithOffset;
+import com.android.dialer.dialpad.SmartDialPrefix.PhoneNumberTokens;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
@@ -88,37 +88,48 @@
     }
 
     /**
+     * Matches a phone number against a query. Let the test application overwrite the NANP setting.
+     *
+     * @param phoneNumber - Raw phone number
+     * @param query - Normalized query (only contains numbers from 0-9)
+     * @param useNanp - Overwriting nanp setting boolean, used for testing.
+     * @return {@literal null} if the number and the query don't match, a valid
+     *         SmartDialMatchPosition with the matching positions otherwise
+     */
+    @VisibleForTesting
+    public SmartDialMatchPosition matchesNumber(String phoneNumber, String query, boolean useNanp) {
+        // Try matching the number as is
+        SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0);
+        if (matchPos == null) {
+            final PhoneNumberTokens phoneNumberTokens =
+                    SmartDialPrefix.parsePhoneNumber(phoneNumber);
+
+            if (phoneNumberTokens == null) {
+                return matchPos;
+            }
+            if (phoneNumberTokens.countryCodeOffset != 0) {
+                matchPos = matchesNumberWithOffset(phoneNumber, query,
+                        phoneNumberTokens.countryCodeOffset);
+            }
+            if (matchPos == null && phoneNumberTokens.nanpCodeOffset != 0 && useNanp) {
+                matchPos = matchesNumberWithOffset(phoneNumber, query,
+                        phoneNumberTokens.nanpCodeOffset);
+            }
+        }
+        return matchPos;
+    }
+
+    /**
      * Matches a phone number against a query, taking care of formatting characters and also
      * taking into account country code prefixes and special NANP number treatment.
      *
      * @param phoneNumber - Raw phone number
      * @param query - Normalized query (only contains numbers from 0-9)
-     * @param matchNanp - Whether or not to do special matching for NANP numbers
      * @return {@literal null} if the number and the query don't match, a valid
      *         SmartDialMatchPosition with the matching positions otherwise
      */
-    public SmartDialMatchPosition matchesNumber(String phoneNumber, String query,
-            boolean matchNanp) {
-        // Try matching the number as is
-        SmartDialMatchPosition matchPos = matchesNumberWithOffset(phoneNumber, query, 0);
-        if (matchPos == null) {
-            // Try matching the number without the '+' prefix, if any
-            final CountryCodeWithOffset code = SmartDialTrie.getOffsetWithoutCountryCode(
-                    phoneNumber);
-            if (code != null) {
-                matchPos = matchesNumberWithOffset(phoneNumber, query, code.offset);
-            }
-            if (matchPos == null && matchNanp) {
-                // Try matching NANP numbers
-                final int[] offsets = SmartDialTrie.getOffsetForNANPNumbers(phoneNumber,
-                        mMap);
-                for (int i = 0; i < offsets.length; i++) {
-                    matchPos = matchesNumberWithOffset(phoneNumber, query, offsets[i]);
-                    if (matchPos != null) break;
-                }
-            }
-        }
-        return matchPos;
+    public SmartDialMatchPosition matchesNumber(String phoneNumber, String query) {
+        return matchesNumber(phoneNumber, query, true);
     }
 
     /**
diff --git a/src/com/android/dialer/dialpad/SmartDialPrefix.java b/src/com/android/dialer/dialpad/SmartDialPrefix.java
new file mode 100644
index 0000000..857f640
--- /dev/null
+++ b/src/com/android/dialer/dialpad/SmartDialPrefix.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.dialpad;
+
+import android.content.Context;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Smart Dial utility class to find prefixes of contacts. It contains both methods to find supported
+ * prefix combinations for contact names, and also methods to find supported prefix combinations for
+ * contacts' phone numbers. Each contact name is separated into several tokens, such as first name,
+ * middle name, family name etc. Each phone number is also separated into country code, NANP area
+ * code, and local number if such separation is possible.
+ */
+public class SmartDialPrefix {
+
+    /** The number of starting and ending tokens in a contact's name considered for initials.
+     * For example, if both constants are set to 2, and a contact's name is
+     * "Albert Ben Charles Daniel Ed Foster", the first two tokens "Albert" "Ben", and last two
+     * tokens "Ed" "Foster" can be replaced by their initials in contact name matching.
+     * Users can look up this contact by combinations of his initials such as "AF" "BF" "EF" "ABF"
+     * "BEF" "ABEF" etc, but can not use combinations such as "CF" "DF" "ACF" "ADF" etc.
+     */
+    private static final int LAST_TOKENS_FOR_INITIALS = 2;
+    private static final int FIRST_TOKENS_FOR_INITIALS = 2;
+
+    /** The country code of the user's sim card obtained by calling getSimCountryIso*/
+    private static final String PREF_USER_SIM_COUNTRY_CODE =
+            "DialtactsActivity_user_sim_country_code";
+    private static final String PREF_USER_SIM_COUNTRY_CODE_DEFAULT = null;
+    private static String sUserSimCountryCode = PREF_USER_SIM_COUNTRY_CODE_DEFAULT;
+
+    /** Indicates whether user is in NANP regions.*/
+    private static boolean sUserInNanpRegion = false;
+
+    /** Set of country names that use NANP code.*/
+    private static Set<String> sNanpCountries = null;
+
+    /** Set of supported country codes in front of the phone number. */
+    private static Set<String> sCountryCodes = null;
+
+    /** Dialpad mapping. */
+    private static final SmartDialMap mMap = new LatinSmartDialMap();
+
+    private static boolean sNanpInitialized = false;
+
+    /** Initializes the Nanp settings, and finds out whether user is in a NANP region.*/
+    public static void initializeNanpSettings(Context context){
+        final TelephonyManager manager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        if (manager != null) {
+            sUserSimCountryCode = manager.getSimCountryIso();
+        }
+
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+        if (sUserSimCountryCode != null) {
+            /** Updates shared preferences with the latest country obtained from getSimCountryIso.*/
+            prefs.edit().putString(PREF_USER_SIM_COUNTRY_CODE, sUserSimCountryCode).apply();
+        } else {
+            /** Uses previously stored country code if loading fails. */
+            sUserSimCountryCode = prefs.getString(PREF_USER_SIM_COUNTRY_CODE,
+                    PREF_USER_SIM_COUNTRY_CODE_DEFAULT);
+        }
+        /** Queries the NANP country list to find out whether user is in a NANP region.*/
+        sUserInNanpRegion = isCountryNanp(sUserSimCountryCode);
+        sNanpInitialized = true;
+    }
+
+    /**
+     * Explicitly setting the user Nanp to the given boolean
+     */
+    @VisibleForTesting
+    public static void setUserInNanpRegion(boolean userInNanpRegion) {
+        sUserInNanpRegion = userInNanpRegion;
+    }
+
+    /**
+     * Class to record phone number parsing information.
+     */
+    public static class PhoneNumberTokens {
+        /** Country code of the phone number. */
+        final String countryCode;
+
+        /** Offset of national number after the country code. */
+        final int countryCodeOffset;
+
+        /** Offset of local number after NANP area code.*/
+        final int nanpCodeOffset;
+
+        public PhoneNumberTokens(String countryCode, int countryCodeOffset, int nanpCodeOffset) {
+            this.countryCode = countryCode;
+            this.countryCodeOffset = countryCodeOffset;
+            this.nanpCodeOffset = nanpCodeOffset;
+        }
+    }
+
+    /**
+     * Parses a contact's name into a list of separated tokens.
+     *
+     * @param contactName Contact's name stored in string.
+     * @return A list of name tokens, for example separated first names, last name, etc.
+     */
+    public static ArrayList<String> parseToIndexTokens(String contactName) {
+        final int length = contactName.length();
+        final ArrayList<String> result = Lists.newArrayList();
+        char c;
+        final StringBuilder currentIndexToken = new StringBuilder();
+        /**
+         * Iterates through the whole name string. If the current character is a valid character,
+         * append it to the current token. If the current character is not a valid character, for
+         * example space " ", mark the current token as complete and add it to the list of tokens.
+         */
+        for (int i = 0; i < length; i++) {
+            c = mMap.normalizeCharacter(contactName.charAt(i));
+            if (mMap.isValidDialpadCharacter(c)) {
+                /** Converts a character into the number on dialpad that represents the character.*/
+                currentIndexToken.append(mMap.getDialpadIndex(c));
+            } else {
+                if (currentIndexToken.length() != 0) {
+                    result.add(currentIndexToken.toString());
+                }
+                currentIndexToken.delete(0, currentIndexToken.length());
+            }
+        }
+
+        /** Adds the last token in case it has not been added.*/
+        if (currentIndexToken.length() != 0) {
+            result.add(currentIndexToken.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Generates a list of strings that any prefix of any string in the list can be used to look
+     * up the contact's name.
+     *
+     * @param index The contact's name in string.
+     * @return A List of strings, whose prefix can be used to look up the contact.
+     */
+    public static ArrayList<String> generateNamePrefixes(String index) {
+        final ArrayList<String> result = Lists.newArrayList();
+
+        /** Parses the name into a list of tokens.*/
+        final ArrayList<String> indexTokens = parseToIndexTokens(index);
+
+        if (indexTokens.size() > 0) {
+            /** Adds the full token combinations to the list. For example, a contact with name
+             * "Albert Ben Ed Foster" can be looked up by any prefix of the following strings
+             * "Foster" "EdFoster" "BenEdFoster" and "AlbertBenEdFoster". This covers all cases of
+             * look up that contains only one token, and that spans multiple continuous tokens.
+             */
+            final StringBuilder fullNameToken = new StringBuilder();
+            for (int i = indexTokens.size() - 1; i >= 0; i--) {
+                fullNameToken.insert(0, indexTokens.get(i));
+                result.add(fullNameToken.toString());
+            }
+
+            /** Adds initial combinations to the list, with the number of initials restricted by
+             * {@link #LAST_TOKENS_FOR_INITIALS} and {@link #FIRST_TOKENS_FOR_INITIALS}.
+             * For example, a contact with name "Albert Ben Ed Foster" can be looked up by any
+             * prefix of the following strings "EFoster" "BFoster" "BEFoster" "AFoster" "ABFoster"
+             * "AEFoster" and "ABEFoster". This covers all cases of initial lookup.
+             */
+            ArrayList<String> fullNames = Lists.newArrayList();
+            fullNames.add(indexTokens.get(indexTokens.size() - 1));
+            final int recursiveNameStart = result.size();
+            int recursiveNameEnd = result.size();
+            String initial = "";
+            for (int i = indexTokens.size() - 2; i >= 0; i--) {
+                if ((i >= indexTokens.size() - LAST_TOKENS_FOR_INITIALS) ||
+                        (i < FIRST_TOKENS_FOR_INITIALS)) {
+                    initial = indexTokens.get(i).substring(0, 1);
+
+                    /** Recursively adds initial combinations to the list.*/
+                    for (int j = 0; j < fullNames.size(); ++j) {
+                        result.add(initial + fullNames.get(j));
+                    }
+                    for (int j = recursiveNameStart; j < recursiveNameEnd; ++j) {
+                       result.add(initial + result.get(j));
+                    }
+                    recursiveNameEnd = result.size();
+                    final String currentFullName = fullNames.get(fullNames.size() - 1);
+                    fullNames.add(indexTokens.get(i) +  currentFullName);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Computes a list of number strings based on tokens of a given phone number. Any prefix
+     * of any string in the list can be used to look up the phone number. The list include the
+     * full phone number, the national number if there is a country code in the phone number, and
+     * the local number if there is an area code in the phone number following the NANP format.
+     * For example, if a user has phone number +41 71 394 8392, the list will contain 41713948392
+     * and 713948392. Any prefix to either of the strings can be used to look up the phone number.
+     * If a user has a phone number +1 555-302-3029 (NANP format), the list will contain
+     * 15553023029, 5553023029, and 3023029.
+     *
+     * @param number String of user's phone number.
+     * @return A list of strings where any prefix of any entry can be used to look up the number.
+     */
+    public static ArrayList<String> parseToNumberTokens(String number) {
+        final ArrayList<String> result = Lists.newArrayList();
+        if (!TextUtils.isEmpty(number)) {
+            /** Adds the full number to the list.*/
+            result.add(SmartDialNameMatcher.normalizeNumber(number, mMap));
+
+            final PhoneNumberTokens phoneNumberTokens = parsePhoneNumber(number);
+            if (phoneNumberTokens == null) {
+                return result;
+            }
+
+            if (phoneNumberTokens.countryCodeOffset != 0) {
+                result.add(SmartDialNameMatcher.normalizeNumber(number,
+                        phoneNumberTokens.countryCodeOffset, mMap));
+            }
+
+            if (phoneNumberTokens.nanpCodeOffset != 0) {
+                result.add(SmartDialNameMatcher.normalizeNumber(number,
+                        phoneNumberTokens.nanpCodeOffset, mMap));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses a phone number to find out whether it has country code and NANP area code.
+     *
+     * @param number Raw phone number.
+     * @return a PhoneNumberToken instance with country code, NANP code information.
+     */
+    public static PhoneNumberTokens parsePhoneNumber(String number) {
+        String countryCode = "";
+        int countryCodeOffset = 0;
+        int nanpNumberOffset = 0;
+
+        if (!TextUtils.isEmpty(number)) {
+            String normalizedNumber = SmartDialNameMatcher.normalizeNumber(number, mMap);
+            if (number.charAt(0) == '+') {
+                /** If the number starts with '+', tries to find valid country code. */
+                for (int i = 1; i <= 1 + 3; i++) {
+                    if (number.length() <= i) {
+                        break;
+                    }
+                    countryCode = number.substring(1, i);
+                    if (isValidCountryCode(countryCode)) {
+                        countryCodeOffset = i;
+                        break;
+                    }
+                }
+            } else {
+                /** If the number does not start with '+', finds out whether it is in NANP
+                 * format and has '1' preceding the number.
+                 */
+                if ((normalizedNumber.charAt(0) == '1') && (normalizedNumber.length() == 11) &&
+                        (sUserInNanpRegion)) {
+                    countryCode = "1";
+                    countryCodeOffset = number.indexOf(normalizedNumber.charAt(1));
+                    if (countryCodeOffset == -1) {
+                        countryCodeOffset = 0;
+                    }
+                }
+            }
+
+            /** If user is in NANP region, finds out whether a number is in NANP format.*/
+            if (sUserInNanpRegion)  {
+                String areaCode = "";
+                if (countryCode.equals("") && normalizedNumber.length() == 10){
+                    /** if the number has no country code but fits the NANP format, extracts the
+                     * NANP area code, and finds out offset of the local number.
+                     */
+                    areaCode = normalizedNumber.substring(0, 3);
+                } else if (countryCode.equals("1") && normalizedNumber.length() == 11) {
+                    /** If the number has country code '1', finds out area code and offset of the
+                     * local number.
+                     */
+                    areaCode = normalizedNumber.substring(1, 4);
+                }
+                if (!areaCode.equals("")) {
+                    final int areaCodeIndex = number.indexOf(areaCode);
+                    if (areaCodeIndex != -1) {
+                        nanpNumberOffset = number.indexOf(areaCode) + 3;
+                    }
+                }
+            }
+        }
+        return new PhoneNumberTokens(countryCode, countryCodeOffset, nanpNumberOffset);
+    }
+
+    /**
+     * Checkes whether a country code is valid.
+     */
+    private static boolean isValidCountryCode(String countryCode) {
+        if (sCountryCodes == null) {
+            sCountryCodes = initCountryCodes();
+        }
+        return sCountryCodes.contains(countryCode);
+    }
+
+    private static Set<String> initCountryCodes() {
+        final HashSet<String> result = new HashSet<String>();
+        result.add("1");
+        result.add("7");
+        result.add("20");
+        result.add("27");
+        result.add("30");
+        result.add("31");
+        result.add("32");
+        result.add("33");
+        result.add("34");
+        result.add("36");
+        result.add("39");
+        result.add("40");
+        result.add("41");
+        result.add("43");
+        result.add("44");
+        result.add("45");
+        result.add("46");
+        result.add("47");
+        result.add("48");
+        result.add("49");
+        result.add("51");
+        result.add("52");
+        result.add("53");
+        result.add("54");
+        result.add("55");
+        result.add("56");
+        result.add("57");
+        result.add("58");
+        result.add("60");
+        result.add("61");
+        result.add("62");
+        result.add("63");
+        result.add("64");
+        result.add("65");
+        result.add("66");
+        result.add("81");
+        result.add("82");
+        result.add("84");
+        result.add("86");
+        result.add("90");
+        result.add("91");
+        result.add("92");
+        result.add("93");
+        result.add("94");
+        result.add("95");
+        result.add("98");
+        result.add("211");
+        result.add("212");
+        result.add("213");
+        result.add("216");
+        result.add("218");
+        result.add("220");
+        result.add("221");
+        result.add("222");
+        result.add("223");
+        result.add("224");
+        result.add("225");
+        result.add("226");
+        result.add("227");
+        result.add("228");
+        result.add("229");
+        result.add("230");
+        result.add("231");
+        result.add("232");
+        result.add("233");
+        result.add("234");
+        result.add("235");
+        result.add("236");
+        result.add("237");
+        result.add("238");
+        result.add("239");
+        result.add("240");
+        result.add("241");
+        result.add("242");
+        result.add("243");
+        result.add("244");
+        result.add("245");
+        result.add("246");
+        result.add("247");
+        result.add("248");
+        result.add("249");
+        result.add("250");
+        result.add("251");
+        result.add("252");
+        result.add("253");
+        result.add("254");
+        result.add("255");
+        result.add("256");
+        result.add("257");
+        result.add("258");
+        result.add("260");
+        result.add("261");
+        result.add("262");
+        result.add("263");
+        result.add("264");
+        result.add("265");
+        result.add("266");
+        result.add("267");
+        result.add("268");
+        result.add("269");
+        result.add("290");
+        result.add("291");
+        result.add("297");
+        result.add("298");
+        result.add("299");
+        result.add("350");
+        result.add("351");
+        result.add("352");
+        result.add("353");
+        result.add("354");
+        result.add("355");
+        result.add("356");
+        result.add("357");
+        result.add("358");
+        result.add("359");
+        result.add("370");
+        result.add("371");
+        result.add("372");
+        result.add("373");
+        result.add("374");
+        result.add("375");
+        result.add("376");
+        result.add("377");
+        result.add("378");
+        result.add("379");
+        result.add("380");
+        result.add("381");
+        result.add("382");
+        result.add("385");
+        result.add("386");
+        result.add("387");
+        result.add("389");
+        result.add("420");
+        result.add("421");
+        result.add("423");
+        result.add("500");
+        result.add("501");
+        result.add("502");
+        result.add("503");
+        result.add("504");
+        result.add("505");
+        result.add("506");
+        result.add("507");
+        result.add("508");
+        result.add("509");
+        result.add("590");
+        result.add("591");
+        result.add("592");
+        result.add("593");
+        result.add("594");
+        result.add("595");
+        result.add("596");
+        result.add("597");
+        result.add("598");
+        result.add("599");
+        result.add("670");
+        result.add("672");
+        result.add("673");
+        result.add("674");
+        result.add("675");
+        result.add("676");
+        result.add("677");
+        result.add("678");
+        result.add("679");
+        result.add("680");
+        result.add("681");
+        result.add("682");
+        result.add("683");
+        result.add("685");
+        result.add("686");
+        result.add("687");
+        result.add("688");
+        result.add("689");
+        result.add("690");
+        result.add("691");
+        result.add("692");
+        result.add("800");
+        result.add("808");
+        result.add("850");
+        result.add("852");
+        result.add("853");
+        result.add("855");
+        result.add("856");
+        result.add("870");
+        result.add("878");
+        result.add("880");
+        result.add("881");
+        result.add("882");
+        result.add("883");
+        result.add("886");
+        result.add("888");
+        result.add("960");
+        result.add("961");
+        result.add("962");
+        result.add("963");
+        result.add("964");
+        result.add("965");
+        result.add("966");
+        result.add("967");
+        result.add("968");
+        result.add("970");
+        result.add("971");
+        result.add("972");
+        result.add("973");
+        result.add("974");
+        result.add("975");
+        result.add("976");
+        result.add("977");
+        result.add("979");
+        result.add("992");
+        result.add("993");
+        result.add("994");
+        result.add("995");
+        result.add("996");
+        result.add("998");
+        return result;
+    }
+
+    public static SmartDialMap getMap() {
+        return mMap;
+    }
+
+    /**
+     * Indicates whether the given country uses NANP numbers
+     * @see <a href="https://en.wikipedia.org/wiki/North_American_Numbering_Plan">
+     *     https://en.wikipedia.org/wiki/North_American_Numbering_Plan</a>
+     *
+     * @param country ISO 3166 country code (case doesn't matter)
+     * @return True if country uses NANP numbers (e.g. US, Canada), false otherwise
+     */
+    @VisibleForTesting
+    public static boolean isCountryNanp(String country) {
+        if (TextUtils.isEmpty(country)) {
+            return false;
+        }
+        if (sNanpCountries == null) {
+            sNanpCountries = initNanpCountries();
+        }
+        return sNanpCountries.contains(country.toUpperCase());
+    }
+
+    private static Set<String> initNanpCountries() {
+        final HashSet<String> result = new HashSet<String>();
+        result.add("US"); // United States
+        result.add("CA"); // Canada
+        result.add("AS"); // American Samoa
+        result.add("AI"); // Anguilla
+        result.add("AG"); // Antigua and Barbuda
+        result.add("BS"); // Bahamas
+        result.add("BB"); // Barbados
+        result.add("BM"); // Bermuda
+        result.add("VG"); // British Virgin Islands
+        result.add("KY"); // Cayman Islands
+        result.add("DM"); // Dominica
+        result.add("DO"); // Dominican Republic
+        result.add("GD"); // Grenada
+        result.add("GU"); // Guam
+        result.add("JM"); // Jamaica
+        result.add("PR"); // Puerto Rico
+        result.add("MS"); // Montserrat
+        result.add("MP"); // Northern Mariana Islands
+        result.add("KN"); // Saint Kitts and Nevis
+        result.add("LC"); // Saint Lucia
+        result.add("VC"); // Saint Vincent and the Grenadines
+        result.add("TT"); // Trinidad and Tobago
+        result.add("TC"); // Turks and Caicos Islands
+        result.add("VI"); // U.S. Virgin Islands
+        return result;
+    }
+
+    /**
+     * Returns whether the user is in a region that uses Nanp format based on the sim location.
+     *
+     * @return Whether user is in Nanp region.
+     */
+    public static boolean getUserInNanpRegion() {
+        return sUserInNanpRegion;
+    }
+}
diff --git a/src/com/android/dialer/dialpad/SmartDialTrie.java b/src/com/android/dialer/dialpad/SmartDialTrie.java
deleted file mode 100644
index c62210b..0000000
--- a/src/com/android/dialer/dialpad/SmartDialTrie.java
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.dialpad;
-
-import android.text.TextUtils;
-
-import com.android.dialer.dialpad.SmartDialCache.ContactNumber;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Prefix trie where the only allowed characters are the characters '0' to '9'. Multiple contacts
- * can occupy the same nodes.
- *
- * <p>Provides functions to get all contacts that lie on or below a node.
- * This is useful for retrieving all contacts that start with that prefix.</p>
- *
- * <p>Also contains special logic to handle NANP numbers in the case that the user is from a region
- * that uses NANP numbers.</p>
- */
-public class SmartDialTrie {
-    @VisibleForTesting
-    static class ParseInfo {
-        byte[] indexes;
-        int nthFirstTokenPos;
-        int nthLastTokenPos;
-    }
-
-    /**
-     * A country code and integer offset pair that represents the parsed country code in a
-     * phone number. The country code is a string containing the numeric country-code prefix in
-     * a phone number (e.g. 1 or 852). The offset is the integer position of where the country code
-     * ends in a phone number.
-     */
-    public static class CountryCodeWithOffset {
-        public static final CountryCodeWithOffset NO_COUNTRY_CODE = new CountryCodeWithOffset(0,
-                "");
-
-        final String countryCode;
-        final int offset;
-
-        public CountryCodeWithOffset(int offset, String countryCode) {
-            this.countryCode = countryCode;
-            this.offset = offset;
-        }
-    }
-
-    final Node mRoot = new Node();
-    private int mSize = 0;
-    private final SmartDialMap mMap;
-    private final boolean mFormatNanp;
-
-    private static final int LAST_TOKENS_FOR_INITIALS = 2;
-    private static final int FIRST_TOKENS_FOR_INITIALS = 2;
-
-    // Static set of all possible country codes in the world
-    public static Set<String> sCountryCodes = null;
-
-    public SmartDialTrie() {
-        // Use the latin letter to digit map by default if none provided
-        this(new LatinSmartDialMap(), false);
-    }
-
-    /**
-     * Creates a new SmartDialTrie.
-     *
-     * @param formatNanp True if inserted numbers are to be treated as NANP numbers
-     * such that numbers are automatically broken up by country prefix and area code.
-     */
-    @VisibleForTesting
-    public SmartDialTrie(boolean formatNanp) {
-        this(new LatinSmartDialMap(), formatNanp);
-    }
-
-    /**
-     * Creates a new SmartDialTrie.
-     *
-     * @param charMap Mapping of characters to digits to use when inserting names into the trie.
-     * @param formatNanp True if inserted numbers are to be treated as NANP numbers
-     * such that numbers are automatically broken up by country prefix and area code.
-     */
-    public SmartDialTrie(SmartDialMap map, boolean formatNanp) {
-        mMap = map;
-        mFormatNanp = formatNanp;
-    }
-
-    /**
-     * Returns all contacts in the prefix tree that correspond to this prefix.
-     */
-    public ArrayList<ContactNumber> getAllWithPrefix(CharSequence prefix) {
-        final ArrayList<ContactNumber> result = Lists.newArrayList();
-        if (TextUtils.isEmpty(prefix)) {
-            return result;
-        }
-        Node current = mRoot;
-        for (int i = 0; i < prefix.length(); i++) {
-            char ch = prefix.charAt(i);
-            current = current.getChild(ch, false);
-            if (current == null) {
-                return result;
-            }
-        }
-        // return all contacts that correspond to this prefix
-        getAll(current, result);
-        return result;
-    }
-
-    /**
-     * Returns all the contacts located at and under the provided node(including its children)
-     */
-    private void getAll(Node root, ArrayList<ContactNumber> output) {
-        if (root == null) {
-            return;
-        }
-        if (root.getContents() != null) {
-            output.addAll(root.getContents());
-        }
-        for (int i = 0; i < root.getChildrenSize(); i++) {
-            getAll(root.getChild(i, false), output);
-        }
-    }
-
-    /**
-     * Adds the display name and phone number of a contact into the prefix trie.
-     *
-     * @param contact Desired contact to add
-     */
-    public void put(ContactNumber contact) {
-        // Preconvert the display name into a byte array containing indexes to avoid having to
-        // remap each character over multiple passes
-        final ParseInfo info = parseToIndexes(contact.displayName, FIRST_TOKENS_FOR_INITIALS,
-                LAST_TOKENS_FOR_INITIALS);
-        putForPrefix(contact, mRoot, info, 0, true);
-        // We don't need to do the same for phone numbers since we only make one pass over them.
-        // Strip the calling code from the phone number here
-        if (!TextUtils.isEmpty(contact.phoneNumber)) {
-            // Handle country codes for numbers with a + prefix
-            final CountryCodeWithOffset code = getOffsetWithoutCountryCode(contact.phoneNumber);
-            if (code.offset != 0) {
-                putNumber(contact, contact.phoneNumber, code.offset);
-            }
-            if ((code.countryCode.equals("1") || code.offset == 0) && mFormatNanp) {
-                // Special case handling for NANP numbers (1-xxx-xxx-xxxx)
-                final String stripped = SmartDialNameMatcher.normalizeNumber(
-                        contact.phoneNumber, code.offset, mMap);
-                if (!TextUtils.isEmpty(stripped)) {
-                    int trunkPrefixOffset = 0;
-                    if (stripped.charAt(0) == '1') {
-                        // If the number starts with 1, we can assume its the trunk prefix.
-                        trunkPrefixOffset = 1;
-                    }
-                    if (stripped.length() == (10 + trunkPrefixOffset)) {
-                        // Valid NANP number
-                        if (trunkPrefixOffset != 0) {
-                            // Add the digits that follow the 1st digit (trunk prefix)
-                            // If trunkPrefixOffset is 0, we will add the number as is anyway, so
-                            // don't bother.
-                            putNumber(contact, stripped, trunkPrefixOffset);
-                        }
-                        // Add the digits that follow the next 3 digits (area code)
-                        putNumber(contact, stripped, 3 + trunkPrefixOffset);
-                    }
-                }
-            }
-            putNumber(contact, contact.phoneNumber, 0);
-        }
-        mSize++;
-    }
-
-    public static CountryCodeWithOffset getOffsetWithoutCountryCode(String number) {
-        if (!TextUtils.isEmpty(number)) {
-            if (number.charAt(0) == '+') {
-                // check for international code here
-                for (int i = 1; i <= 1 + 3; i++) {
-                    if (number.length() <= i) break;
-                    final String countryCode = number.substring(1, i);
-                    if (isValidCountryCode(countryCode)) {
-                        return new CountryCodeWithOffset(i, countryCode);
-                    }
-                }
-            }
-        }
-        return CountryCodeWithOffset.NO_COUNTRY_CODE;
-    }
-
-    /**
-     * Used by SmartDialNameMatcher to determine which character in the phone number to start
-     * the matching process from for a NANP formatted number.
-     *
-     * @param number Raw phone number
-     * @return An empty array if the provided number does not appear to be an NANP number,
-     * and an array containing integer offsets for the number (starting after the '1' prefix,
-     * and the area code prefix respectively.
-     */
-    public static int[] getOffsetForNANPNumbers(String number, SmartDialMap map) {
-        int validDigits = 0;
-        boolean hasPrefix = false;
-        int firstOffset = 0; // Tracks the location of the first digit after the '1' prefix
-        int secondOffset = 0; // Tracks the location of the first digit after the area code
-        for (int i = 0; i < number.length(); i++) {
-            final char ch = number.charAt(i);
-            if (map.isValidDialpadNumericChar(ch)) {
-                if (validDigits == 0) {
-                    // Check the first digit to see if it is 1
-                    if (ch == '1') {
-                        if (hasPrefix) {
-                            // Prefix has two '1's in a row. Invalid number, since area codes
-                            // cannot start with 1, so just bail
-                            break;
-                        }
-                        hasPrefix = true;
-                        continue;
-                    }
-                }
-                validDigits++;
-                if (validDigits == 1) {
-                    // Found the first digit after the country code
-                    firstOffset = i;
-                } else if (validDigits == 4) {
-                    // Found the first digit after the area code
-                    secondOffset = i;
-                }
-            }
-
-        }
-        if (validDigits == 10) {
-            return hasPrefix ? new int[] {firstOffset, secondOffset} : new int[] {secondOffset};
-        }
-        return new int[0];
-    }
-
-    /**
-     * Converts the given characters into a byte array of index and returns it together with offset
-     * information in a {@link ParseInfo} data structure.
-     * @param chars Characters to convert into indexes
-     * @param firstNTokens The first n tokens we want the offset for
-     * @param lastNTokens The last n tokens we want the offset for
-     */
-    @VisibleForTesting
-    ParseInfo parseToIndexes(CharSequence chars, int firstNTokens, int lastNTokens) {
-        final int length = chars.length();
-        final byte[] result = new byte[length];
-        final ArrayList<Integer> offSets = new ArrayList<Integer>();
-        char c;
-        int tokenCount = 0;
-        boolean atSeparator = true;
-        for (int i = 0; i < length; i++) {
-            c = mMap.normalizeCharacter(chars.charAt(i));
-            if (mMap.isValidDialpadCharacter(c)) {
-                if (atSeparator) {
-                    tokenCount++;
-                }
-                atSeparator = false;
-                result[i] = mMap.getDialpadIndex(c);
-            } else {
-                // Found the last character of the current token
-                if (!atSeparator) {
-                    offSets.add(i);
-                }
-                result[i] = -1;
-                atSeparator = true;
-            }
-        }
-
-        final ParseInfo info = new ParseInfo();
-        info.indexes = result;
-        info.nthFirstTokenPos = offSets.size() >= firstNTokens ? offSets.get(firstNTokens - 1) :
-                length - 1;
-        info.nthLastTokenPos = offSets.size() >= lastNTokens ? offSets.get(offSets.size() -
-                lastNTokens) : 0;
-        return info;
-    }
-
-    /**
-     * Puts a phone number and its associated contact into the prefix trie.
-     *
-     * @param contact - Contact to add to the trie
-     * @param phoneNumber - Phone number of the contact
-     * @param offSet - The nth character of the phone number to start from
-     */
-    private void putNumber(ContactNumber contact, String phoneNumber, int offSet) {
-        Preconditions.checkArgument(offSet < phoneNumber.length());
-        Node current = mRoot;
-        final int length = phoneNumber.length();
-        char ch;
-        for (int i = offSet; i < length; i++) {
-            ch = phoneNumber.charAt(i);
-            if (mMap.isValidDialpadNumericChar(ch)) {
-                current = current.getChild(ch, true);
-            }
-        }
-        current.add(contact);
-    }
-
-    /**
-     * Place an contact into the trie using at the provided node using the provided prefix. Uses as
-     * the input prefix a byte array instead of a CharSequence, as we will be traversing the array
-     * multiple times and it is more efficient to pre-convert the prefix into indexes before hand.
-     * Adds initial matches for the first token, and the last N tokens in the name.
-     *
-     * @param contact Contact to put
-     * @param root Root node to use as the starting point
-     * @param parseInfo ParseInfo data structure containing the converted byte array, as well as
-     *        token offsets that determine which tokens should have entries added for initial
-     *        search
-     * @param start - Starting index of the byte array
-     * @param isFullName If true, prefix will be treated as a full name and everytime a new name
-     *        token is encountered, the rest of the name segment is added into the trie.
-     */
-    private void putForPrefix(ContactNumber contact, Node root, ParseInfo info, int start,
-            boolean isFullName) {
-        final boolean addInitialMatches = (start >= info.nthLastTokenPos ||
-                start <= info.nthFirstTokenPos);
-        final byte[] indexes = info.indexes;
-        Node current = root;
-        Node initialNode = root;
-        final int length = indexes.length;
-        boolean atSeparator = true;
-        byte index;
-        for (int i = start; i < length; i++) {
-            index = indexes[i];
-            if (index > -1) {
-                if (atSeparator) {
-                    atSeparator = false;
-                    // encountered a new name token, so add this token into the tree starting from
-                    // the root node
-                    if (initialNode != this.mRoot) {
-                        if (isFullName) {
-                            putForPrefix(contact, this.mRoot, info, i, false);
-                        }
-                        if (addInitialMatches &&
-                                (i >= info.nthLastTokenPos || i <= info.nthFirstTokenPos) &&
-                                initialNode != root) {
-                            putForPrefix(contact, initialNode, info, i, false);
-                        }
-                    }
-                    // Set initial node to the node indexed by the first character of the current
-                    // prefix
-                    if (initialNode == root) {
-                        initialNode = initialNode.getChild(index, true);
-                    }
-                }
-                current = current.getChild(index, true);
-            } else {
-                atSeparator = true;
-            }
-        }
-        current.add(contact);
-    }
-
-    /* Used only for testing to verify we insert the correct number of entries into the trie */
-    @VisibleForTesting
-    int numEntries() {
-        final ArrayList<ContactNumber> result = Lists.newArrayList();
-        getAll(mRoot, result);
-        return result.size();
-    }
-
-
-    @VisibleForTesting
-    public int size() {
-        return mSize;
-    }
-
-    @VisibleForTesting
-    /* package */ static class Node {
-        Node[] mChildren;
-        private ArrayList<ContactNumber> mContents;
-
-        public Node() {
-            // don't allocate array or contents unless needed
-        }
-
-        public int getChildrenSize() {
-            if (mChildren == null) {
-                return -1;
-            }
-            return mChildren.length;
-        }
-
-        /**
-         * Returns a specific child of the current node.
-         *
-         * @param index Index of the child to return.
-         * @param createIfDoesNotExist Whether or not to create a node in that child slot if one
-         *        does not already currently exist.
-         * @return The existing or newly created child, or {@literal null} if the child does not
-         *         exist and createIfDoesNotExist is false.
-         */
-        public Node getChild(int index, boolean createIfDoesNotExist) {
-            if (createIfDoesNotExist) {
-                if (mChildren == null) {
-                    mChildren = new Node[10];
-                }
-                if (mChildren[index] == null) {
-                    mChildren[index] = new Node();
-                }
-            } else {
-                if (mChildren == null) {
-                    return null;
-                }
-            }
-            return mChildren[index];
-        }
-
-        /**
-         * Same as getChild(int index, boolean createIfDoesNotExist), but takes a character from '0'
-         * to '9' as an index.
-         */
-        public Node getChild(char index, boolean createIfDoesNotExist) {
-            return getChild(index - '0', createIfDoesNotExist);
-        }
-
-        public void add(ContactNumber contact) {
-            if (mContents == null) {
-                mContents = Lists.newArrayList();
-            }
-            mContents.add(contact);
-        }
-
-        public ArrayList<ContactNumber> getContents() {
-            return mContents;
-        }
-    }
-
-    private static boolean isValidCountryCode(String countryCode) {
-        if (sCountryCodes == null) {
-            sCountryCodes = initCountryCodes();
-        }
-        return sCountryCodes.contains(countryCode);
-    }
-
-    private static Set<String> initCountryCodes() {
-        final HashSet<String> result = new HashSet<String>();
-        result.add("1");
-        result.add("7");
-        result.add("20");
-        result.add("27");
-        result.add("30");
-        result.add("31");
-        result.add("32");
-        result.add("33");
-        result.add("34");
-        result.add("36");
-        result.add("39");
-        result.add("40");
-        result.add("41");
-        result.add("43");
-        result.add("44");
-        result.add("45");
-        result.add("46");
-        result.add("47");
-        result.add("48");
-        result.add("49");
-        result.add("51");
-        result.add("52");
-        result.add("53");
-        result.add("54");
-        result.add("55");
-        result.add("56");
-        result.add("57");
-        result.add("58");
-        result.add("60");
-        result.add("61");
-        result.add("62");
-        result.add("63");
-        result.add("64");
-        result.add("65");
-        result.add("66");
-        result.add("81");
-        result.add("82");
-        result.add("84");
-        result.add("86");
-        result.add("90");
-        result.add("91");
-        result.add("92");
-        result.add("93");
-        result.add("94");
-        result.add("95");
-        result.add("98");
-        result.add("211");
-        result.add("212");
-        result.add("213");
-        result.add("216");
-        result.add("218");
-        result.add("220");
-        result.add("221");
-        result.add("222");
-        result.add("223");
-        result.add("224");
-        result.add("225");
-        result.add("226");
-        result.add("227");
-        result.add("228");
-        result.add("229");
-        result.add("230");
-        result.add("231");
-        result.add("232");
-        result.add("233");
-        result.add("234");
-        result.add("235");
-        result.add("236");
-        result.add("237");
-        result.add("238");
-        result.add("239");
-        result.add("240");
-        result.add("241");
-        result.add("242");
-        result.add("243");
-        result.add("244");
-        result.add("245");
-        result.add("246");
-        result.add("247");
-        result.add("248");
-        result.add("249");
-        result.add("250");
-        result.add("251");
-        result.add("252");
-        result.add("253");
-        result.add("254");
-        result.add("255");
-        result.add("256");
-        result.add("257");
-        result.add("258");
-        result.add("260");
-        result.add("261");
-        result.add("262");
-        result.add("263");
-        result.add("264");
-        result.add("265");
-        result.add("266");
-        result.add("267");
-        result.add("268");
-        result.add("269");
-        result.add("290");
-        result.add("291");
-        result.add("297");
-        result.add("298");
-        result.add("299");
-        result.add("350");
-        result.add("351");
-        result.add("352");
-        result.add("353");
-        result.add("354");
-        result.add("355");
-        result.add("356");
-        result.add("357");
-        result.add("358");
-        result.add("359");
-        result.add("370");
-        result.add("371");
-        result.add("372");
-        result.add("373");
-        result.add("374");
-        result.add("375");
-        result.add("376");
-        result.add("377");
-        result.add("378");
-        result.add("379");
-        result.add("380");
-        result.add("381");
-        result.add("382");
-        result.add("385");
-        result.add("386");
-        result.add("387");
-        result.add("389");
-        result.add("420");
-        result.add("421");
-        result.add("423");
-        result.add("500");
-        result.add("501");
-        result.add("502");
-        result.add("503");
-        result.add("504");
-        result.add("505");
-        result.add("506");
-        result.add("507");
-        result.add("508");
-        result.add("509");
-        result.add("590");
-        result.add("591");
-        result.add("592");
-        result.add("593");
-        result.add("594");
-        result.add("595");
-        result.add("596");
-        result.add("597");
-        result.add("598");
-        result.add("599");
-        result.add("670");
-        result.add("672");
-        result.add("673");
-        result.add("674");
-        result.add("675");
-        result.add("676");
-        result.add("677");
-        result.add("678");
-        result.add("679");
-        result.add("680");
-        result.add("681");
-        result.add("682");
-        result.add("683");
-        result.add("685");
-        result.add("686");
-        result.add("687");
-        result.add("688");
-        result.add("689");
-        result.add("690");
-        result.add("691");
-        result.add("692");
-        result.add("800");
-        result.add("808");
-        result.add("850");
-        result.add("852");
-        result.add("853");
-        result.add("855");
-        result.add("856");
-        result.add("870");
-        result.add("878");
-        result.add("880");
-        result.add("881");
-        result.add("882");
-        result.add("883");
-        result.add("886");
-        result.add("888");
-        result.add("960");
-        result.add("961");
-        result.add("962");
-        result.add("963");
-        result.add("964");
-        result.add("965");
-        result.add("966");
-        result.add("967");
-        result.add("968");
-        result.add("970");
-        result.add("971");
-        result.add("972");
-        result.add("973");
-        result.add("974");
-        result.add("975");
-        result.add("976");
-        result.add("977");
-        result.add("979");
-        result.add("992");
-        result.add("993");
-        result.add("994");
-        result.add("995");
-        result.add("996");
-        result.add("998");
-        return result;
-    }
-}
diff --git a/tests/src/com/android/dialer/database/SmartDialPrefixTest.java b/tests/src/com/android/dialer/database/SmartDialPrefixTest.java
new file mode 100644
index 0000000..db0a0f3
--- /dev/null
+++ b/tests/src/com/android/dialer/database/SmartDialPrefixTest.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.database;
+
+import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.AndroidTestCase;
+
+import com.android.dialer.database.DialerDatabaseHelper;
+import com.android.dialer.database.DialerDatabaseHelper.ContactNumber;
+import com.android.dialer.dialpad.SmartDialNameMatcher;
+import com.android.dialer.dialpad.SmartDialPrefix;
+
+import junit.framework.TestCase;
+
+import java.lang.Exception;
+import java.lang.FindBugsSuppressWarnings;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+
+/**
+ * To run this test, use the command:
+ * adb shell am instrument -w -e class com.android.dialer.dialpad.SmartDialPrefixTest /
+ * com.android.dialer.tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class SmartDialPrefixTest extends AndroidTestCase {
+
+    private DialerDatabaseHelper mTestHelper;
+
+    public void testIsCountryNanp_CaseInsensitive() {
+        assertFalse(SmartDialPrefix.isCountryNanp(null));
+        assertFalse(SmartDialPrefix.isCountryNanp("CN"));
+        assertFalse(SmartDialPrefix.isCountryNanp("HK"));
+        assertFalse(SmartDialPrefix.isCountryNanp("uk"));
+        assertFalse(SmartDialPrefix.isCountryNanp("sg"));
+        assertTrue(SmartDialPrefix.isCountryNanp("US"));
+        assertTrue(SmartDialPrefix.isCountryNanp("CA"));
+        assertTrue(SmartDialPrefix.isCountryNanp("AS"));
+        assertTrue(SmartDialPrefix.isCountryNanp("AI"));
+        assertTrue(SmartDialPrefix.isCountryNanp("AG"));
+        assertTrue(SmartDialPrefix.isCountryNanp("BS"));
+        assertTrue(SmartDialPrefix.isCountryNanp("BB"));
+        assertTrue(SmartDialPrefix.isCountryNanp("bm"));
+        assertTrue(SmartDialPrefix.isCountryNanp("vg"));
+        assertTrue(SmartDialPrefix.isCountryNanp("ky"));
+        assertTrue(SmartDialPrefix.isCountryNanp("dm"));
+        assertTrue(SmartDialPrefix.isCountryNanp("do"));
+        assertTrue(SmartDialPrefix.isCountryNanp("gd"));
+        assertTrue(SmartDialPrefix.isCountryNanp("gu"));
+        assertTrue(SmartDialPrefix.isCountryNanp("jm"));
+        assertTrue(SmartDialPrefix.isCountryNanp("pr"));
+        assertTrue(SmartDialPrefix.isCountryNanp("ms"));
+        assertTrue(SmartDialPrefix.isCountryNanp("mp"));
+        assertTrue(SmartDialPrefix.isCountryNanp("kn"));
+        assertTrue(SmartDialPrefix.isCountryNanp("lc"));
+        assertTrue(SmartDialPrefix.isCountryNanp("vc"));
+        assertTrue(SmartDialPrefix.isCountryNanp("tt"));
+        assertTrue(SmartDialPrefix.isCountryNanp("tc"));
+        assertTrue(SmartDialPrefix.isCountryNanp("vi"));
+    }
+
+    protected void setUp() {
+        mTestHelper = DialerDatabaseHelper.getNewInstanceForTest(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+        mTestHelper.removeAllContacts(db);
+        super.tearDown();
+    }
+
+    @Suppress
+    public void testForNewContacts() {
+    }
+
+    @Suppress
+    public void testForUpdatedContacts() {
+    }
+
+    @Suppress
+    public void testForDeletedContacts() {
+    }
+
+    @Suppress
+    public void testSize() {
+    }
+
+
+    private MatrixCursor constructNewNameCursor() {
+        final MatrixCursor cursor = new MatrixCursor(new String[]{
+                DialerDatabaseHelper.SmartDialDbColumns.DISPLAY_NAME_PRIMARY,
+                DialerDatabaseHelper.SmartDialDbColumns.CONTACT_ID});
+        return cursor;
+    }
+
+    private MatrixCursor constructNewContactCursor() {
+        final MatrixCursor cursor = new MatrixCursor(new String[]{
+                Phone._ID,
+                Phone.TYPE,
+                Phone.LABEL,
+                Phone.NUMBER,
+                Phone.CONTACT_ID,
+                Phone.LOOKUP_KEY,
+                Phone.DISPLAY_NAME_PRIMARY,
+                Data.LAST_TIME_USED,
+                Data.TIMES_USED,
+                Contacts.STARRED,
+                Data.IS_SUPER_PRIMARY,
+                Contacts.IN_VISIBLE_GROUP,
+                Data.IS_PRIMARY});
+
+        return cursor;
+    }
+
+    private ContactNumber constructNewContact(MatrixCursor contactCursor, MatrixCursor nameCursor,
+            int id, String number, int contactId, String lookupKey, String displayName,
+            int lastTimeUsed, int timesUsed, int starred, int isSuperPrimary, int inVisibleGroup,
+            int isPrimary) {
+        assertNotNull(contactCursor);
+        assertNotNull(nameCursor);
+
+        contactCursor.addRow(new Object[]{id, "", "", number, contactId, lookupKey, displayName,
+                lastTimeUsed, timesUsed, starred, isSuperPrimary, inVisibleGroup, isPrimary});
+        nameCursor.addRow(new Object[]{displayName, contactId});
+
+        return new ContactNumber(contactId, displayName, number, lookupKey);
+    }
+
+    private ArrayList<ContactNumber> getLooseMatchesFromDb(String query) {
+        final SmartDialNameMatcher nameMatcher = new SmartDialNameMatcher(query,
+                SmartDialPrefix.getMap());
+        return mTestHelper.getLooseMatches(query, nameMatcher);
+    }
+
+    public void testPutForFullName() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber jasonsmith = constructNewContact(contactCursor, nameCursor,
+                0, "", 0, "", "Jason Smith", 0, 0, 0, 0, 0, 0);
+        final ContactNumber jasonsmitt = constructNewContact(contactCursor, nameCursor,
+                1, "", 1, "", "Jason Smitt", 0, 0, 0, 0, 0, 0);
+        final ContactNumber alphabet = constructNewContact(contactCursor, nameCursor,
+                0, "12345678", 0, "", "abc def ghi jkl mno pqrs tuv wxyz", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        final ArrayList<ContactNumber> result1 = getLooseMatchesFromDb("5276676484");
+        assertFalse(result1.contains(jasonsmitt));
+
+        final ArrayList<ContactNumber> result2 = getLooseMatchesFromDb("5276676488");
+        assertFalse(result2.contains(jasonsmith));
+        assertTrue(result2.contains(jasonsmitt));
+
+        assertTrue(getLooseMatchesFromDb("22233344455566677778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("33344455566677778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("44455566677778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("55566677778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("66677778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("77778889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("8889999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("9999").contains(alphabet));
+
+        // Makes sure the phone number is correctly added.
+        assertTrue(getLooseMatchesFromDb("12345678").contains(alphabet));
+    }
+
+    public void testPutForPartialName() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber maryjane = constructNewContact(contactCursor, nameCursor,
+                0, "", 0, "", "Mary Jane", 0, 0, 0, 0, 0, 0);
+        final ContactNumber sarahsmith = constructNewContact(contactCursor, nameCursor,
+                0, "", 1, "", "Sarah Smith", 0, 0, 0, 0, 0, 0);
+        final ContactNumber jasonsmitt = constructNewContact(contactCursor, nameCursor,
+                0, "", 2, "", "Jason Smitt", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        final ArrayList<ContactNumber> result1 = getLooseMatchesFromDb("6279");
+        assertTrue(result1.contains(maryjane));
+        assertFalse(result1.contains(jasonsmitt));
+
+        // 72 corresponds to sa = "Sarah Smith" but not "Jason Smitt" or "Mary Jane"
+        final ArrayList<ContactNumber> result2 = getLooseMatchesFromDb("72");
+        assertFalse(result2.contains(maryjane));
+        assertTrue(result2.contains(sarahsmith));
+        assertFalse(result2.contains(jasonsmitt));
+
+        // 76 corresponds to sm = "Sarah Smith" and "Jason Smitt" but not "Mary Jane"
+        final ArrayList<ContactNumber> result3 = getLooseMatchesFromDb("76");
+        assertFalse(result3.contains(maryjane));
+        assertTrue(result3.contains(sarahsmith));
+        assertTrue(result3.contains(jasonsmitt));
+    }
+
+    public void testPutForNameTokens() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber jasonfwilliams = constructNewContact(contactCursor, nameCursor,
+                0, "", 0, "", "Jason F. Williams", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("527").contains(jasonfwilliams));
+        // 72 corresponds to sa = "Sarah Smith" but not "Jason Smitt" or "Mary Jane"
+        assertTrue(getLooseMatchesFromDb("945").contains(jasonfwilliams));
+        // 76 corresponds to sm = "Sarah Smith" and "Jason Smitt" but not "Mary Jane"
+        assertFalse(getLooseMatchesFromDb("66").contains(jasonfwilliams));
+    }
+
+    public void testPutForInitialMatches() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber martinjuniorharry = constructNewContact(contactCursor, nameCursor,
+                0, "", 0, "", "Martin Jr Harry", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        // 654 corresponds to mjh = "(M)artin (J)r (H)arry"
+        assertTrue(getLooseMatchesFromDb("654").contains(martinjuniorharry));
+        // The reverse (456) does not match (for now)
+        assertFalse(getLooseMatchesFromDb("456").contains(martinjuniorharry));
+        // 6542 corresponds to mjha = "(M)artin (J)r (Ha)rry"
+        assertTrue(getLooseMatchesFromDb("6542").contains(martinjuniorharry));
+        // 542 corresponds to jha = "Martin (J)r (Ha)rry"
+        assertTrue(getLooseMatchesFromDb("542").contains(martinjuniorharry));
+        // 642 corresponds to mha = "(M)artin Jr (Ha)rry"
+        assertTrue(getLooseMatchesFromDb("642").contains(martinjuniorharry));
+        // 6542779 (M)artin (J)r (Harry)
+        assertTrue(getLooseMatchesFromDb("6542779").contains(martinjuniorharry));
+        // 65742779 (M)artin (Jr) (Harry)
+        assertTrue(getLooseMatchesFromDb("65742779").contains(martinjuniorharry));
+        // 542779 Martin (J)r (Harry)
+        assertTrue(getLooseMatchesFromDb("542779").contains(martinjuniorharry));
+        // 547 doesn't match
+        assertFalse(getLooseMatchesFromDb("547").contains(martinjuniorharry));
+        // 655 doesn't match
+        assertFalse(getLooseMatchesFromDb("655").contains(martinjuniorharry));
+        // 653 doesn't match
+        assertFalse(getLooseMatchesFromDb("653").contains(martinjuniorharry));
+        // 6543 doesn't match
+        assertFalse(getLooseMatchesFromDb("6543").contains(martinjuniorharry));
+
+        assertEquals(7, mTestHelper.countPrefixTableRows(db));
+    }
+
+    public void testPutForInitialMatchesForLongTokenNames() {
+
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber alphabet = constructNewContact(contactCursor, nameCursor,
+                0, "12345678", 0, "", "abc def ghi jkl mno pqrs tuv wxyz", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        // Makes sure only only the first two and last two token are considered for initials.
+        // The cut-off constant can be set in SmartDialPrefix.java
+        assertTrue(getLooseMatchesFromDb("2389999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("239999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("23888").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("2333").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("289999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("2888").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("29999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("3888").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("39999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("389999").contains(alphabet));
+        assertTrue(getLooseMatchesFromDb("89999").contains(alphabet));
+    }
+
+    public void testCheckLongToken() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber alphabet = constructNewContact(contactCursor, nameCursor,
+                0, "1", 0, "", " aaaa bbbb cccc dddd eeee ffff gggg" +
+                " hhhh iiii jjjj kkkk llll mmmm nnnn oooo pppp qqqq rrrr ssss tttt uuuu vvvv " +
+                " wwww xxxx yyyy zzzz", 0, 0, 0, 0, 0, 0);
+
+        final ContactNumber alphabet2 = constructNewContact(contactCursor, nameCursor,
+                0, "1", 1, "", "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnoooopppp" +
+                "qqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("2222").contains(alphabet));
+        assertEquals(40, mTestHelper.countPrefixTableRows(db));
+    }
+
+    public void testParseInfo() {
+        final String name = "Mcdonald Jamie-Cullum";
+        final ArrayList<String> info = SmartDialPrefix.parseToIndexTokens(name);
+        assertEquals(3, info.size());
+        assertEquals(8, info.get(0).length());
+        assertEquals(5, info.get(1).length());
+        assertEquals(6, info.get(2).length());
+
+        final String name2 = "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk";
+        final ArrayList<String> info2 = SmartDialPrefix.parseToIndexTokens(name2);
+        assertEquals(11, info2.size());
+        assertEquals(3, info2.get(0).length());
+        assertEquals(3, info2.get(10).length());
+
+        final String name3 = "this  is- a,test    name";
+        final ArrayList<String> info3 = SmartDialPrefix.parseToIndexTokens(name3);
+        assertEquals(5, info3.size());
+        assertEquals(2, info3.get(1).length());
+        assertEquals(1, info3.get(2).length());
+        assertEquals(4, info3.get(3).length());
+        assertEquals(4, info3.get(4).length());
+
+        final String name4 = "M c-Donald James";
+        final ArrayList<String> info4 = SmartDialPrefix.parseToIndexTokens(name4);
+        assertEquals(4, info4.size());
+        assertEquals(1, info4.get(1).length());
+        assertEquals(6, info4.get(2).length());
+
+        final String name5 = "   Aa'Bb    c    dddd  e'e";
+        final ArrayList<String> info5 = SmartDialPrefix.parseToIndexTokens(name5);
+        assertEquals(6, info5.size());
+        assertEquals(2, info5.get(0).length());
+        assertEquals(1, info5.get(5).length());
+    }
+
+    public void testAccentedCharacters() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber reene = constructNewContact(contactCursor, nameCursor,
+                0, "0", 0, "", "Reenée", 0, 0, 0, 0, 0, 0);
+        final ContactNumber bronte = constructNewContact(contactCursor, nameCursor,
+                0, "0", 1, "", "Brontë", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("733633").contains(reene));
+        assertTrue(getLooseMatchesFromDb("276683").contains(bronte));
+    }
+
+    public void testNumbersInName() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber contact = constructNewContact(contactCursor, nameCursor,
+                0, "0", 0, "", "12345678", 0, 0, 0, 0, 0, 0);
+        final ContactNumber teacher = constructNewContact(contactCursor, nameCursor,
+                0, "0", 1, "", "1st Grade Teacher", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("12345678").contains(contact));
+        assertTrue(getLooseMatchesFromDb("17847233").contains(teacher));
+        assertTrue(getLooseMatchesFromDb("14832").contains(teacher));
+    }
+
+    public void testPutForNumbers() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber contactno1 = constructNewContact(contactCursor, nameCursor,
+                0, "510-527-2357", 0, "", "James", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno2 = constructNewContact(contactCursor, nameCursor,
+                0, "77212862357", 1, "", "James", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno3 = constructNewContact(contactCursor, nameCursor,
+                0, "+13684976334", 2, "", "James", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("510").contains(contactno1));
+        assertFalse(getLooseMatchesFromDb("511").contains(contactno1));
+        assertTrue(getLooseMatchesFromDb("77212862357").contains(contactno2));
+        assertFalse(getLooseMatchesFromDb("77212862356").contains(contactno2));
+        assertTrue(getLooseMatchesFromDb("1368").contains(contactno3));
+        assertFalse(getLooseMatchesFromDb("1367").contains(contactno3));
+    }
+
+    public void testPutNumbersCountryCode() {
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber contactno1 = constructNewContact(contactCursor, nameCursor,
+                0, "+13684976334", 0, "", "James", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno2 = constructNewContact(contactCursor, nameCursor,
+                0, "+65 9177-6930", 1, "", "Jason", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno3 = constructNewContact(contactCursor, nameCursor,
+                0, "+85212345678", 2, "", "Mike", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno4 = constructNewContact(contactCursor, nameCursor,
+                0, "+85112345678", 3, "", "Invalid", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno5 = constructNewContact(contactCursor, nameCursor,
+                0, "+852", 4, "", "Invalid", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("1368").contains(contactno1));
+        assertTrue(getLooseMatchesFromDb("368497").contains(contactno1));
+        assertFalse(getLooseMatchesFromDb("2368497").contains(contactno1));
+
+        assertTrue(getLooseMatchesFromDb("6591776930").contains(contactno2));
+        assertTrue(getLooseMatchesFromDb("91776930").contains(contactno2));
+        assertFalse(getLooseMatchesFromDb("591776930").contains(contactno2));
+
+        assertTrue(getLooseMatchesFromDb("85212345678").contains(contactno3));
+        assertTrue(getLooseMatchesFromDb("12345678").contains(contactno3));
+        assertFalse(getLooseMatchesFromDb("5212345678").contains(contactno3));
+
+        assertTrue(getLooseMatchesFromDb("85112345678").contains(contactno4));
+        assertFalse(getLooseMatchesFromDb("12345678").contains(contactno4));
+    }
+
+    // Tests special case handling for NANP numbers
+    public void testPutNumbersNANP() {
+        SmartDialPrefix.setUserInNanpRegion(true);
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+        final ContactNumber contactno1 = constructNewContact(contactCursor, nameCursor,
+                0, "16503337596", 0, "", "James", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno2 = constructNewContact(contactCursor, nameCursor,
+                0, "5109921234", 1, "", "Michael", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno3 = constructNewContact(contactCursor, nameCursor,
+                0, "(415)-123-4567", 2, "", "Jason", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno4 = constructNewContact(contactCursor, nameCursor,
+                0, "1 510-284-9170", 3, "", "Mike", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno5 = constructNewContact(contactCursor, nameCursor,
+                0, "1-415-123-123", 4, "", "Invalid", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno6 = constructNewContact(contactCursor, nameCursor,
+                0, "415-123-123", 5, "", "Invalid2", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno7 = constructNewContact(contactCursor, nameCursor,
+                0, "+1-510-284-9170", 6, "", "Mike", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno8 = constructNewContact(contactCursor, nameCursor,
+                0, "+1-510-284-917", 7, "", "Invalid", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno9 = constructNewContact(contactCursor, nameCursor,
+                0, "+857-510-284-9170", 8, "", "Inv", 0, 0, 0, 0, 0, 0);
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("16503337596").contains(contactno1));
+        assertTrue(getLooseMatchesFromDb("6503337596").contains(contactno1));
+        assertTrue(getLooseMatchesFromDb("3337596").contains(contactno1));
+
+        assertTrue(getLooseMatchesFromDb("5109921234").contains(contactno2));
+        assertTrue(getLooseMatchesFromDb("9921234").contains(contactno2));
+
+        assertTrue(getLooseMatchesFromDb("4151234567").contains(contactno3));
+        assertTrue(getLooseMatchesFromDb("1234567").contains(contactno3));
+
+        assertTrue(getLooseMatchesFromDb("15102849170").contains(contactno4));
+        assertTrue(getLooseMatchesFromDb("5102849170").contains(contactno4));
+        assertTrue(getLooseMatchesFromDb("2849170").contains(contactno4));
+
+        assertTrue(getLooseMatchesFromDb("1415123123").contains(contactno5));
+        assertFalse(getLooseMatchesFromDb("415123123").contains(contactno5));
+        assertFalse(getLooseMatchesFromDb("123123").contains(contactno5));
+
+        assertTrue(getLooseMatchesFromDb("415123123").contains(contactno6));
+        assertFalse(getLooseMatchesFromDb("123123").contains(contactno6));
+
+        assertTrue(getLooseMatchesFromDb("15102849170").contains(contactno7));
+        assertTrue(getLooseMatchesFromDb("5102849170").contains(contactno7));
+        assertTrue(getLooseMatchesFromDb("2849170").contains(contactno7));
+        assertFalse(getLooseMatchesFromDb("849170").contains(contactno7));
+        assertFalse(getLooseMatchesFromDb("10849170").contains(contactno7));
+
+        assertTrue(getLooseMatchesFromDb("1510284917").contains(contactno8));
+        assertTrue(getLooseMatchesFromDb("510284917").contains(contactno8));
+        assertFalse(getLooseMatchesFromDb("2849170").contains(contactno8));
+
+        assertTrue(getLooseMatchesFromDb("8575102849170").contains(contactno9));
+        assertFalse(getLooseMatchesFromDb("5102849170").contains(contactno9));
+        assertFalse(getLooseMatchesFromDb("2849170").contains(contactno9));
+
+//        // If user's region is determined to be not in North America, then the NANP number
+//        // workarounds should not be applied
+//        final SmartDialTrie trieNonNANP = new SmartDialTrie();
+//
+//        trieNonNANP.put(contactno3);
+//        assertTrue(checkContains(trieNonNANP, contactno3, "4151234567"));
+//        assertFalse(checkContains(trieNonNANP, contactno3, "1234567"));
+//
+//        trieNonNANP.put(contactno4);
+//        assertTrue(checkContains(trieNonNANP, contactno4, "15102849170"));
+//        assertFalse(checkContains(trieNonNANP, contactno4, "5102849170"));
+//        assertFalse(checkContains(trieNonNANP, contactno4, "2849170"));
+    }
+
+    // Tests special case handling for non-NANP numbers
+    public void testPutNumbersNonNANP() {
+        SmartDialPrefix.setUserInNanpRegion(false);
+        final SQLiteDatabase db = mTestHelper.getWritableDatabase();
+
+        final MatrixCursor nameCursor =  constructNewNameCursor();
+        final MatrixCursor contactCursor = constructNewContactCursor();
+
+        final ContactNumber contactno0 = constructNewContact(contactCursor, nameCursor,
+                0, "(415)-123-4567", 0, "", "Jason", 0, 0, 0, 0, 0, 0);
+        final ContactNumber contactno1 = constructNewContact(contactCursor, nameCursor,
+                0, "1 510-284-9170", 1, "", "Mike", 0, 0, 0, 0, 0, 0);
+
+
+        mTestHelper.insertUpdatedContactsAndNumberPrefix(db, contactCursor, Long.valueOf(0));
+        mTestHelper.insertNamePrefixes(db, nameCursor);
+
+        nameCursor.close();
+        contactCursor.close();
+
+        assertTrue(getLooseMatchesFromDb("4151234567").contains(contactno0));
+        assertFalse(getLooseMatchesFromDb("1234567").contains(contactno0));
+
+        assertTrue(getLooseMatchesFromDb("15102849170").contains(contactno1));
+        assertFalse(getLooseMatchesFromDb("5102849170").contains(contactno1));
+        assertFalse(getLooseMatchesFromDb("2849170").contains(contactno1));
+    }
+}
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java b/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java
deleted file mode 100644
index 8d96eda..0000000
--- a/tests/src/com/android/dialer/dialpad/SmartDialCacheTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.dialpad;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-@SmallTest
-public class SmartDialCacheTest extends TestCase {
-    public void testIsCountryNanp_CaseInsensitive() {
-        assertFalse(SmartDialCache.isCountryNanp(null));
-        assertFalse(SmartDialCache.isCountryNanp("CN"));
-        assertFalse(SmartDialCache.isCountryNanp("HK"));
-        assertFalse(SmartDialCache.isCountryNanp("uk"));
-        assertFalse(SmartDialCache.isCountryNanp("sg"));
-        assertTrue(SmartDialCache.isCountryNanp("US"));
-        assertTrue(SmartDialCache.isCountryNanp("CA"));
-        assertTrue(SmartDialCache.isCountryNanp("AS"));
-        assertTrue(SmartDialCache.isCountryNanp("AI"));
-        assertTrue(SmartDialCache.isCountryNanp("AG"));
-        assertTrue(SmartDialCache.isCountryNanp("BS"));
-        assertTrue(SmartDialCache.isCountryNanp("BB"));
-        assertTrue(SmartDialCache.isCountryNanp("bm"));
-        assertTrue(SmartDialCache.isCountryNanp("vg"));
-        assertTrue(SmartDialCache.isCountryNanp("ky"));
-        assertTrue(SmartDialCache.isCountryNanp("dm"));
-        assertTrue(SmartDialCache.isCountryNanp("do"));
-        assertTrue(SmartDialCache.isCountryNanp("gd"));
-        assertTrue(SmartDialCache.isCountryNanp("gu"));
-        assertTrue(SmartDialCache.isCountryNanp("jm"));
-        assertTrue(SmartDialCache.isCountryNanp("pr"));
-        assertTrue(SmartDialCache.isCountryNanp("ms"));
-        assertTrue(SmartDialCache.isCountryNanp("mp"));
-        assertTrue(SmartDialCache.isCountryNanp("kn"));
-        assertTrue(SmartDialCache.isCountryNanp("lc"));
-        assertTrue(SmartDialCache.isCountryNanp("vc"));
-        assertTrue(SmartDialCache.isCountryNanp("tt"));
-        assertTrue(SmartDialCache.isCountryNanp("tc"));
-        assertTrue(SmartDialCache.isCountryNanp("vi"));
-    }
-}
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
index 47edaf3..1e578ee 100644
--- a/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
+++ b/tests/src/com/android/dialer/dialpad/SmartDialNameMatcherTest.java
@@ -19,8 +19,10 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
+import android.test.AndroidTestCase;
 
 import com.android.dialer.dialpad.SmartDialNameMatcher;
+import com.android.dialer.dialpad.SmartDialPrefix;
 
 import java.text.Normalizer;
 import java.util.ArrayList;
@@ -237,8 +239,7 @@
     private void checkMatchesNumber(String number, String query, boolean expectedMatches,
             boolean matchNanp, int matchStart, int matchEnd) {
         final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
-        final SmartDialMatchPosition pos = matcher.matchesNumber(number, query,
-                matchNanp);
+        final SmartDialMatchPosition pos = matcher.matchesNumber(number, query, matchNanp);
         assertEquals(expectedMatches, pos != null);
         if (expectedMatches) {
             assertEquals("start", matchStart, pos.start);
diff --git a/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java b/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java
deleted file mode 100644
index f0c4cbb..0000000
--- a/tests/src/com/android/dialer/dialpad/SmartDialTrieTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.dialpad;
-
-import static com.android.dialer.dialpad.SmartDialCache.ContactNumber;
-
-import com.android.dialer.dialpad.SmartDialTrie.Node;
-import com.android.dialer.dialpad.SmartDialTrie.ParseInfo;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-/**
- * To run this test, use the command:
- * adb shell am instrument -w -e class com.android.dialer.dialpad.SmartDialTrieTest /
- * com.android.dialer.tests/android.test.InstrumentationTestRunner
- */
-@SmallTest
-public class SmartDialTrieTest extends TestCase{
-
-    public void testSize() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        trie.put(new ContactNumber(0, "Jason", "0", "0", 1));
-        assertEquals(1, trie.size());
-        trie.put(new ContactNumber(1, "Mary", "0", "1", 2));
-        assertEquals(2, trie.size());
-        trie.put(new ContactNumber(2, "John", "0", "2", 3));
-        assertEquals(3, trie.size());
-    }
-
-    public void testPutForFullName() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber jasonsmith = new ContactNumber(0, "Jason Smith", "0", "0", 1);
-        final ContactNumber jasonsmitt = new ContactNumber(1, "Jason Smitt", "0", "1", 2);
-        trie.put(jasonsmith);
-        trie.put(jasonsmitt);
-        assertTrue(trie.getAllWithPrefix("5276676484").contains(jasonsmith));
-        assertFalse(trie.getAllWithPrefix("5276676484").contains(jasonsmitt));
-
-        assertFalse(trie.getAllWithPrefix("5276676488").contains(jasonsmith));
-        assertTrue(trie.getAllWithPrefix("5276676488").contains(jasonsmitt));
-
-    }
-
-    public void testPutForPartialName() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber maryjane = new ContactNumber(0, "Mary Jane", "0", "0", 1);
-        final ContactNumber sarahsmith = new ContactNumber(1, "Sarah Smith", "0", "1", 2);
-        final ContactNumber jasonsmitt = new ContactNumber(2, "Jason Smitt", "0", "2", 3);
-        trie.put(maryjane);
-        trie.put(sarahsmith);
-        trie.put(jasonsmitt);
-
-        // 6279 corresponds to mary = "Mary Jane" but not "Jason Smitt"
-        assertTrue(checkContains(trie, maryjane, "6279"));
-        assertFalse(checkContains(trie, jasonsmitt, "6279"));
-
-        // 72 corresponds to sa = "Sarah Smith" but not "Jason Smitt" or "Mary Jane"
-        assertFalse(checkContains(trie, maryjane, "72"));
-        assertTrue(checkContains(trie, sarahsmith, "72"));
-        assertFalse(checkContains(trie, jasonsmitt, "72"));
-
-        // 76 corresponds to sm = "Sarah Smith" and "Jason Smitt" but not "Mary Jane"
-        assertFalse(checkContains(trie, maryjane, "76"));
-        assertTrue(checkContains(trie, sarahsmith, "76"));
-        assertTrue(checkContains(trie, jasonsmitt, "76"));
-    }
-
-    public void testPutForNameTokens() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber jasonfwilliams = new ContactNumber(0, "Jason F. Williams", "0", "0", 1);
-        trie.put(jasonfwilliams);
-
-        // 527 corresponds to jas = "Jason"
-        assertTrue(checkContains(trie, jasonfwilliams, "527"));
-        // 945 corresponds to wil = "Wil"
-        assertTrue(checkContains(trie, jasonfwilliams, "945"));
-        // 66 doesn't match
-        assertFalse(checkContains(trie, jasonfwilliams, "66"));
-    }
-
-    public void testPutForInitialMatches() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber martinjuniorharry =
-                new ContactNumber(0, "Martin Jr Harry", "0", "0", 1);
-        trie.put(martinjuniorharry);
-        // 654 corresponds to mjh = "(M)artin (J)r (H)arry"
-        assertTrue(checkContains(trie, martinjuniorharry, "654"));
-        // The reverse (456) does not match (for now)
-        assertFalse(checkContains(trie, martinjuniorharry, "456"));
-        // 6542 corresponds to mjha = "(M)artin (J)r (Ha)rry"
-        assertTrue(checkContains(trie, martinjuniorharry, "6542"));
-        // 542 corresponds to jha = "Martin (J)r (Ha)rry"
-        assertTrue(checkContains(trie, martinjuniorharry, "542"));
-        // 642 corresponds to mha = "(M)artin Jr (Ha)rry"
-        assertTrue(checkContains(trie, martinjuniorharry, "642"));
-        // 6542779 (M)artin (J)r (Harry)
-        assertTrue(checkContains(trie, martinjuniorharry, "6542779"));
-        // 65742779 (M)artin (Jr) (Harry)
-        assertTrue(checkContains(trie, martinjuniorharry, "65742779"));
-        // 542779 Martin (J)r (Harry)
-        assertTrue(checkContains(trie, martinjuniorharry, "542779"));
-        // 547 doesn't match
-        assertFalse(checkContains(trie, martinjuniorharry, "547"));
-        // 655 doesn't match
-        assertFalse(checkContains(trie, martinjuniorharry, "655"));
-        // 653 doesn't match
-        assertFalse(checkContains(trie, martinjuniorharry, "653"));
-        // 6543 doesn't match
-        assertFalse(checkContains(trie, martinjuniorharry, "6543"));
-        // 7(2^3 -1) entries for the name, and 1 for the number
-        assertEquals(8, trie.numEntries());
-    }
-
-    public void testPutForInitialMatchesCombinations() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber alphabet = new ContactNumber(0, "abc def ghi jkl mno pqrs tuv wxyz",
-                "12345678", "1", 2);
-        trie.put(alphabet);
-        assertEquals(20, trie.numEntries());
-        // 8 name entries (abcdefghi..., defghi..., ...)
-        assertTrue(checkContains(trie, alphabet, "22233344455566677778889999"));
-        assertTrue(checkContains(trie, alphabet, "33344455566677778889999"));
-        assertTrue(checkContains(trie, alphabet, "44455566677778889999"));
-        assertTrue(checkContains(trie, alphabet, "55566677778889999"));
-        assertTrue(checkContains(trie, alphabet, "66677778889999"));
-        assertTrue(checkContains(trie, alphabet, "77778889999"));
-        assertTrue(checkContains(trie, alphabet, "8889999"));
-        assertTrue(checkContains(trie, alphabet, "9999"));
-        // 1 number entry
-        assertTrue(checkContains(trie, alphabet, "12345678"));
-        // 11 initial entries (adtw, adw, adt, ad, atw, at, aw, dt, dw, dtw, tw)
-        // 4c2(6) + 4c3(4) + 4c4(1)
-        assertTrue(checkContains(trie, alphabet, "2389999"));
-        assertTrue(checkContains(trie, alphabet, "239999"));
-        assertTrue(checkContains(trie, alphabet, "23888"));
-        assertTrue(checkContains(trie, alphabet, "2333"));
-        assertTrue(checkContains(trie, alphabet, "289999"));
-        assertTrue(checkContains(trie, alphabet, "2888"));
-        assertTrue(checkContains(trie, alphabet, "29999"));
-        assertTrue(checkContains(trie, alphabet, "3888"));
-        assertTrue(checkContains(trie, alphabet, "39999"));
-        assertTrue(checkContains(trie, alphabet, "389999"));
-        assertTrue(checkContains(trie, alphabet, "89999"));
-    }
-
-    public void testCheckLongToken() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber alphabet = new ContactNumber(0, " aaaa bbbb cccc dddd eeee ffff gggg" +
-                " hhhh iiii jjjj kkkk llll mmmm nnnn oooo pppp qqqq rrrr ssss tttt uuuu vvvv " +
-                " wwww xxxx yyyy zzzz", "1", "1", 2);
-        // Make sure the check to prevent overly long tokens from causing an OOM kicks in
-        trie.put(alphabet);
-        assertTrue(checkContains(trie, alphabet, "2222"));
-        // 26 name entries (aaaabbbbcccc...., bbbbccccdddd...., ccccdddd...)
-        // 1 number entry
-        // 11 initial entries 4c2(6) + 4c3(4) + 4c4(1)
-        assertEquals(38, trie.numEntries());
-
-        final ContactNumber alphabet2 = new ContactNumber(0, "aaaabbbbccccddddeeeeffffgggg" +
-                "hhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz",
-                "1", "1", 2);
-        trie.put(alphabet2);
-        // added one name, and one number entry
-        assertEquals(40, trie.numEntries());
-    }
-
-    public void testParseInfo() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final String name = "Mcdonald Jamie-Cullum";
-        final ParseInfo info = trie.parseToIndexes(name, 2, 2);
-        // Make sure the dash is correctly converted to a separator character
-        for (int i = 0; i < name.length(); i++) {
-            // separators at position 8 and 12
-            if (i == 8 || i == 14) {
-                assertTrue(info.indexes[i] == -1);
-            } else {
-                assertFalse(info.indexes[i] == -1);
-            }
-        }
-        assertEquals(14, info.nthFirstTokenPos);
-        assertEquals(8, info.nthLastTokenPos);
-
-        final String name2 = "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk";
-        final ParseInfo info2 = trie.parseToIndexes(name2, 2, 2);
-        assertEquals(7, info2.nthFirstTokenPos);
-        assertEquals(35, info2.nthLastTokenPos);
-
-        final String name3 = "this  is- a,test    name";
-        final ParseInfo info3 = trie.parseToIndexes(name3, 3, 3);
-        assertEquals(11, info3.nthFirstTokenPos);
-        assertEquals(8, info3.nthLastTokenPos);
-
-        final String name4 = "M c-Donald James";
-        final ParseInfo info4 = trie.parseToIndexes(name4, 2, 3);
-        assertEquals(3, info4.nthFirstTokenPos);
-        assertEquals(1, info4.nthLastTokenPos);
-
-        final String name5 = "   Aa'Bb    c    dddd  e'e";
-        final ParseInfo info5 = trie.parseToIndexes(name5, 4, 4);
-        assertEquals(21, info5.nthFirstTokenPos);
-        assertEquals(8, info5.nthLastTokenPos);
-    }
-
-    public void testAccentedCharacters() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber reenee = new ContactNumber(0, "Reenée", "0", "0", 1);
-        final ContactNumber bronte = new ContactNumber(2, "Brontë", "0", "1", 2);
-        trie.put(reenee);
-        trie.put(bronte);
-        assertTrue(checkContains(trie, reenee, "733633"));
-        assertTrue(checkContains(trie, bronte, "276683"));
-    }
-
-    public void testNumbersInName() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber contact = new ContactNumber(0, "12345678", "0", "0", 1);
-        final ContactNumber teacher = new ContactNumber(1, "1st Grade Teacher", "0", "1", 2);
-        trie.put(contact);
-        trie.put(teacher);
-        assertTrue(checkContains(trie, contact, "12345678"));
-        // (1st Grade) Teacher
-        assertTrue(checkContains(trie, teacher, "17847233"));
-        // (1)st (G)rade (Tea)cher
-        assertTrue(checkContains(trie, teacher, "14832"));
-    }
-
-    public void testPutForNumbers() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber contactno1 = new ContactNumber(0, "James", "510-527-2357", "0", 1);
-        trie.put(contactno1);
-        final ContactNumber contactno2 = new ContactNumber(0, "James", "77212862357", "0", 1);
-        trie.put(contactno2);
-        final ContactNumber contactno3 = new ContactNumber(0, "James", "+13684976334", "0", 1);
-        trie.put(contactno3);
-        // all phone numbers belonging to the contact should correspond to it
-        assertTrue(checkContains(trie, contactno1, "510"));
-        assertFalse(checkContains(trie, contactno1, "511"));
-        assertTrue(checkContains(trie, contactno2, "77212862357"));
-        assertFalse(checkContains(trie, contactno2, "77212862356"));
-        assertTrue(checkContains(trie, contactno3, "1368"));
-        assertFalse(checkContains(trie, contactno3, "1367"));
-
-    }
-
-    public void testPutNumbersCountryCode() {
-        final SmartDialTrie trie = new SmartDialTrie();
-        final ContactNumber contactno1 = new ContactNumber(0, "James", "+13684976334", "0", 1);
-        trie.put(contactno1);
-
-        // all phone numbers belonging to the contact should correspond to it
-        assertTrue(checkContains(trie, contactno1, "1368"));
-        assertTrue(checkContains(trie, contactno1, "368497"));
-        assertFalse(checkContains(trie, contactno1, "2368497"));
-
-        final ContactNumber contactno2 = new ContactNumber(0, "Jason", "+65 9177-6930", "0", 1);
-        trie.put(contactno2);
-
-        assertTrue(checkContains(trie, contactno2, "6591776930"));
-        assertTrue(checkContains(trie, contactno2, "91776930"));
-        assertFalse(checkContains(trie, contactno2, "591776930"));
-
-        final ContactNumber contactno3 = new ContactNumber(0, "Mike", "+85212345678", "0", 1);
-        trie.put(contactno3);
-        assertTrue(checkContains(trie, contactno3, "85212345678"));
-        assertTrue(checkContains(trie, contactno3, "12345678"));
-        assertFalse(checkContains(trie, contactno2, "5212345678"));
-
-        // Invalid country code, don't try to parse it
-        final ContactNumber contactno4 = new ContactNumber(0, "Invalid", "+85112345678", "0", 1);
-        trie.put(contactno4);
-        assertTrue(checkContains(trie, contactno4, "85112345678"));
-        assertFalse(checkContains(trie, contactno4, "12345678"));
-
-        final ContactNumber contactno5 = new ContactNumber(0, "Invalid", "+852", "0", 1);
-        // Shouldn't crash
-        trie.put(contactno5);
-    }
-
-    // Tests special case handling for NANP numbers
-    public void testPutNumbersNANP() {
-        final SmartDialTrie trie = new SmartDialTrie(true /* formatNanp */);
-        // Unformatted number with 1 prefix
-        final ContactNumber contactno1 = new ContactNumber(0, "James", "16503337596", "0", 1);
-        trie.put(contactno1);
-
-        assertTrue(checkContains(trie, contactno1, "16503337596"));
-        assertTrue(checkContains(trie, contactno1, "6503337596"));
-        assertTrue(checkContains(trie, contactno1, "3337596"));
-
-        // Number with seperators
-        final ContactNumber contactno2 = new ContactNumber(0, "Michael", "5109921234", "0", 1);
-        trie.put(contactno2);
-        assertTrue(checkContains(trie, contactno2, "5109921234"));
-        assertTrue(checkContains(trie, contactno2, "9921234"));
-
-        // Number with area code only + separators
-        final ContactNumber contactno3 = new ContactNumber(0, "Jason", "(415)-123-4567", "0", 1);
-        trie.put(contactno3);
-        assertTrue(checkContains(trie, contactno3, "4151234567"));
-        assertTrue(checkContains(trie, contactno3, "1234567"));
-
-        // Number without +1 prefix but is a NANP number
-        final ContactNumber contactno4 = new ContactNumber(0, "Mike", "1 510-284-9170", "0", 1);
-        trie.put(contactno4);
-        assertTrue(checkContains(trie, contactno4, "15102849170"));
-        assertTrue(checkContains(trie, contactno4, "5102849170"));
-        assertTrue(checkContains(trie, contactno4, "2849170"));
-
-        // Invalid number(has 1 prefix, but is only 10 characters long)
-        final ContactNumber contactno5 = new ContactNumber(0, "Invalid", "1-415-123-123", "0", 1);
-        trie.put(contactno5);
-        // It should still be inserted as is
-        assertTrue(checkContains(trie, contactno5, "1415123123"));
-        // But the NANP special case handling should not work
-        assertFalse(checkContains(trie, contactno5, "415123123"));
-        assertFalse(checkContains(trie, contactno5, "123123"));
-
-        // Invalid number(only 9 characters long)
-        final ContactNumber contactno6 = new ContactNumber(0, "Invalid2", "415-123-123", "0", 1);
-        trie.put(contactno6);
-        // It should still be inserted as is
-        assertTrue(checkContains(trie, contactno6, "415123123"));
-        // But the NANP special case handling should not work
-        assertFalse(checkContains(trie, contactno6, "123123"));
-
-        // Number with +1 prefix and is a NANP number
-        final ContactNumber contactno7 = new ContactNumber(0, "Mike", "+1-510-284-9170", "0", 1);
-        trie.put(contactno7);
-        assertTrue(checkContains(trie, contactno7, "15102849170"));
-        assertTrue(checkContains(trie, contactno7, "5102849170"));
-        assertTrue(checkContains(trie, contactno7, "2849170"));
-        assertFalse(checkContains(trie, contactno7, "849170"));
-        assertFalse(checkContains(trie, contactno7, "10849170"));
-
-        // Number with +1 prefix but is an invalid NANP number
-        final ContactNumber contactno8 = new ContactNumber(0, "Invalid", "+1-510-284-917", "0", 1);
-        trie.put(contactno8);
-        assertTrue(checkContains(trie, contactno8, "1510284917"));
-        assertTrue(checkContains(trie, contactno8, "510284917"));
-        assertFalse(checkContains(trie, contactno8, "2849170"));
-
-        // Number with invalid country code prefix
-        final ContactNumber contactno9 = new ContactNumber(0, "Inv", "+857-510-284-9170", "0", 1);
-        trie.put(contactno9);
-        assertTrue(checkContains(trie, contactno9, "8575102849170"));
-        assertFalse(checkContains(trie, contactno9, "5102849170"));
-        assertFalse(checkContains(trie, contactno9, "2849170"));
-
-        // If user's region is determined to be not in North America, then the NANP number
-        // workarounds should not be applied
-        final SmartDialTrie trieNonNANP = new SmartDialTrie();
-
-        trieNonNANP.put(contactno3);
-        assertTrue(checkContains(trieNonNANP, contactno3, "4151234567"));
-        assertFalse(checkContains(trieNonNANP, contactno3, "1234567"));
-
-        trieNonNANP.put(contactno4);
-        assertTrue(checkContains(trieNonNANP, contactno4, "15102849170"));
-        assertFalse(checkContains(trieNonNANP, contactno4, "5102849170"));
-        assertFalse(checkContains(trieNonNANP, contactno4, "2849170"));
-    }
-
-    public void testNodeConstructor() {
-        final Node n = new Node();
-        // Node member variables should not be initialized by default at construction to reduce
-        // unnecessary memory usage
-        assertEquals(-1, n.getChildrenSize());
-        assertNull(n.getChild(5, false));
-        assertNull(n.getChild(0, false));
-    }
-
-    public void testNodeGetChild() {
-        final Node n = new Node();
-        // A node shouldn't contain children until getChild(index, true) is called
-        assertEquals(-1, n.getChildrenSize());
-        final Node child = n.getChild(1, true);
-        // A node should always have 10 children once the child array is created
-        assertEquals(10, n.getChildrenSize());
-        // getChild(index, true) should never return null
-        assertNotNull(child);
-    }
-
-    public void testNodeAddContact() {
-        final Node n = new Node();
-        assertNull(n.getContents());
-        final ContactNumber contact = new ContactNumber(0, "James", "510-527-2357", "0", 1);
-        final ContactNumber contactNotIn = new ContactNumber(2, "Jason Smitt", "0", "2", 3);
-        n.add(contact);
-        assertTrue(n.getContents().contains(contact));
-        assertFalse(n.getContents().contains(contactNotIn));
-    }
-
-    private boolean checkContains(SmartDialTrie trie, ContactNumber contact, CharSequence prefix) {
-        return trie.getAllWithPrefix(prefix).contains(contact);
-    }
-}