Home | History | Annotate | Download | only in incallui
      1 package com.android.incallui;
      2 
      3 import android.content.Context;
      4 import android.content.Loader;
      5 import android.content.Loader.OnLoadCompleteListener;
      6 import android.net.Uri;
      7 import android.telecom.PhoneAccount;
      8 import android.telecom.TelecomManager;
      9 import android.text.TextUtils;
     10 import android.util.Log;
     11 
     12 import com.android.contacts.common.model.Contact;
     13 import com.android.contacts.common.model.ContactLoader;
     14 
     15 import java.util.Arrays;
     16 
     17 /**
     18  * Utility methods for contact and caller info related functionality
     19  */
     20 public class CallerInfoUtils {
     21 
     22     private static final String TAG = CallerInfoUtils.class.getSimpleName();
     23 
     24     /** Define for not a special CNAP string */
     25     private static final int CNAP_SPECIAL_CASE_NO = -1;
     26 
     27     public CallerInfoUtils() {
     28     }
     29 
     30     private static final int QUERY_TOKEN = -1;
     31 
     32     /**
     33      * This is called to get caller info for a call. This will return a CallerInfo
     34      * object immediately based off information in the call, but
     35      * more information is returned to the OnQueryCompleteListener (which contains
     36      * information about the phone number label, user's name, etc).
     37      */
     38     public static CallerInfo getCallerInfoForCall(Context context, Call call,
     39             CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
     40         CallerInfo info = buildCallerInfo(context, call);
     41 
     42         // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
     43 
     44         if (info.numberPresentation == TelecomManager.PRESENTATION_ALLOWED) {
     45             // Start the query with the number provided from the call.
     46             Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()...");
     47             CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, info, listener, call);
     48         }
     49         return info;
     50     }
     51 
     52     public static CallerInfo buildCallerInfo(Context context, Call call) {
     53         CallerInfo info = new CallerInfo();
     54 
     55         // Store CNAP information retrieved from the Connection (we want to do this
     56         // here regardless of whether the number is empty or not).
     57         info.cnapName = call.getCnapName();
     58         info.name = info.cnapName;
     59         info.numberPresentation = call.getNumberPresentation();
     60         info.namePresentation = call.getCnapNamePresentation();
     61 
     62         String number = call.getNumber();
     63         if (!TextUtils.isEmpty(number)) {
     64             final String[] numbers = number.split("&");
     65             number = numbers[0];
     66             if (numbers.length > 1) {
     67                 info.forwardingNumber = numbers[1];
     68             }
     69 
     70             number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
     71             info.phoneNumber = number;
     72         }
     73 
     74         // Because the InCallUI is immediately launched before the call is connected, occasionally
     75         // a voicemail call will be passed to InCallUI as a "voicemail:" URI without a number.
     76         // This call should still be handled as a voicemail call.
     77         if ((call.getHandle() != null &&
     78                 PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) ||
     79                 isVoiceMailNumber(context, call)) {
     80             info.markAsVoiceMail(context);
     81         }
     82 
     83         ContactInfoCache.getInstance(context).maybeInsertCnapInformationIntoCache(context, call,
     84                 info);
     85 
     86         return info;
     87     }
     88 
     89     public static boolean isVoiceMailNumber(Context context, Call call) {
     90          TelecomManager telecomManager =
     91                  (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
     92          return telecomManager.isVoiceMailNumber(
     93                  call.getTelecommCall().getDetails().getAccountHandle(), call.getNumber());
     94     }
     95 
     96     /**
     97      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
     98      * from the network to indicate different number presentations, convert them to
     99      * expected number and presentation values within the CallerInfo object.
    100      * @param number number we use to verify if we are in a corner case
    101      * @param presentation presentation value used to verify if we are in a corner case
    102      * @return the new String that should be used for the phone number
    103      */
    104     /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
    105             String number, int presentation) {
    106         // Obviously we return number if ci == null, but still return number if
    107         // number == null, because in these cases the correct string will still be
    108         // displayed/logged after this function returns based on the presentation value.
    109         if (ci == null || number == null) return number;
    110 
    111         Log.d(TAG, "modifyForSpecialCnapCases: initially, number="
    112                 + toLogSafePhoneNumber(number)
    113                 + ", presentation=" + presentation + " ci " + ci);
    114 
    115         // "ABSENT NUMBER" is a possible value we could get from the network as the
    116         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
    117         // and fix the presentation to be the same.
    118         final String[] absentNumberValues =
    119                 context.getResources().getStringArray(R.array.absent_num);
    120         if (Arrays.asList(absentNumberValues).contains(number)
    121                 && presentation == TelecomManager.PRESENTATION_ALLOWED) {
    122             number = context.getString(R.string.unknown);
    123             ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
    124         }
    125 
    126         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
    127         // cases only apply if we received an allowed presentation from the network, so check
    128         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
    129         // match the presentation passed in for verification (meaning we changed it previously
    130         // because it's a corner case and we're being called from a different entry point).
    131         if (ci.numberPresentation == TelecomManager.PRESENTATION_ALLOWED
    132                 || (ci.numberPresentation != presentation
    133                         && presentation == TelecomManager.PRESENTATION_ALLOWED)) {
    134             // For all special strings, change number & numberPrentation.
    135             if (isCnapSpecialCaseRestricted(number)) {
    136                 number = context.getString(R.string.private_num);
    137                 ci.numberPresentation = TelecomManager.PRESENTATION_RESTRICTED;
    138             } else if (isCnapSpecialCaseUnknown(number)) {
    139                 number = context.getString(R.string.unknown);
    140                 ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
    141             }
    142             Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number)
    143                     + "; presentation now=" + ci.numberPresentation);
    144         }
    145         Log.d(TAG, "modifyForSpecialCnapCases: returning number string="
    146                 + toLogSafePhoneNumber(number));
    147         return number;
    148     }
    149 
    150     private static boolean isCnapSpecialCaseRestricted(String n) {
    151         return n.equals("PRIVATE") || n.equals("P") || n.equals("RES");
    152     }
    153 
    154     private static boolean isCnapSpecialCaseUnknown(String n) {
    155         return n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U");
    156     }
    157 
    158     /* package */static String toLogSafePhoneNumber(String number) {
    159         // For unknown number, log empty string.
    160         if (number == null) {
    161             return "";
    162         }
    163 
    164         // Todo: Figure out an equivalent for VDBG
    165         if (false) {
    166             // When VDBG is true we emit PII.
    167             return number;
    168         }
    169 
    170         // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
    171         // sanitized phone numbers.
    172         StringBuilder builder = new StringBuilder();
    173         for (int i = 0; i < number.length(); i++) {
    174             char c = number.charAt(i);
    175             if (c == '-' || c == '@' || c == '.' || c == '&') {
    176                 builder.append(c);
    177             } else {
    178                 builder.append('x');
    179             }
    180         }
    181         return builder.toString();
    182     }
    183 
    184     /**
    185      * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
    186      * viewing a particular contact, so that it can download the high-res photo.
    187      */
    188     public static void sendViewNotification(Context context, Uri contactUri) {
    189         final ContactLoader loader = new ContactLoader(context, contactUri,
    190                 true /* postViewNotification */);
    191         loader.registerListener(0, new OnLoadCompleteListener<Contact>() {
    192             @Override
    193             public void onLoadComplete(
    194                     Loader<Contact> loader, Contact contact) {
    195                 try {
    196                     loader.reset();
    197                 } catch (RuntimeException e) {
    198                     Log.e(TAG, "Error resetting loader", e);
    199                 }
    200             }
    201         });
    202         loader.startLoading();
    203     }
    204 }
    205