1 /*
2 * Copyright (C) 2015 Samsung System LSI
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 
16 package com.android.bluetooth.map;
17 
18 import android.annotation.TargetApi;
19 import android.content.ContentResolver;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.provider.ContactsContract;
23 import android.provider.ContactsContract.Contacts;
24 import android.provider.ContactsContract.PhoneLookup;
25 import android.provider.Telephony.CanonicalAddressesColumns;
26 import android.provider.Telephony.MmsSms;
27 import android.util.Log;
28 
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.regex.Pattern;
32 
33 /**
34  * Use these functions when extracting data for listings. It caches frequently used data to
35  * speed up building large listings - e.g. before applying filtering.
36  */
37 @TargetApi(19)
38 public class SmsMmsContacts {
39 
40     private static final String TAG = "SmsMmsContacts";
41 
42     private HashMap<Long, String> mPhoneNumbers = null;
43     private final HashMap<String, MapContact> mNames = new HashMap<String, MapContact>(10);
44 
45     private static final Uri ADDRESS_URI =
46             MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
47 
48     private static final String[] ADDRESS_PROJECTION = {
49             CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS
50     };
51     private static final int COL_ADDR_ID =
52             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
53     private static final int COL_ADDR_ADDR =
54             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
55 
56     private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
57     private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
58     private static final int COL_CONTACT_ID =
59             Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
60     private static final int COL_CONTACT_NAME =
61             Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME);
62 
63     /**
64      * Get a contacts phone number based on the canonical addresses id of the contact.
65      * (The ID listed in the Threads table.)
66      * @param resolver the ContantResolver to be used.
67      * @param id the id of the contact, as listed in the Threads table
68      * @return the phone number of the contact - or null if id does not exist.
69      */
getPhoneNumber(ContentResolver resolver, long id)70     public String getPhoneNumber(ContentResolver resolver, long id) {
71         String number;
72         if (mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
73             return number;
74         }
75         fillPhoneCache(resolver);
76         return mPhoneNumbers.get(id);
77     }
78 
getPhoneNumberUncached(ContentResolver resolver, long id)79     public static String getPhoneNumberUncached(ContentResolver resolver, long id) {
80         String where = CanonicalAddressesColumns._ID + " = " + id;
81         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null);
82         try {
83             if (c != null) {
84                 if (c.moveToPosition(0)) {
85                     return c.getString(COL_ADDR_ADDR);
86                 }
87             }
88             Log.e(TAG, "query failed");
89         } finally {
90             if (c != null) {
91                 c.close();
92             }
93         }
94         return null;
95     }
96 
97     /**
98      * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
99      */
clearCache()100     public void clearCache() {
101         if (mPhoneNumbers != null) {
102             mPhoneNumbers.clear();
103         }
104         if (mNames != null) {
105             mNames.clear();
106         }
107     }
108 
109     /**
110      * Refreshes the cache, by clearing all cached values and fill the cache with the result of
111      * a new query.
112      * @param resolver the ContantResolver to be used.
113      */
fillPhoneCache(ContentResolver resolver)114     private void fillPhoneCache(ContentResolver resolver) {
115         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null);
116         if (mPhoneNumbers == null) {
117             int size = 0;
118             if (c != null) {
119                 size = c.getCount();
120             }
121             mPhoneNumbers = new HashMap<Long, String>(size);
122         } else {
123             mPhoneNumbers.clear();
124         }
125         try {
126             if (c != null) {
127                 long id;
128                 String addr;
129                 c.moveToPosition(-1);
130                 while (c.moveToNext()) {
131                     id = c.getLong(COL_ADDR_ID);
132                     addr = c.getString(COL_ADDR_ADDR);
133                     mPhoneNumbers.put(id, addr);
134                 }
135             } else {
136                 Log.e(TAG, "query failed");
137             }
138         } finally {
139             if (c != null) {
140                 c.close();
141             }
142         }
143     }
144 
getContactNameFromPhone(String phone, ContentResolver resolver)145     public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
146         return getContactNameFromPhone(phone, resolver, null);
147     }
148 
149     /**
150      * Lookup a contacts name in the Android Contacts database.
151      * @param phone the phone number of the contact
152      * @param resolver the ContentResolver to use.
153      * @return the name of the contact or null, if no contact was found.
154      */
getContactNameFromPhone(String phone, ContentResolver resolver, String contactNameFilter)155     public MapContact getContactNameFromPhone(String phone, ContentResolver resolver,
156             String contactNameFilter) {
157         MapContact contact = mNames.get(phone);
158 
159         if (contact != null) {
160             if (contact.getId() < 0) {
161                 return null;
162             }
163             if (contactNameFilter == null) {
164                 return contact;
165             }
166             // Validate filter
167             String searchString = contactNameFilter.replace("*", ".*");
168             searchString = ".*" + searchString + ".*";
169             Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
170             if (p.matcher(contact.getName()).find()) {
171                 return contact;
172             }
173             return null;
174         }
175 
176         // TODO: Should we change to extract both formatted name, and display name?
177 
178         Uri uri =
179                 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone));
180         String selection = CONTACT_SEL_VISIBLE;
181         String[] selectionArgs = null;
182         if (contactNameFilter != null) {
183             selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
184             selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
185         }
186 
187         Cursor c = resolver.query(uri, CONTACT_PROJECTION, selection, selectionArgs, null);
188         try {
189             if (c != null && c.getCount() >= 1) {
190                 c.moveToFirst();
191                 long id = c.getLong(COL_CONTACT_ID);
192                 String name = c.getString(COL_CONTACT_NAME);
193                 contact = MapContact.create(id, name);
194                 mNames.put(phone, contact);
195             } else {
196                 contact = MapContact.create(-1, null);
197                 mNames.put(phone, contact);
198                 contact = null;
199             }
200         } finally {
201             if (c != null) {
202                 c.close();
203             }
204         }
205         return contact;
206     }
207 }
208