Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.providers.contacts.aggregation.util;
     18 
     19 import android.app.ActivityManager;
     20 import android.database.Cursor;
     21 import android.database.sqlite.SQLiteDatabase;
     22 import android.util.ArrayMap;
     23 
     24 import com.android.providers.contacts.ContactsDatabaseHelper.NicknameLookupColumns;
     25 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
     26 
     27 import java.lang.ref.SoftReference;
     28 import java.util.BitSet;
     29 
     30 /**
     31  * Cache for common nicknames.
     32  */
     33 public class CommonNicknameCache  {
     34 
     35     // We will use this much memory (in bits) to optimize the nickname cluster lookup
     36     private static final int NICKNAME_BLOOM_FILTER_SIZE = 0x1FFF;   // =long[128]
     37     private BitSet mNicknameBloomFilter;
     38 
     39     private final ArrayMap<String, SoftReference<String[]>> mNicknameClusterCache
     40             = new ArrayMap<>();
     41 
     42     private final SQLiteDatabase mDb;
     43 
     44     public CommonNicknameCache(SQLiteDatabase db) {
     45         mDb = db;
     46     }
     47 
     48     private final static class NicknameLookupPreloadQuery {
     49         public final static String TABLE = Tables.NICKNAME_LOOKUP;
     50 
     51         public final static String[] COLUMNS = new String[] {
     52             NicknameLookupColumns.NAME
     53         };
     54 
     55         public final static int NAME = 0;
     56     }
     57 
     58     /**
     59      * Read all known common nicknames from the database and populate a Bloom
     60      * filter using the corresponding hash codes. The idea is to eliminate most
     61      * of unnecessary database lookups for nicknames. Given a name, we will take
     62      * its hash code and see if it is set in the Bloom filter. If not, we will know
     63      * that the name is not in the database. If it is, we still need to run a
     64      * query.
     65      * <p>
     66      * Given the size of the filter and the expected size of the nickname table,
     67      * we should expect the combination of the Bloom filter and cache will
     68      * prevent around 98-99% of unnecessary queries from running.
     69      */
     70     private void preloadNicknameBloomFilter() {
     71         mNicknameBloomFilter = new BitSet(NICKNAME_BLOOM_FILTER_SIZE + 1);
     72         Cursor cursor = mDb.query(NicknameLookupPreloadQuery.TABLE,
     73                 NicknameLookupPreloadQuery.COLUMNS,
     74                 null, null, null, null, null);
     75         try {
     76             int count = cursor.getCount();
     77             for (int i = 0; i < count; i++) {
     78                 cursor.moveToNext();
     79                 String normalizedName = cursor.getString(NicknameLookupPreloadQuery.NAME);
     80                 int hashCode = normalizedName.hashCode();
     81                 mNicknameBloomFilter.set(hashCode & NICKNAME_BLOOM_FILTER_SIZE);
     82             }
     83         } finally {
     84             cursor.close();
     85         }
     86     }
     87 
     88     /**
     89      * Returns nickname cluster IDs or null. Maintains cache.
     90      */
     91     public String[] getCommonNicknameClusters(String normalizedName) {
     92         if (ActivityManager.isLowRamDeviceStatic()) {
     93             return null; // Do not use common nickname cache on lowram devices.
     94         }
     95         if (mNicknameBloomFilter == null) {
     96             preloadNicknameBloomFilter();
     97         }
     98 
     99         int hashCode = normalizedName.hashCode();
    100         if (!mNicknameBloomFilter.get(hashCode & NICKNAME_BLOOM_FILTER_SIZE)) {
    101             return null;
    102         }
    103 
    104         SoftReference<String[]> ref;
    105         String[] clusters = null;
    106         synchronized (mNicknameClusterCache) {
    107             if (mNicknameClusterCache.containsKey(normalizedName)) {
    108                 ref = mNicknameClusterCache.get(normalizedName);
    109                 if (ref == null) {
    110                     return null;
    111                 }
    112                 clusters = ref.get();
    113             }
    114         }
    115 
    116         if (clusters == null) {
    117             clusters = loadNicknameClusters(normalizedName);
    118             ref = clusters == null ? null : new SoftReference<String[]>(clusters);
    119             synchronized (mNicknameClusterCache) {
    120                 mNicknameClusterCache.put(normalizedName, ref);
    121             }
    122         }
    123         return clusters;
    124     }
    125 
    126     private interface NicknameLookupQuery {
    127         String TABLE = Tables.NICKNAME_LOOKUP;
    128 
    129         String[] COLUMNS = new String[] {
    130             NicknameLookupColumns.CLUSTER
    131         };
    132 
    133         int CLUSTER = 0;
    134     }
    135 
    136     protected String[] loadNicknameClusters(String normalizedName) {
    137         String[] clusters = null;
    138         Cursor cursor = mDb.query(NicknameLookupQuery.TABLE, NicknameLookupQuery.COLUMNS,
    139                 NicknameLookupColumns.NAME + "=?", new String[] { normalizedName },
    140                 null, null, null);
    141         try {
    142             int count = cursor.getCount();
    143             if (count > 0) {
    144                 clusters = new String[count];
    145                 for (int i = 0; i < count; i++) {
    146                     cursor.moveToNext();
    147                     clusters[i] = cursor.getString(NicknameLookupQuery.CLUSTER);
    148                 }
    149             }
    150         } finally {
    151             cursor.close();
    152         }
    153         return clusters;
    154     }
    155 }
    156