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 CommonNicknameCache(SQLiteDatabase db)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 */ preloadNicknameBloomFilter()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 */ getCommonNicknameClusters(String normalizedName)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 loadNicknameClusters(String normalizedName)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