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