1 /* 2 * Copyright (C) 2015 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 package com.android.car.dialer.telecom; 17 18 import android.content.ContentResolver; 19 import android.content.ContentUris; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.database.Cursor; 23 import android.graphics.Bitmap; 24 import android.graphics.BitmapFactory; 25 import android.graphics.BitmapFactory.Options; 26 import android.graphics.Rect; 27 import android.net.Uri; 28 import android.provider.ContactsContract; 29 import android.provider.ContactsContract.CommonDataKinds.Phone; 30 import android.provider.ContactsContract.PhoneLookup; 31 import android.provider.Settings; 32 import android.support.annotation.Nullable; 33 import android.support.annotation.WorkerThread; 34 import android.telecom.Call; 35 import android.telephony.PhoneNumberUtils; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 import android.text.format.DateUtils; 39 import android.util.Log; 40 import android.widget.ImageView; 41 import com.android.car.apps.common.CircleBitmapDrawable; 42 import com.android.car.apps.common.LetterTileDrawable; 43 import com.android.car.dialer.R; 44 45 import java.io.InputStream; 46 import java.util.Locale; 47 48 public class TelecomUtils { 49 private final static String TAG = "Em.TelecomUtils"; 50 51 private static final String[] CONTACT_ID_PROJECTION = new String[] { 52 ContactsContract.PhoneLookup.DISPLAY_NAME, 53 ContactsContract.PhoneLookup.TYPE, 54 ContactsContract.PhoneLookup.LABEL, 55 ContactsContract.PhoneLookup._ID 56 }; 57 58 private static String sVoicemailNumber; 59 private static TelephonyManager sTelephonyManager; 60 61 @WorkerThread getContactPhotoFromNumber(ContentResolver contentResolver, String number)62 public static Bitmap getContactPhotoFromNumber(ContentResolver contentResolver, String number) { 63 if (number == null) { 64 return null; 65 } 66 67 int id = getContactIdFromNumber(contentResolver, number); 68 if (id == 0) { 69 return null; 70 } 71 return getContactPhotoFromId(contentResolver, id); 72 } 73 74 /** 75 * Return the contact id for the given contact id 76 * @param id the contact id to get the photo for 77 * @return the contact photo if it is found, null otherwise. 78 */ getContactPhotoFromId(ContentResolver contentResolver, long id)79 public static Bitmap getContactPhotoFromId(ContentResolver contentResolver, long id) { 80 Uri photoUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); 81 InputStream photoDataStream = ContactsContract.Contacts.openContactPhotoInputStream( 82 contentResolver, photoUri, true); 83 84 Options options = new Options(); 85 options.inPreferQualityOverSpeed = true; 86 // Scaling will be handled by later. We shouldn't scale multiple times to avoid 87 // quality lost due to multiple potential scaling up and down. 88 options.inScaled = false; 89 90 Rect nullPadding = null; 91 Bitmap photo = BitmapFactory.decodeStream(photoDataStream, nullPadding, options); 92 if (photo != null) { 93 photo.setDensity(Bitmap.DENSITY_NONE); 94 } 95 return photo; 96 } 97 98 /** 99 * Return the contact id for the given phone number. 100 * @param number Caller phone number 101 * @return the contact id if it is found, 0 otherwise. 102 */ getContactIdFromNumber(ContentResolver cr, String number)103 public static int getContactIdFromNumber(ContentResolver cr, String number) { 104 if (number == null || number.isEmpty()) { 105 return 0; 106 } 107 108 Uri uri = Uri.withAppendedPath( 109 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, 110 Uri.encode(number)); 111 Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null); 112 113 try { 114 if (cursor != null && cursor.moveToFirst()) { 115 int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID)); 116 return id; 117 } 118 } 119 finally { 120 if (cursor != null) { 121 cursor.close(); 122 } 123 } 124 return 0; 125 } 126 127 /** 128 * Return the label for the given phone number. 129 * @param number Caller phone number 130 * @return the label if it is found, 0 otherwise. 131 */ getTypeFromNumber(Context context, String number)132 public static CharSequence getTypeFromNumber(Context context, String number) { 133 if (Log.isLoggable(TAG, Log.DEBUG)) { 134 Log.d(TAG, "getTypeFromNumber, number: " + number); 135 } 136 String defaultLabel = ""; 137 if (number == null || number.isEmpty()) { 138 return defaultLabel; 139 } 140 141 ContentResolver cr = context.getContentResolver(); 142 Resources res = context.getResources(); 143 Uri uri = Uri.withAppendedPath( 144 PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); 145 Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null); 146 147 try { 148 if (cursor != null && cursor.moveToFirst()) { 149 int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE); 150 int type = cursor.getInt(typeColumn); 151 int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL); 152 String label = cursor.getString(labelColumn); 153 CharSequence typeLabel = 154 Phone.getTypeLabel(res, type, label); 155 return typeLabel; 156 } 157 } 158 finally { 159 if (cursor != null) { 160 cursor.close(); 161 } 162 } 163 return defaultLabel; 164 } 165 getVoicemailNumber(Context context)166 public static String getVoicemailNumber(Context context) { 167 if (sVoicemailNumber == null) { 168 sVoicemailNumber = getTelephonyManager(context).getVoiceMailNumber(); 169 } 170 return sVoicemailNumber; 171 } 172 getTelephonyManager(Context context)173 public static TelephonyManager getTelephonyManager(Context context) { 174 if (sTelephonyManager == null) { 175 sTelephonyManager = 176 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 177 } 178 return sTelephonyManager; 179 } 180 getFormattedNumber(Context context, String number)181 public static String getFormattedNumber(Context context, String number) { 182 if (Log.isLoggable(TAG, Log.DEBUG)) { 183 Log.d(TAG, "getFormattedNumber: " + number); 184 } 185 if (number == null) { 186 return ""; 187 } 188 189 String countryIso = getTelephonyManager(context).getSimCountryIso().toUpperCase(Locale.US); 190 if (countryIso.length() != 2) { 191 countryIso = Locale.getDefault().getCountry(); 192 if (countryIso == null || countryIso.length() != 2) { 193 countryIso = "US"; 194 } 195 } 196 if (Log.isLoggable(TAG, Log.DEBUG)) { 197 Log.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: " 198 + number + ", country: " + countryIso); 199 } 200 String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso); 201 String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso); 202 formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber; 203 if (Log.isLoggable(TAG, Log.DEBUG)) { 204 Log.d(TAG, "getFormattedNumber, result: " + formattedNumber); 205 } 206 return formattedNumber; 207 } 208 getDisplayName(Context context, UiCall call)209 public static String getDisplayName(Context context, UiCall call) { 210 // A call might get created before its children are added. In that case, the display name 211 // would go from "Unknown" to "Conference call" therefore we don't want to cache it. 212 if (call.hasChildren()) { 213 return context.getString(R.string.conference_call); 214 } 215 216 return getDisplayName(context, call.getNumber(), call.getGatewayInfoOriginalAddress()); 217 } 218 getDisplayName(Context context, String number)219 public static String getDisplayName(Context context, String number) { 220 return getDisplayName(context, number, null); 221 } 222 getDisplayName(Context context, String number, Uri gatewayOriginalAddress)223 private static String getDisplayName(Context context, String number, Uri gatewayOriginalAddress) { 224 if (Log.isLoggable(TAG, Log.DEBUG)) { 225 Log.d(TAG, "getDisplayName: " + number 226 + ", gatewayOriginalAddress: " + gatewayOriginalAddress); 227 } 228 229 if (TextUtils.isEmpty(number)) { 230 return context.getString(R.string.unknown); 231 } 232 ContentResolver cr = context.getContentResolver(); 233 String name; 234 if (number.equals(getVoicemailNumber(context))) { 235 name = context.getResources().getString(R.string.voicemail); 236 } else { 237 name = getContactNameFromNumber(cr, number); 238 } 239 240 if (name == null) { 241 name = getFormattedNumber(context, number); 242 } 243 if (name == null && gatewayOriginalAddress != null) { 244 name = gatewayOriginalAddress.getSchemeSpecificPart(); 245 } 246 if (name == null) { 247 name = context.getString(R.string.unknown); 248 } 249 return name; 250 } 251 getContactNameFromNumber(ContentResolver cr, String number)252 private static String getContactNameFromNumber(ContentResolver cr, String number) { 253 Uri uri = Uri.withAppendedPath( 254 ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); 255 256 Cursor cursor = null; 257 String name = null; 258 try { 259 cursor = cr.query(uri, 260 new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); 261 if (cursor != null && cursor.moveToFirst()) { 262 name = cursor.getString(0); 263 } 264 } finally { 265 if (cursor != null) { 266 cursor.close(); 267 } 268 } 269 return name; 270 } 271 272 /** 273 * @return A formatted string that has information about the phone call 274 * Possible strings: 275 * "Mobile · Dialing" 276 * "Mobile · 1:05" 277 * "Bluetooth disconnected" 278 */ getCallInfoText(Context context, UiCall call, CharSequence label)279 public static String getCallInfoText(Context context, UiCall call, CharSequence label) { 280 String text; 281 if (call.getState() == Call.STATE_ACTIVE) { 282 long duration = System.currentTimeMillis() - call.getConnectTimeMillis(); 283 String durationString = DateUtils.formatElapsedTime(duration / 1000); 284 if (!TextUtils.isEmpty(durationString) && !TextUtils.isEmpty(label)) { 285 text = context.getString(R.string.phone_label_with_info, label, durationString); 286 } else if (!TextUtils.isEmpty(durationString)) { 287 text = durationString; 288 } else if (!TextUtils.isEmpty(label)) { 289 text = (String) label; 290 } else { 291 text = ""; 292 } 293 } else { 294 String state = callStateToUiString(context, call.getState()); 295 if (!TextUtils.isEmpty(label)) { 296 text = context.getString(R.string.phone_label_with_info, label, state); 297 } else { 298 text = state; 299 } 300 } 301 return text; 302 } 303 304 /** 305 * @return A string representation of the call state that can be presented to a user. 306 */ callStateToUiString(Context context, int state)307 public static String callStateToUiString(Context context, int state) { 308 Resources res = context.getResources(); 309 switch(state) { 310 case Call.STATE_ACTIVE: 311 return res.getString(R.string.call_state_call_active); 312 case Call.STATE_HOLDING: 313 return res.getString(R.string.call_state_hold); 314 case Call.STATE_NEW: 315 case Call.STATE_CONNECTING: 316 return res.getString(R.string.call_state_connecting); 317 case Call.STATE_SELECT_PHONE_ACCOUNT: 318 case Call.STATE_DIALING: 319 return res.getString(R.string.call_state_dialing); 320 case Call.STATE_DISCONNECTED: 321 return res.getString(R.string.call_state_call_ended); 322 case Call.STATE_RINGING: 323 return res.getString(R.string.call_state_call_ringing); 324 case Call.STATE_DISCONNECTING: 325 return res.getString(R.string.call_state_call_ending); 326 default: 327 throw new IllegalStateException("Unknown Call State: " + state); 328 } 329 } 330 isNetworkAvailable(Context context)331 public static boolean isNetworkAvailable(Context context) { 332 TelephonyManager tm = 333 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 334 return tm.getNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN && 335 tm.getSimState() == TelephonyManager.SIM_STATE_READY; 336 } 337 isAirplaneModeOn(Context context)338 public static boolean isAirplaneModeOn(Context context) { 339 return Settings.System.getInt(context.getContentResolver(), 340 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 341 } 342 343 /** 344 * Sets a Contact bitmap on the provided image taking into account fail cases. 345 * It will attempt to load a Bitmap from the Contacts store, otherwise it will paint 346 * a the first letter of the contact name. 347 * 348 * @param number A key to have a consisten color per phone number. 349 * @return A worker task if a new one was needed to load the bitmap. 350 */ setContactBitmapAsync(Context context, final ImageView icon, final @Nullable String name, final String number)351 @Nullable public static ContactBitmapWorker setContactBitmapAsync(Context context, 352 final ImageView icon, final @Nullable String name, final String number) { 353 return ContactBitmapWorker.loadBitmap(context.getContentResolver(), icon, number, 354 new ContactBitmapWorker.BitmapWorkerListener() { 355 @Override 356 public void onBitmapLoaded(@Nullable Bitmap bitmap) { 357 Resources r = icon.getResources(); 358 if (bitmap != null) { 359 icon.setScaleType(ImageView.ScaleType.CENTER_CROP); 360 icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap)); 361 } else { 362 icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 363 LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); 364 letterTileDrawable.setContactDetails(name, number); 365 letterTileDrawable.setIsCircular(true); 366 icon.setImageDrawable(letterTileDrawable); 367 } 368 } 369 }); 370 } 371 372 } 373