• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CallerInfoUtils()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      */
getCallerInfoForCall(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener)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 
buildCallerInfo(Context context, Call call)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             info.markAsVoiceMail(context);
80         }
81 
82         return info;
83     }
84 
85     /**
86      * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
87      * from the network to indicate different number presentations, convert them to
88      * expected number and presentation values within the CallerInfo object.
89      * @param number number we use to verify if we are in a corner case
90      * @param presentation presentation value used to verify if we are in a corner case
91      * @return the new String that should be used for the phone number
92      */
modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)93     /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
94             String number, int presentation) {
95         // Obviously we return number if ci == null, but still return number if
96         // number == null, because in these cases the correct string will still be
97         // displayed/logged after this function returns based on the presentation value.
98         if (ci == null || number == null) return number;
99 
100         Log.d(TAG, "modifyForSpecialCnapCases: initially, number="
101                 + toLogSafePhoneNumber(number)
102                 + ", presentation=" + presentation + " ci " + ci);
103 
104         // "ABSENT NUMBER" is a possible value we could get from the network as the
105         // phone number, so if this happens, change it to "Unknown" in the CallerInfo
106         // and fix the presentation to be the same.
107         final String[] absentNumberValues =
108                 context.getResources().getStringArray(R.array.absent_num);
109         if (Arrays.asList(absentNumberValues).contains(number)
110                 && presentation == TelecomManager.PRESENTATION_ALLOWED) {
111             number = context.getString(R.string.unknown);
112             ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
113         }
114 
115         // Check for other special "corner cases" for CNAP and fix them similarly. Corner
116         // cases only apply if we received an allowed presentation from the network, so check
117         // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
118         // match the presentation passed in for verification (meaning we changed it previously
119         // because it's a corner case and we're being called from a different entry point).
120         if (ci.numberPresentation == TelecomManager.PRESENTATION_ALLOWED
121                 || (ci.numberPresentation != presentation
122                         && presentation == TelecomManager.PRESENTATION_ALLOWED)) {
123             // For all special strings, change number & numberPrentation.
124             if (isCnapSpecialCaseRestricted(number)) {
125                 number = context.getString(R.string.private_num);
126                 ci.numberPresentation = TelecomManager.PRESENTATION_RESTRICTED;
127             } else if (isCnapSpecialCaseUnknown(number)) {
128                 number = context.getString(R.string.unknown);
129                 ci.numberPresentation = TelecomManager.PRESENTATION_UNKNOWN;
130             }
131             Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number)
132                     + "; presentation now=" + ci.numberPresentation);
133         }
134         Log.d(TAG, "modifyForSpecialCnapCases: returning number string="
135                 + toLogSafePhoneNumber(number));
136         return number;
137     }
138 
isCnapSpecialCaseRestricted(String n)139     private static boolean isCnapSpecialCaseRestricted(String n) {
140         return n.equals("PRIVATE") || n.equals("P") || n.equals("RES");
141     }
142 
isCnapSpecialCaseUnknown(String n)143     private static boolean isCnapSpecialCaseUnknown(String n) {
144         return n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U");
145     }
146 
toLogSafePhoneNumber(String number)147     /* package */static String toLogSafePhoneNumber(String number) {
148         // For unknown number, log empty string.
149         if (number == null) {
150             return "";
151         }
152 
153         // Todo: Figure out an equivalent for VDBG
154         if (false) {
155             // When VDBG is true we emit PII.
156             return number;
157         }
158 
159         // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
160         // sanitized phone numbers.
161         StringBuilder builder = new StringBuilder();
162         for (int i = 0; i < number.length(); i++) {
163             char c = number.charAt(i);
164             if (c == '-' || c == '@' || c == '.' || c == '&') {
165                 builder.append(c);
166             } else {
167                 builder.append('x');
168             }
169         }
170         return builder.toString();
171     }
172 
173     /**
174      * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
175      * viewing a particular contact, so that it can download the high-res photo.
176      */
sendViewNotification(Context context, Uri contactUri)177     public static void sendViewNotification(Context context, Uri contactUri) {
178         final ContactLoader loader = new ContactLoader(context, contactUri,
179                 true /* postViewNotification */);
180         loader.registerListener(0, new OnLoadCompleteListener<Contact>() {
181             @Override
182             public void onLoadComplete(
183                     Loader<Contact> loader, Contact contact) {
184                 try {
185                     loader.reset();
186                 } catch (RuntimeException e) {
187                     Log.e(TAG, "Error resetting loader", e);
188                 }
189             }
190         });
191         loader.startLoading();
192     }
193 }
194