Home | History | Annotate | Download | only in telecom
      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.support.car.ui.CircleBitmapDrawable;
     35 import android.telecom.Call;
     36 import android.telephony.PhoneNumberUtils;
     37 import android.telephony.TelephonyManager;
     38 import android.text.TextUtils;
     39 import android.text.format.DateUtils;
     40 import android.util.Log;
     41 import android.widget.ImageView;
     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
     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      */
     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      */
    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      */
    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 
    166     public static String getVoicemailNumber(Context context) {
    167         if (sVoicemailNumber == null) {
    168             sVoicemailNumber = getTelephonyManager(context).getVoiceMailNumber();
    169         }
    170         return sVoicemailNumber;
    171     }
    172 
    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 
    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 
    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 
    219     public static String getDisplayName(Context context, String number) {
    220         return getDisplayName(context, number, null);
    221     }
    222 
    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 
    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      */
    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      */
    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 
    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 
    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      */
    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