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