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