• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.mms.data;
2 
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 
8 import android.content.Context;
9 import android.content.ContentResolver;
10 import android.content.ContentValues;
11 import android.content.ContentUris;
12 import android.database.Cursor;
13 import android.net.Uri;
14 import android.text.TextUtils;
15 import android.util.Log;
16 import android.provider.Telephony;
17 
18 import com.android.mms.LogTag;
19 import android.database.sqlite.SqliteWrapper;
20 
21 public class RecipientIdCache {
22     private static final boolean LOCAL_DEBUG = false;
23     private static final String TAG = "Mms/cache";
24 
25     private static Uri sAllCanonical =
26             Uri.parse("content://mms-sms/canonical-addresses");
27 
28     private static Uri sSingleCanonicalAddressUri =
29             Uri.parse("content://mms-sms/canonical-address");
30 
31     private static RecipientIdCache sInstance;
getInstance()32     static RecipientIdCache getInstance() { return sInstance; }
33     private final Map<Long, String> mCache;
34     private final Context mContext;
35 
36     public static class Entry {
37         public long id;
38         public String number;
39 
Entry(long id, String number)40         public Entry(long id, String number) {
41             this.id = id;
42             this.number = number;
43         }
44     };
45 
init(Context context)46     static void init(Context context) {
47         sInstance = new RecipientIdCache(context);
48         new Thread(new Runnable() {
49             public void run() {
50                 fill();
51             }
52         }).start();
53     }
54 
RecipientIdCache(Context context)55     RecipientIdCache(Context context) {
56         mCache = new HashMap<Long, String>();
57         mContext = context;
58     }
59 
fill()60     public static void fill() {
61         if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
62             LogTag.debug("[RecipientIdCache] fill: begin");
63         }
64 
65         Context context = sInstance.mContext;
66         Cursor c = SqliteWrapper.query(context, context.getContentResolver(),
67                 sAllCanonical, null, null, null, null);
68         if (c == null) {
69             Log.w(TAG, "null Cursor in fill()");
70             return;
71         }
72 
73         try {
74             synchronized (sInstance) {
75                 // Technically we don't have to clear this because the stupid
76                 // canonical_addresses table is never GC'ed.
77                 sInstance.mCache.clear();
78                 while (c.moveToNext()) {
79                     // TODO: don't hardcode the column indices
80                     long id = c.getLong(0);
81                     String number = c.getString(1);
82                     sInstance.mCache.put(id, number);
83                 }
84             }
85         } finally {
86             c.close();
87         }
88 
89         if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
90             LogTag.debug("[RecipientIdCache] fill: finished");
91             dump();
92         }
93     }
94 
getAddresses(String spaceSepIds)95     public static List<Entry> getAddresses(String spaceSepIds) {
96         synchronized (sInstance) {
97             List<Entry> numbers = new ArrayList<Entry>();
98             String[] ids = spaceSepIds.split(" ");
99             for (String id : ids) {
100                 long longId;
101 
102                 try {
103                     longId = Long.parseLong(id);
104                 } catch (NumberFormatException ex) {
105                     // skip this id
106                     continue;
107                 }
108 
109                 String number = sInstance.mCache.get(longId);
110 
111                 if (number == null) {
112                     Log.w(TAG, "RecipientId " + longId + " not in cache!");
113                     if (Log.isLoggable(LogTag.THREAD_CACHE, Log.VERBOSE)) {
114                         dump();
115                     }
116 
117                     fill();
118                     number = sInstance.mCache.get(longId);
119                 }
120 
121                 if (TextUtils.isEmpty(number)) {
122                     Log.w(TAG, "RecipientId " + longId + " has empty number!");
123                 } else {
124                     numbers.add(new Entry(longId, number));
125                 }
126             }
127             return numbers;
128         }
129     }
130 
updateNumbers(long threadId, ContactList contacts)131     public static void updateNumbers(long threadId, ContactList contacts) {
132         long recipientId = 0;
133 
134         for (Contact contact : contacts) {
135             if (contact.isNumberModified()) {
136                 contact.setIsNumberModified(false);
137             } else {
138                 // if the contact's number wasn't modified, don't bother.
139                 continue;
140             }
141 
142             recipientId = contact.getRecipientId();
143             if (recipientId == 0) {
144                 continue;
145             }
146 
147             String number1 = contact.getNumber();
148             String number2 = sInstance.mCache.get(recipientId);
149 
150             if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
151                 Log.d(TAG, "[RecipientIdCache] updateNumbers: comparing " + number1 +
152                         " with " + number2);
153             }
154 
155             // if the numbers don't match, let's update the RecipientIdCache's number
156             // with the new number in the contact.
157             if (!number1.equalsIgnoreCase(number2)) {
158                 sInstance.mCache.put(recipientId, number1);
159                 sInstance.updateCanonicalAddressInDb(recipientId, number1);
160             }
161         }
162     }
163 
updateCanonicalAddressInDb(long id, String number)164     private void updateCanonicalAddressInDb(long id, String number) {
165         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
166             Log.d(TAG, "[RecipientIdCache] updateCanonicalAddressInDb: id=" + id +
167                     ", number=" + number);
168         }
169 
170         final ContentValues values = new ContentValues();
171         values.put(Telephony.CanonicalAddressesColumns.ADDRESS, number);
172 
173         final StringBuilder buf = new StringBuilder(Telephony.CanonicalAddressesColumns._ID);
174         buf.append('=').append(id);
175 
176         final Uri uri = ContentUris.withAppendedId(sSingleCanonicalAddressUri, id);
177         final ContentResolver cr = mContext.getContentResolver();
178 
179         // We're running on the UI thread so just fire & forget, hope for the best.
180         // (We were ignoring the return value anyway...)
181         new Thread("updateCanonicalAddressInDb") {
182             public void run() {
183                 cr.update(uri, values, buf.toString(), null);
184             }
185         }.start();
186     }
187 
dump()188     public static void dump() {
189         // Only dump user private data if we're in special debug mode
190         synchronized (sInstance) {
191             Log.d(TAG, "*** Recipient ID cache dump ***");
192             for (Long id : sInstance.mCache.keySet()) {
193                 Log.d(TAG, id + ": " + sInstance.mCache.get(id));
194             }
195         }
196     }
197 }
198