Home | History | Annotate | Download | only in incallui
      1 package com.android.incallui;
      2 
      3 import android.content.Context;
      4 import android.content.Intent;
      5 import android.net.Uri;
      6 import android.text.TextUtils;
      7 
      8 import com.android.services.telephony.common.Call;
      9 import com.android.services.telephony.common.CallIdentification;
     10 
     11 import java.util.Arrays;
     12 
     13 /**
     14  * Utility methods for contact and caller info related functionality
     15  */
     16 public class CallerInfoUtils {
     17 
     18     private static final String TAG = CallerInfoUtils.class.getSimpleName();
     19 
     20     /** Define for not a special CNAP string */
     21     private static final int CNAP_SPECIAL_CASE_NO = -1;
     22 
     23     private static final String VIEW_NOTIFICATION_ACTION =
     24             "com.android.contacts.VIEW_NOTIFICATION";
     25     private static final String VIEW_NOTIFICATION_PACKAGE = "com.android.contacts";
     26     private static final String VIEW_NOTIFICATION_CLASS =
     27             "com.android.contacts.ViewNotificationService";
     28 
     29     public CallerInfoUtils() {
     30     }
     31 
     32     private static final int QUERY_TOKEN = -1;
     33 
     34     /**
     35      * This is called to get caller info for a call. This will return a CallerInfo
     36      * object immediately based off information in the call, but
     37      * more information is returned to the OnQueryCompleteListener (which contains
     38      * information about the phone number label, user's name, etc).
     39      */
     40     public static CallerInfo getCallerInfoForCall(Context context, CallIdentification call,
     41             CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
     42         CallerInfo info = buildCallerInfo(context, call);
     43         String number = info.phoneNumber;
     44 
     45         // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.
     46 
     47         if (info.numberPresentation == Call.PRESENTATION_ALLOWED) {
     48             // Start the query with the number provided from the call.
     49             Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()...");
     50             CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number, listener, call);
     51         }
     52         return info;
     53     }
     54 
     55     public static CallerInfo buildCallerInfo(Context context, CallIdentification identification) {
     56         CallerInfo info = new CallerInfo();
     57 
     58         // Store CNAP information retrieved from the Connection (we want to do this
     59         // here regardless of whether the number is empty or not).
     60         info.cnapName = identification.getCnapName();
     61         info.name = info.cnapName;
     62         info.numberPresentation = identification.getNumberPresentation();
     63         info.namePresentation = identification.getCnapNamePresentation();
     64 
     65         String number = identification.getNumber();
     66         if (!TextUtils.isEmpty(number)) {
     67             final String[] numbers = number.split("&");
     68             number = numbers[0];
     69             if (numbers.length > 1) {
     70                 info.forwardingNumber = numbers[1];
     71             }
     72 
     73             number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
     74             info.phoneNumber = number;
     75         }
     76         return info;
     77     }
     78 
     79     /**
     80      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
     81      * from the network to indicate different number presentations, convert them to
     82      * expected number and presentation values within the CallerInfo object.
     83      * @param number number we use to verify if we are in a corner case
     84      * @param presentation presentation value used to verify if we are in a corner case
     85      * @return the new String that should be used for the phone number
     86      */
     87     /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
     88             String number, int presentation) {
     89         // Obviously we return number if ci == null, but still return number if
     90         // number == null, because in these cases the correct string will still be
     91         // displayed/logged after this function returns based on the presentation value.
     92         if (ci == null || number == null) return number;
     93 
     94         Log.d(TAG, "modifyForSpecialCnapCases: initially, number="
     95                 + toLogSafePhoneNumber(number)
     96                 + ", presentation=" + presentation + " ci " + ci);
     97 
     98         // "ABSENT NUMBER" is a possible value we could get from the network as the
     99         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
    100         // and fix the presentation to be the same.
    101         final String[] absentNumberValues =
    102                 context.getResources().getStringArray(R.array.absent_num);
    103         if (Arrays.asList(absentNumberValues).contains(number)
    104                 && presentation == Call.PRESENTATION_ALLOWED) {
    105             number = context.getString(R.string.unknown);
    106             ci.numberPresentation = Call.PRESENTATION_UNKNOWN;
    107         }
    108 
    109         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
    110         // cases only apply if we received an allowed presentation from the network, so check
    111         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
    112         // match the presentation passed in for verification (meaning we changed it previously
    113         // because it's a corner case and we're being called from a different entry point).
    114         if (ci.numberPresentation == Call.PRESENTATION_ALLOWED
    115                 || (ci.numberPresentation != presentation
    116                         && presentation == Call.PRESENTATION_ALLOWED)) {
    117             int cnapSpecialCase = checkCnapSpecialCases(number);
    118             if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
    119                 // For all special strings, change number & numberPresentation.
    120                 if (cnapSpecialCase == Call.PRESENTATION_RESTRICTED) {
    121                     number = context.getString(R.string.private_num);
    122                 } else if (cnapSpecialCase == Call.PRESENTATION_UNKNOWN) {
    123                     number = context.getString(R.string.unknown);
    124                 }
    125                 Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number)
    126                         + "; presentation now=" + cnapSpecialCase);
    127                 ci.numberPresentation = cnapSpecialCase;
    128             }
    129         }
    130         Log.d(TAG, "modifyForSpecialCnapCases: returning number string="
    131                 + toLogSafePhoneNumber(number));
    132         return number;
    133     }
    134 
    135     /**
    136      * Based on the input CNAP number string,
    137      * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
    138      * Otherwise, return CNAP_SPECIAL_CASE_NO.
    139      */
    140     private static int checkCnapSpecialCases(String n) {
    141         if (n.equals("PRIVATE") ||
    142                 n.equals("P") ||
    143                 n.equals("RES")) {
    144             Log.d(TAG, "checkCnapSpecialCases, PRIVATE string: " + n);
    145             return Call.PRESENTATION_RESTRICTED;
    146         } else if (n.equals("UNAVAILABLE") ||
    147                 n.equals("UNKNOWN") ||
    148                 n.equals("UNA") ||
    149                 n.equals("U")) {
    150             Log.d(TAG, "checkCnapSpecialCases, UNKNOWN string: " + n);
    151             return Call.PRESENTATION_UNKNOWN;
    152         } else {
    153             Log.d(TAG, "checkCnapSpecialCases, normal str. number: " + n);
    154             return CNAP_SPECIAL_CASE_NO;
    155         }
    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 that that we are viewing a particular contact, so that the high-res
    186      * photo is downloaded by the sync adapter.
    187      */
    188     public static void sendViewNotification(Context context, Uri contactUri) {
    189         final Intent intent = new Intent(VIEW_NOTIFICATION_ACTION, contactUri);
    190         intent.setClassName(VIEW_NOTIFICATION_PACKAGE, VIEW_NOTIFICATION_CLASS);
    191         context.startService(intent);
    192     }
    193 }
    194