1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.cluster.sample; 18 19 /** 20 * Utility class to retrieve contact information. 21 */ 22 import android.annotation.Nullable; 23 import android.annotation.WorkerThread; 24 import android.content.ContentResolver; 25 import android.content.ContentUris; 26 import android.content.Context; 27 import android.database.Cursor; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.BitmapFactory.Options; 31 import android.graphics.Rect; 32 import android.net.Uri; 33 import android.provider.ContactsContract; 34 import android.telephony.PhoneNumberUtils; 35 import android.telephony.TelephonyManager; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.util.LruCache; 39 40 import java.io.InputStream; 41 import java.util.HashMap; 42 import java.util.Locale; 43 44 public class TelecomUtils { 45 46 private final static String TAG = DebugUtil.getTag(TelecomUtils.class); 47 48 private static final String[] CONTACT_ID_PROJECTION = new String[] { 49 ContactsContract.PhoneLookup.DISPLAY_NAME, 50 ContactsContract.PhoneLookup.TYPE, 51 ContactsContract.PhoneLookup.LABEL, 52 ContactsContract.PhoneLookup._ID 53 }; 54 55 private static LruCache<String, Bitmap> sContactPhotoNumberCache; 56 private static LruCache<Long, Bitmap> sContactPhotoIdCache; 57 private static HashMap<String, Integer> sContactIdCache; 58 private static HashMap<String, String> sFormattedNumberCache; 59 private static HashMap<String, String> sDisplayNameCache; 60 private static HashMap<String, String> sContactNameCache; 61 private static String sVoicemailNumber; 62 private static TelephonyManager sTelephonyManager; 63 64 /** 65 * Fetch contact photo by number from local cache. 66 * 67 * @param number 68 * @return Contact photo if it's in the cache, otherwise null. 69 */ 70 @Nullable 71 public static Bitmap getCachedContactPhotoFromNumber(String number) { 72 if (number == null) { 73 return null; 74 } 75 76 if (sContactPhotoNumberCache == null) { 77 sContactPhotoNumberCache = new LruCache<String, Bitmap>(4194304 /** 4 mb **/) { 78 @Override 79 protected int sizeOf(String key, Bitmap value) { 80 return value.getByteCount(); 81 } 82 }; 83 } else if (sContactPhotoNumberCache.get(number) != null) { 84 return sContactPhotoNumberCache.get(number); 85 } 86 87 return null; 88 } 89 90 @WorkerThread 91 public static Bitmap getContactPhotoFromNumber(ContentResolver contentResolver, String number) { 92 if (number == null) { 93 return null; 94 } 95 96 Bitmap photo = getCachedContactPhotoFromNumber(number); 97 if (photo != null) { 98 return photo; 99 } 100 101 int id = getContactIdFromNumber(contentResolver, number); 102 if (id == 0) { 103 return null; 104 } 105 photo = getContactPhotoFromId(contentResolver, id); 106 if (photo != null) { 107 sContactPhotoNumberCache.put(number, photo); 108 } 109 return photo; 110 } 111 112 /** 113 * Return the contact id for the given contact id 114 * @param id the contact id to get the photo for 115 * @return the contact photo if it is found, null otherwise. 116 */ 117 public static Bitmap getContactPhotoFromId(ContentResolver contentResolver, long id) { 118 if (sContactPhotoIdCache == null) { 119 sContactPhotoIdCache = new LruCache<Long, Bitmap>(4194304 /** 4 mb **/) { 120 @Override 121 protected int sizeOf(Long key, Bitmap value) { 122 return value.getByteCount(); 123 } 124 }; 125 } else if (sContactPhotoIdCache.get(id) != null) { 126 return sContactPhotoIdCache.get(id); 127 } 128 129 Uri photoUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); 130 InputStream photoDataStream = ContactsContract.Contacts.openContactPhotoInputStream( 131 contentResolver, photoUri, true); 132 133 Options options = new Options(); 134 options.inPreferQualityOverSpeed = true; 135 // Scaling will be handled by later. We shouldn't scale multiple times to avoid 136 // quality lost due to multiple potential scaling up and down. 137 options.inScaled = false; 138 139 Rect nullPadding = null; 140 Bitmap photo = BitmapFactory.decodeStream(photoDataStream, nullPadding, options); 141 if (photo != null) { 142 photo.setDensity(Bitmap.DENSITY_NONE); 143 sContactPhotoIdCache.put(id, photo); 144 } 145 return photo; 146 } 147 148 /** 149 * Return the contact id for the given phone number. 150 * @param number Caller phone number 151 * @return the contact id if it is found, 0 otherwise. 152 */ 153 public static int getContactIdFromNumber(ContentResolver cr, String number) { 154 if (number == null || number.isEmpty()) { 155 return 0; 156 } 157 if (sContactIdCache == null) { 158 sContactIdCache = new HashMap<>(); 159 } else if (sContactIdCache.containsKey(number)) { 160 return sContactIdCache.get(number); 161 } 162 163 164 Uri uri = Uri.withAppendedPath( 165 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 166 Uri.encode(number)); 167 Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null); 168 169 try { 170 if (cursor != null && cursor.moveToFirst()) { 171 int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID)); 172 sContactIdCache.put(number, id); 173 return id; 174 } 175 } 176 finally { 177 if (cursor != null) { 178 cursor.close(); 179 } 180 } 181 return 0; 182 } 183 184 public static String getVoicemailNumber(Context context) { 185 if (sVoicemailNumber == null) { 186 sVoicemailNumber = getTelephonyManager(context).getVoiceMailNumber(); 187 } 188 return sVoicemailNumber; 189 } 190 191 public static TelephonyManager getTelephonyManager(Context context) { 192 if (sTelephonyManager == null) { 193 sTelephonyManager = 194 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 195 } 196 return sTelephonyManager; 197 } 198 199 public static String getFormattedNumber(Context context, String number) { 200 Log.d(TAG, "getFormattedNumber: " + number); 201 if (number == null) { 202 return ""; 203 } 204 205 if (sFormattedNumberCache == null) { 206 sFormattedNumberCache = new HashMap<>(); 207 } else { 208 if (sFormattedNumberCache.containsKey(number)) { 209 return sFormattedNumberCache.get(number); 210 } 211 } 212 213 String countryIso = getTelephonyManager(context).getSimCountryIso().toUpperCase(Locale.US); 214 if (countryIso.length() != 2) { 215 countryIso = Locale.getDefault().getCountry(); 216 if (countryIso == null || countryIso.length() != 2) { 217 countryIso = "US"; 218 } 219 } 220 Log.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: " 221 + number + ", country: " + countryIso); 222 String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso); 223 String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso); 224 formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber; 225 sFormattedNumberCache.put(number, formattedNumber); 226 Log.d(TAG, "getFormattedNumber, result: " + formattedNumber); 227 return formattedNumber; 228 } 229 230 public static String getDisplayName(Context context, String number) { 231 return getDisplayName(context, number, null); 232 } 233 234 private static String getDisplayName(Context context, String number, Uri gatewayOriginalAddress) { 235 Log.d(TAG, "getDisplayName: " + number 236 + ", gatewayOriginalAddress: " + gatewayOriginalAddress); 237 if (sDisplayNameCache == null) { 238 sDisplayNameCache = new HashMap<>(); 239 } else { 240 if (sDisplayNameCache.containsKey(number)) { 241 return sDisplayNameCache.get(number); 242 } 243 } 244 245 if (TextUtils.isEmpty(number)) { 246 return context.getString(R.string.unknown); 247 } 248 ContentResolver cr = context.getContentResolver(); 249 String name; 250 if (number.equals(getVoicemailNumber(context))) { 251 name = context.getResources().getString(R.string.voicemail); 252 } else { 253 name = getContactNameFromNumber(cr, number); 254 } 255 256 if (name == null) { 257 name = getFormattedNumber(context, number); 258 } 259 if (name == null && gatewayOriginalAddress != null) { 260 name = gatewayOriginalAddress.getSchemeSpecificPart(); 261 } 262 if (name == null) { 263 name = context.getString(R.string.unknown); 264 } 265 sDisplayNameCache.put(number, name); 266 return name; 267 } 268 269 private static String getContactNameFromNumber(ContentResolver cr, String number) { 270 if (sContactNameCache == null) { 271 sContactNameCache = new HashMap<>(); 272 } else if (sContactNameCache.containsKey(number)) { 273 return sContactNameCache.get(number); 274 } 275 276 Uri uri = Uri.withAppendedPath( 277 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); 278 279 Cursor cursor = null; 280 String name = null; 281 try { 282 cursor = cr.query(uri, 283 new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); 284 if (cursor != null && cursor.moveToFirst()) { 285 name = cursor.getString(0); 286 sContactNameCache.put(number, name); 287 } 288 } finally { 289 if (cursor != null) { 290 cursor.close(); 291 } 292 } 293 return name; 294 } 295 } 296