| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.dialer.telecom; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.BitmapFactory.Options; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.CommonDataKinds.Phone; |
| import android.provider.ContactsContract.PhoneLookup; |
| import android.provider.Settings; |
| import android.support.annotation.Nullable; |
| import android.support.annotation.WorkerThread; |
| import android.support.car.ui.CircleBitmapDrawable; |
| import android.telecom.Call; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.text.format.DateUtils; |
| import android.util.Log; |
| import android.widget.ImageView; |
| import com.android.car.apps.common.LetterTileDrawable; |
| import com.android.car.dialer.R; |
| |
| import java.io.InputStream; |
| import java.util.Locale; |
| |
| public class TelecomUtils { |
| private final static String TAG = "Em.TelecomUtils"; |
| |
| private static final String[] CONTACT_ID_PROJECTION = new String[] { |
| ContactsContract.PhoneLookup.DISPLAY_NAME, |
| ContactsContract.PhoneLookup.TYPE, |
| ContactsContract.PhoneLookup.LABEL, |
| ContactsContract.PhoneLookup._ID |
| }; |
| |
| private static String sVoicemailNumber; |
| private static TelephonyManager sTelephonyManager; |
| |
| @WorkerThread |
| public static Bitmap getContactPhotoFromNumber(ContentResolver contentResolver, String number) { |
| if (number == null) { |
| return null; |
| } |
| |
| int id = getContactIdFromNumber(contentResolver, number); |
| if (id == 0) { |
| return null; |
| } |
| return getContactPhotoFromId(contentResolver, id); |
| } |
| |
| /** |
| * Return the contact id for the given contact id |
| * @param id the contact id to get the photo for |
| * @return the contact photo if it is found, null otherwise. |
| */ |
| public static Bitmap getContactPhotoFromId(ContentResolver contentResolver, long id) { |
| Uri photoUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); |
| InputStream photoDataStream = ContactsContract.Contacts.openContactPhotoInputStream( |
| contentResolver, photoUri, true); |
| |
| Options options = new Options(); |
| options.inPreferQualityOverSpeed = true; |
| // Scaling will be handled by later. We shouldn't scale multiple times to avoid |
| // quality lost due to multiple potential scaling up and down. |
| options.inScaled = false; |
| |
| Rect nullPadding = null; |
| Bitmap photo = BitmapFactory.decodeStream(photoDataStream, nullPadding, options); |
| if (photo != null) { |
| photo.setDensity(Bitmap.DENSITY_NONE); |
| } |
| return photo; |
| } |
| |
| /** |
| * Return the contact id for the given phone number. |
| * @param number Caller phone number |
| * @return the contact id if it is found, 0 otherwise. |
| */ |
| public static int getContactIdFromNumber(ContentResolver cr, String number) { |
| if (number == null || number.isEmpty()) { |
| return 0; |
| } |
| |
| Uri uri = Uri.withAppendedPath( |
| ContactsContract.PhoneLookup.CONTENT_FILTER_URI, |
| Uri.encode(number)); |
| Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null); |
| |
| try { |
| if (cursor != null && cursor.moveToFirst()) { |
| int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.PhoneLookup._ID)); |
| return id; |
| } |
| } |
| finally { |
| if (cursor != null) { |
| cursor.close(); |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * Return the label for the given phone number. |
| * @param number Caller phone number |
| * @return the label if it is found, 0 otherwise. |
| */ |
| public static CharSequence getTypeFromNumber(Context context, String number) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "getTypeFromNumber, number: " + number); |
| } |
| String defaultLabel = ""; |
| if (number == null || number.isEmpty()) { |
| return defaultLabel; |
| } |
| |
| ContentResolver cr = context.getContentResolver(); |
| Resources res = context.getResources(); |
| Uri uri = Uri.withAppendedPath( |
| PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); |
| Cursor cursor = cr.query(uri, CONTACT_ID_PROJECTION, null, null, null); |
| |
| try { |
| if (cursor != null && cursor.moveToFirst()) { |
| int typeColumn = cursor.getColumnIndex(PhoneLookup.TYPE); |
| int type = cursor.getInt(typeColumn); |
| int labelColumn = cursor.getColumnIndex(PhoneLookup.LABEL); |
| String label = cursor.getString(labelColumn); |
| CharSequence typeLabel = |
| Phone.getTypeLabel(res, type, label); |
| return typeLabel; |
| } |
| } |
| finally { |
| if (cursor != null) { |
| cursor.close(); |
| } |
| } |
| return defaultLabel; |
| } |
| |
| public static String getVoicemailNumber(Context context) { |
| if (sVoicemailNumber == null) { |
| sVoicemailNumber = getTelephonyManager(context).getVoiceMailNumber(); |
| } |
| return sVoicemailNumber; |
| } |
| |
| public static TelephonyManager getTelephonyManager(Context context) { |
| if (sTelephonyManager == null) { |
| sTelephonyManager = |
| (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| } |
| return sTelephonyManager; |
| } |
| |
| public static String getFormattedNumber(Context context, String number) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "getFormattedNumber: " + number); |
| } |
| if (number == null) { |
| return ""; |
| } |
| |
| String countryIso = getTelephonyManager(context).getSimCountryIso().toUpperCase(Locale.US); |
| if (countryIso.length() != 2) { |
| countryIso = Locale.getDefault().getCountry(); |
| if (countryIso == null || countryIso.length() != 2) { |
| countryIso = "US"; |
| } |
| } |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "PhoneNumberUtils.formatNumberToE16, number: " |
| + number + ", country: " + countryIso); |
| } |
| String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso); |
| String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso); |
| formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "getFormattedNumber, result: " + formattedNumber); |
| } |
| return formattedNumber; |
| } |
| |
| public static String getDisplayName(Context context, UiCall call) { |
| // A call might get created before its children are added. In that case, the display name |
| // would go from "Unknown" to "Conference call" therefore we don't want to cache it. |
| if (call.hasChildren()) { |
| return context.getString(R.string.conference_call); |
| } |
| |
| return getDisplayName(context, call.getNumber(), call.getGatewayInfoOriginalAddress()); |
| } |
| |
| public static String getDisplayName(Context context, String number) { |
| return getDisplayName(context, number, null); |
| } |
| |
| private static String getDisplayName(Context context, String number, Uri gatewayOriginalAddress) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "getDisplayName: " + number |
| + ", gatewayOriginalAddress: " + gatewayOriginalAddress); |
| } |
| |
| if (TextUtils.isEmpty(number)) { |
| return context.getString(R.string.unknown); |
| } |
| ContentResolver cr = context.getContentResolver(); |
| String name; |
| if (number.equals(getVoicemailNumber(context))) { |
| name = context.getResources().getString(R.string.voicemail); |
| } else { |
| name = getContactNameFromNumber(cr, number); |
| } |
| |
| if (name == null) { |
| name = getFormattedNumber(context, number); |
| } |
| if (name == null && gatewayOriginalAddress != null) { |
| name = gatewayOriginalAddress.getSchemeSpecificPart(); |
| } |
| if (name == null) { |
| name = context.getString(R.string.unknown); |
| } |
| return name; |
| } |
| |
| private static String getContactNameFromNumber(ContentResolver cr, String number) { |
| Uri uri = Uri.withAppendedPath( |
| ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); |
| |
| Cursor cursor = null; |
| String name = null; |
| try { |
| cursor = cr.query(uri, |
| new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); |
| if (cursor != null && cursor.moveToFirst()) { |
| name = cursor.getString(0); |
| } |
| } finally { |
| if (cursor != null) { |
| cursor.close(); |
| } |
| } |
| return name; |
| } |
| |
| /** |
| * @return A formatted string that has information about the phone call |
| * Possible strings: |
| * "Mobile · Dialing" |
| * "Mobile · 1:05" |
| * "Bluetooth disconnected" |
| */ |
| public static String getCallInfoText(Context context, UiCall call, CharSequence label) { |
| String text; |
| if (call.getState() == Call.STATE_ACTIVE) { |
| long duration = System.currentTimeMillis() - call.getConnectTimeMillis(); |
| String durationString = DateUtils.formatElapsedTime(duration / 1000); |
| if (!TextUtils.isEmpty(durationString) && !TextUtils.isEmpty(label)) { |
| text = context.getString(R.string.phone_label_with_info, label, durationString); |
| } else if (!TextUtils.isEmpty(durationString)) { |
| text = durationString; |
| } else if (!TextUtils.isEmpty(label)) { |
| text = (String) label; |
| } else { |
| text = ""; |
| } |
| } else { |
| String state = callStateToUiString(context, call.getState()); |
| if (!TextUtils.isEmpty(label)) { |
| text = context.getString(R.string.phone_label_with_info, label, state); |
| } else { |
| text = state; |
| } |
| } |
| return text; |
| } |
| |
| /** |
| * @return A string representation of the call state that can be presented to a user. |
| */ |
| public static String callStateToUiString(Context context, int state) { |
| Resources res = context.getResources(); |
| switch(state) { |
| case Call.STATE_ACTIVE: |
| return res.getString(R.string.call_state_call_active); |
| case Call.STATE_HOLDING: |
| return res.getString(R.string.call_state_hold); |
| case Call.STATE_NEW: |
| case Call.STATE_CONNECTING: |
| return res.getString(R.string.call_state_connecting); |
| case Call.STATE_SELECT_PHONE_ACCOUNT: |
| case Call.STATE_DIALING: |
| return res.getString(R.string.call_state_dialing); |
| case Call.STATE_DISCONNECTED: |
| return res.getString(R.string.call_state_call_ended); |
| case Call.STATE_RINGING: |
| return res.getString(R.string.call_state_call_ringing); |
| case Call.STATE_DISCONNECTING: |
| return res.getString(R.string.call_state_call_ending); |
| default: |
| throw new IllegalStateException("Unknown Call State: " + state); |
| } |
| } |
| |
| public static boolean isNetworkAvailable(Context context) { |
| TelephonyManager tm = |
| (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| return tm.getNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN && |
| tm.getSimState() == TelephonyManager.SIM_STATE_READY; |
| } |
| |
| public static boolean isAirplaneModeOn(Context context) { |
| return Settings.System.getInt(context.getContentResolver(), |
| Settings.Global.AIRPLANE_MODE_ON, 0) != 0; |
| } |
| |
| /** |
| * Sets a Contact bitmap on the provided image taking into account fail cases. |
| * It will attempt to load a Bitmap from the Contacts store, otherwise it will paint |
| * a the first letter of the contact name. |
| * |
| * @param number A key to have a consisten color per phone number. |
| * @return A worker task if a new one was needed to load the bitmap. |
| */ |
| @Nullable public static ContactBitmapWorker setContactBitmapAsync(Context context, |
| final ImageView icon, final @Nullable String name, final String number) { |
| return ContactBitmapWorker.loadBitmap(context.getContentResolver(), icon, number, |
| new ContactBitmapWorker.BitmapWorkerListener() { |
| @Override |
| public void onBitmapLoaded(@Nullable Bitmap bitmap) { |
| Resources r = icon.getResources(); |
| if (bitmap != null) { |
| icon.setScaleType(ImageView.ScaleType.CENTER_CROP); |
| icon.setImageDrawable(new CircleBitmapDrawable(r, bitmap)); |
| } else { |
| icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); |
| LetterTileDrawable letterTileDrawable = new LetterTileDrawable(r); |
| letterTileDrawable.setContactDetails(name, number); |
| letterTileDrawable.setIsCircular(true); |
| icon.setImageDrawable(letterTileDrawable); |
| } |
| } |
| }); |
| } |
| |
| } |