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 java.util.Arrays;
19 import java.util.HashMap;
20 import java.util.regex.Pattern;
21 
22 import android.annotation.TargetApi;
23 import android.content.ContentResolver;
24 import android.database.Cursor;
25 import android.net.Uri;
26 import android.provider.ContactsContract;
27 import android.provider.ContactsContract.Contacts;
28 import android.provider.ContactsContract.PhoneLookup;
29 import android.provider.Telephony.CanonicalAddressesColumns;
30 import android.provider.Telephony.MmsSms;
31 import android.util.Log;
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 = { CanonicalAddressesColumns._ID,
49                     CanonicalAddressesColumns.ADDRESS };
50     private static final int COL_ADDR_ID =
51             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
52     private static final int COL_ADDR_ADDR =
53             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
54 
55     private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
56     private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
57     private static final int COL_CONTACT_ID =
58             Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
59     private static final int COL_CONTACT_NAME =
60             Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts.DISPLAY_NAME);
61 
62     /**
63      * Get a contacts phone number based on the canonical addresses id of the contact.
64      * (The ID listed in the Threads table.)
65      * @param resolver the ContantResolver to be used.
66      * @param id the id of the contact, as listed in the Threads table
67      * @return the phone number of the contact - or null if id does not exist.
68      */
getPhoneNumber(ContentResolver resolver, long id)69     public String getPhoneNumber(ContentResolver resolver, long id) {
70         String number;
71         if(mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
72             return number;
73         }
74         fillPhoneCache(resolver);
75         return mPhoneNumbers.get(id);
76     }
77 
getPhoneNumberUncached(ContentResolver resolver, long id)78     public static String getPhoneNumberUncached(ContentResolver resolver, long id) {
79         String where = CanonicalAddressesColumns._ID + " = " + id;
80         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null);
81         try {
82             if (c != null) {
83                 if(c.moveToPosition(0)) {
84                     return c.getString(COL_ADDR_ADDR);
85                 }
86             }
87             Log.e(TAG, "query failed");
88         } finally {
89             if(c != null) c.close();
90         }
91         return null;
92     }
93 
94     /**
95      * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
96      */
clearCache()97     public void clearCache() {
98         if(mPhoneNumbers != null) mPhoneNumbers.clear();
99         if(mNames != null) mNames.clear();
100     }
101 
102     /**
103      * Refreshes the cache, by clearing all cached values and fill the cache with the result of
104      * a new query.
105      * @param resolver the ContantResolver to be used.
106      */
fillPhoneCache(ContentResolver resolver)107     private void fillPhoneCache(ContentResolver resolver){
108         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null);
109         if(mPhoneNumbers == null) {
110             int size = 0;
111             if(c != null)
112             {
113                 size = c.getCount();
114             }
115             mPhoneNumbers = new HashMap<Long, String>(size);
116         } else {
117             mPhoneNumbers.clear();
118         }
119         try {
120             if (c != null) {
121                 long id;
122                 String addr;
123                 c.moveToPosition(-1);
124                 while (c.moveToNext()) {
125                     id = c.getLong(COL_ADDR_ID);
126                     addr = c.getString(COL_ADDR_ADDR);
127                     mPhoneNumbers.put(id, addr);
128                 }
129             } else {
130                 Log.e(TAG, "query failed");
131             }
132         } finally {
133             if(c != null) c.close();
134         }
135     }
136 
getContactNameFromPhone(String phone, ContentResolver resolver)137     public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
138         return getContactNameFromPhone(phone, resolver, null);
139     }
140     /**
141      * Lookup a contacts name in the Android Contacts database.
142      * @param phone the phone number of the contact
143      * @param resolver the ContentResolver to use.
144      * @return the name of the contact or null, if no contact was found.
145      */
getContactNameFromPhone(String phone, ContentResolver resolver, String contactNameFilter)146     public MapContact getContactNameFromPhone(String phone, ContentResolver resolver,
147             String contactNameFilter) {
148         MapContact contact = mNames.get(phone);
149 
150         if(contact != null){
151             if(contact.getId() < 0) {
152                 return null;
153             }
154             if(contactNameFilter == null) {
155                 return contact;
156             }
157             // Validate filter
158             String searchString = contactNameFilter.replace("*", ".*");
159             searchString = ".*" + searchString + ".*";
160             Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
161             if(p.matcher(contact.getName()).find()) {
162                 return contact;
163             }
164             return null;
165         }
166 
167         // TODO: Should we change to extract both formatted name, and display name?
168 
169         Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,Uri.encode(phone));
170         String selection = CONTACT_SEL_VISIBLE;
171         String[] selectionArgs = null;
172         if(contactNameFilter != null) {
173             selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
174             selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
175         }
176 
177         Cursor c = resolver.query(uri, CONTACT_PROJECTION, selection, selectionArgs, null);
178         try {
179             if (c != null && c.getCount() >= 1) {
180                 c.moveToFirst();
181                 long id = c.getLong(COL_CONTACT_ID);
182                 String name = c.getString(COL_CONTACT_NAME);
183                 contact = MapContact.create(id, name);
184                 mNames.put(phone, contact);
185             } else {
186                 contact = MapContact.create(-1, null);
187                 mNames.put(phone, contact);
188                 contact = null;
189             }
190         } finally {
191             if (c != null) c.close();
192         }
193         return contact;
194     }
195 }
196