• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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