blob: fb6f99405759bad71251241b2d5818807adff1f1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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
Hall Liud2f962a2019-10-31 15:17:58 -070017package android.telecom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
Chen Xufba9ca42019-09-07 18:56:17 -070019import android.annotation.Nullable;
Artur Satayev53ada2a2019-12-10 17:47:56 +000020import android.compat.annotation.UnsupportedAppUsage;
Hall Liu7d02a832018-11-21 14:40:19 -080021import android.content.ComponentName;
Roshan Pius93018a42015-07-13 12:57:40 -070022import android.content.ContentResolver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
24import android.database.Cursor;
Daisuke Miyakawa26597482012-04-16 14:18:09 -070025import android.graphics.Bitmap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.graphics.drawable.Drawable;
Jake Hamby7f5bee02013-10-08 16:31:16 -070027import android.location.Country;
David Brown94202fe2011-06-10 16:24:05 -070028import android.location.CountryDetector;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.net.Uri;
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -070030import android.provider.ContactsContract.CommonDataKinds.Phone;
Makoto Onukia2295e62014-07-10 15:32:16 -070031import android.provider.ContactsContract.Contacts;
David Brown85e0ff82010-10-22 12:54:42 -070032import android.provider.ContactsContract.Data;
33import android.provider.ContactsContract.PhoneLookup;
34import android.provider.ContactsContract.RawContacts;
Hall Liud2f962a2019-10-31 15:17:58 -070035import android.telephony.PhoneNumberUtils;
36import android.telephony.SubscriptionManager;
37import android.telephony.TelephonyManager;
David Brown85e0ff82010-10-22 12:54:42 -070038import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
Shaopeng Jiae7135762011-08-12 13:25:41 +020040import com.android.i18n.phonenumbers.NumberParseException;
41import com.android.i18n.phonenumbers.PhoneNumberUtil;
42import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
Hall Liu7d02a832018-11-21 14:40:19 -080043import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
Austin Wanga63a2c02019-12-19 06:38:19 +000044import com.android.internal.annotations.VisibleForTesting;
Artur Satayev53ada2a2019-12-10 17:47:56 +000045
David Brown94202fe2011-06-10 16:24:05 -070046import java.util.Locale;
47
Wink Saville2e27a0b2010-10-07 08:28:34 -070048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049/**
50 * Looks up caller information for the given phone number.
51 *
52 * {@hide}
53 */
54public class CallerInfo {
55 private static final String TAG = "CallerInfo";
Hall Liud2f962a2019-10-31 15:17:58 -070056 private static final boolean VDBG = Log.VERBOSE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057
Chen Xufba9ca42019-09-07 18:56:17 -070058 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +000059 public static final long USER_TYPE_CURRENT = 0;
Chen Xufba9ca42019-09-07 18:56:17 -070060 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +000061 public static final long USER_TYPE_WORK = 1;
62
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 /**
64 * Please note that, any one of these member variables can be null,
65 * and any accesses to them should be prepared to handle such a case.
66 *
67 * Also, it is implied that phoneNumber is more often populated than
68 * name is, (think of calls being dialed/received using numbers where
69 * names are not known to the device), so phoneNumber should serve as
70 * a dependable fallback when name is unavailable.
71 *
72 * One other detail here is that this CallerInfo object reflects
73 * information found on a connection, it is an OUTPUT that serves
74 * mainly to display information to the user. In no way is this object
75 * used as input to make a connection, so we can choose to display
76 * whatever human-readable text makes sense to the user for a
77 * connection. This is especially relevant for the phone number field,
78 * since it is the one field that is most likely exposed to the user.
79 *
80 * As an example:
81 * 1. User dials "911"
82 * 2. Device recognizes that this is an emergency number
83 * 3. We use the "Emergency Number" string instead of "911" in the
84 * phoneNumber field.
85 *
86 * What we're really doing here is treating phoneNumber as an essential
87 * field here, NOT name. We're NOT always guaranteed to have a name
88 * for a connection, but the number should be displayable.
89 */
Chen Xufba9ca42019-09-07 18:56:17 -070090 private String name;
91 private String phoneNumber;
92 /** @hide */
David Brown94202fe2011-06-10 16:24:05 -070093 public String normalizedNumber;
Chen Xufba9ca42019-09-07 18:56:17 -070094 /** @hide */
David Brown94202fe2011-06-10 16:24:05 -070095 public String geoDescription;
Chen Xufba9ca42019-09-07 18:56:17 -070096 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -070097 public String cnapName;
Chen Xufba9ca42019-09-07 18:56:17 -070098 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -070099 public int numberPresentation;
Chen Xufba9ca42019-09-07 18:56:17 -0700100 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -0700101 public int namePresentation;
Chen Xufba9ca42019-09-07 18:56:17 -0700102 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -0700103 public boolean contactExists;
Chen Xufba9ca42019-09-07 18:56:17 -0700104 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 public String phoneLabel;
Chen Xufba9ca42019-09-07 18:56:17 -0700106 /**
107 * Split up the phoneLabel into number type and label name.
108 * @hide
109 */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100110 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 public int numberType;
Chen Xufba9ca42019-09-07 18:56:17 -0700112 /** @hide */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100113 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 public String numberLabel;
Chen Xufba9ca42019-09-07 18:56:17 -0700115 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 public int photoResource;
Makoto Onukia2295e62014-07-10 15:32:16 -0700117
118 // Contact ID, which will be 0 if a contact comes from the corp CP2.
Chen Xufba9ca42019-09-07 18:56:17 -0700119 private long contactIdOrZero;
120 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 public boolean needUpdate;
Chen Xufba9ca42019-09-07 18:56:17 -0700122 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 public Uri contactRefUri;
Chen Xufba9ca42019-09-07 18:56:17 -0700124 /** @hide */
Yorke Lee282f3682014-08-29 18:39:16 -0700125 public String lookupKey;
Chen Xufba9ca42019-09-07 18:56:17 -0700126 /** @hide */
Hall Liu7d02a832018-11-21 14:40:19 -0800127 public ComponentName preferredPhoneAccountComponent;
Chen Xufba9ca42019-09-07 18:56:17 -0700128 /** @hide */
Hall Liu7d02a832018-11-21 14:40:19 -0800129 public String preferredPhoneAccountId;
Chen Xufba9ca42019-09-07 18:56:17 -0700130 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +0000131 public long userType;
132
Makoto Onukia2295e62014-07-10 15:32:16 -0700133 /**
134 * Contact display photo URI. If a contact has no display photo but a thumbnail, it'll be
135 * the thumbnail URI instead.
136 */
Chen Xufba9ca42019-09-07 18:56:17 -0700137 private Uri contactDisplayPhotoUri;
Makoto Onukia2295e62014-07-10 15:32:16 -0700138
Wink Saville2563a3a2009-06-09 10:30:03 -0700139 // fields to hold individual contact preference data,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 // including the send to voicemail flag and the ringtone
141 // uri reference.
Chen Xufba9ca42019-09-07 18:56:17 -0700142 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 public Uri contactRingtoneUri;
Chen Xufba9ca42019-09-07 18:56:17 -0700144 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 public boolean shouldSendToVoicemail;
146
147 /**
148 * Drawable representing the caller image. This is essentially
149 * a cache for the image data tied into the connection /
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700150 * callerinfo object.
151 *
152 * This might be a high resolution picture which is more suitable
153 * for full-screen image view than for smaller icons used in some
154 * kinds of notifications.
155 *
156 * The {@link #isCachedPhotoCurrent} flag indicates if the image
157 * data needs to be reloaded.
Chen Xufba9ca42019-09-07 18:56:17 -0700158 *
159 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 */
161 public Drawable cachedPhoto;
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700162 /**
163 * Bitmap representing the caller image which has possibly lower
164 * resolution than {@link #cachedPhoto} and thus more suitable for
165 * icons (like notification icons).
166 *
167 * In usual cases this is just down-scaled image of {@link #cachedPhoto}.
168 * If the down-scaling fails, this will just become null.
169 *
170 * The {@link #isCachedPhotoCurrent} flag indicates if the image
171 * data needs to be reloaded.
Chen Xufba9ca42019-09-07 18:56:17 -0700172 *
173 * @hide
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700174 */
175 public Bitmap cachedPhotoIcon;
176 /**
177 * Boolean which indicates if {@link #cachedPhoto} and
178 * {@link #cachedPhotoIcon} is fresh enough. If it is false,
179 * those images aren't pointing to valid objects.
Chen Xufba9ca42019-09-07 18:56:17 -0700180 *
181 * @hide
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700182 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 public boolean isCachedPhotoCurrent;
184
Nicolas Cataniae2241582009-09-14 19:01:43 -0700185 private boolean mIsEmergency;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700186 private boolean mIsVoiceMail;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187
Chen Xufba9ca42019-09-07 18:56:17 -0700188 /** @hide */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100189 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 public CallerInfo() {
Nicolas Cataniae2241582009-09-14 19:01:43 -0700191 // TODO: Move all the basic initialization here?
192 mIsEmergency = false;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700193 mIsVoiceMail = false;
Victor Chang9359ee12016-01-04 15:48:09 +0000194 userType = USER_TYPE_CURRENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 }
196
197 /**
198 * getCallerInfo given a Cursor.
199 * @param context the context used to retrieve string constants
200 * @param contactRef the URI to attach to this CallerInfo object
201 * @param cursor the first object in the cursor is used to build the CallerInfo object.
202 * @return the CallerInfo which contains the caller id for the given
203 * number. The returned CallerInfo is null if no number is supplied.
Chen Xufba9ca42019-09-07 18:56:17 -0700204 *
205 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 */
207 public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 CallerInfo info = new CallerInfo();
209 info.photoResource = 0;
210 info.phoneLabel = null;
211 info.numberType = 0;
212 info.numberLabel = null;
213 info.cachedPhoto = null;
214 info.isCachedPhotoCurrent = false;
Wink Savilledda53912009-05-28 17:32:34 -0700215 info.contactExists = false;
Victor Chang9359ee12016-01-04 15:48:09 +0000216 info.userType = USER_TYPE_CURRENT;
Wink Saville2563a3a2009-06-09 10:30:03 -0700217
Hall Liud2f962a2019-10-31 15:17:58 -0700218 if (VDBG) Log.v(TAG, "getCallerInfo() based on cursor...");
Wink Saville2563a3a2009-06-09 10:30:03 -0700219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 if (cursor != null) {
221 if (cursor.moveToFirst()) {
Nicolas Cataniac72509b2010-01-05 16:03:08 -0800222 // TODO: photo_id is always available but not taken
223 // care of here. Maybe we should store it in the
224 // CallerInfo object as well.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225
226 int columnIndex;
227
228 // Look for the name
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700229 columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 if (columnIndex != -1) {
231 info.name = cursor.getString(columnIndex);
232 }
233
234 // Look for the number
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700235 columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 if (columnIndex != -1) {
237 info.phoneNumber = cursor.getString(columnIndex);
238 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700239
Bai Tao6a3d1882010-08-31 17:46:33 +0800240 // Look for the normalized number
241 columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
242 if (columnIndex != -1) {
David Brown94202fe2011-06-10 16:24:05 -0700243 info.normalizedNumber = cursor.getString(columnIndex);
Bai Tao6a3d1882010-08-31 17:46:33 +0800244 }
245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 // Look for the label/type combo
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700247 columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 if (columnIndex != -1) {
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700249 int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 if (typeColumnIndex != -1) {
251 info.numberType = cursor.getInt(typeColumnIndex);
252 info.numberLabel = cursor.getString(columnIndex);
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700253 info.phoneLabel = Phone.getDisplayLabel(context,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 info.numberType, info.numberLabel)
255 .toString();
256 }
257 }
258
David Brown85e0ff82010-10-22 12:54:42 -0700259 // Look for the person_id.
260 columnIndex = getColumnIndexForPersonId(contactRef, cursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 if (columnIndex != -1) {
Makoto Onukia2295e62014-07-10 15:32:16 -0700262 final long contactId = cursor.getLong(columnIndex);
Makoto Onuki0e917332014-08-26 14:06:30 -0700263 if (contactId != 0 && !Contacts.isEnterpriseContactId(contactId)) {
Makoto Onukia2295e62014-07-10 15:32:16 -0700264 info.contactIdOrZero = contactId;
265 if (VDBG) {
Hall Liud2f962a2019-10-31 15:17:58 -0700266 Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
Makoto Onukia2295e62014-07-10 15:32:16 -0700267 }
268 }
Victor Chang9359ee12016-01-04 15:48:09 +0000269 if (Contacts.isEnterpriseContactId(contactId)) {
270 info.userType = USER_TYPE_WORK;
271 }
Nicolas Cataniac72509b2010-01-05 16:03:08 -0800272 } else {
David Brown85e0ff82010-10-22 12:54:42 -0700273 // No valid columnIndex, so we can't look up person_id.
Hall Liud2f962a2019-10-31 15:17:58 -0700274 Log.w(TAG, "Couldn't find contact_id column for " + contactRef);
David Brown85e0ff82010-10-22 12:54:42 -0700275 // Watch out: this means that anything that depends on
276 // person_id will be broken (like contact photo lookups in
277 // the in-call UI, for example.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700279
Yorke Lee282f3682014-08-29 18:39:16 -0700280 // Contact lookupKey
281 columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
282 if (columnIndex != -1) {
283 info.lookupKey = cursor.getString(columnIndex);
284 }
285
Makoto Onukia2295e62014-07-10 15:32:16 -0700286 // Display photo URI.
287 columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
288 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
289 info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
290 } else {
291 info.contactDisplayPhotoUri = null;
292 }
293
Hall Liu7d02a832018-11-21 14:40:19 -0800294 columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME);
295 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
296 info.preferredPhoneAccountComponent =
297 ComponentName.unflattenFromString(cursor.getString(columnIndex));
298 }
299
300 columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_ID);
301 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
302 info.preferredPhoneAccountId = cursor.getString(columnIndex);
303 }
304
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 // look for the custom ringtone, create from the string stored
306 // in the database.
Wenyi Wang750bb852015-09-15 16:46:29 -0700307 // An empty string ("") in the database indicates a silent ringtone,
308 // and we set contactRingtoneUri = Uri.EMPTY, so that no ringtone will be played.
309 // {null} in the database indicates the default ringtone,
310 // and we set contactRingtoneUri = null, so that default ringtone will be played.
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700311 columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
Wenyi Wang750bb852015-09-15 16:46:29 -0700313 if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
314 info.contactRingtoneUri = Uri.EMPTY;
315 } else {
316 info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
317 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 } else {
319 info.contactRingtoneUri = null;
320 }
321
322 // look for the send to voicemail flag, set it to true only
323 // under certain circumstances.
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700324 columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
Wink Saville2563a3a2009-06-09 10:30:03 -0700325 info.shouldSendToVoicemail = (columnIndex != -1) &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 ((cursor.getInt(columnIndex)) == 1);
Wink Savilledda53912009-05-28 17:32:34 -0700327 info.contactExists = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
329 cursor.close();
Wink Savillefb40dd42014-06-12 17:02:31 -0700330 cursor = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 }
332
333 info.needUpdate = false;
334 info.name = normalize(info.name);
335 info.contactRefUri = contactRef;
336
337 return info;
338 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700339
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 /**
341 * getCallerInfo given a URI, look up in the call-log database
342 * for the uri unique key.
343 * @param context the context used to get the ContentResolver
344 * @param contactRef the URI used to lookup caller id
345 * @return the CallerInfo which contains the caller id for the given
346 * number. The returned CallerInfo is null if no number is supplied.
Chen Xufba9ca42019-09-07 18:56:17 -0700347 *
348 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100350 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
Roshan Pius93018a42015-07-13 12:57:40 -0700352 CallerInfo info = null;
353 ContentResolver cr = CallerInfoAsyncQuery.getCurrentProfileContentResolver(context);
354 if (cr != null) {
355 try {
356 info = getCallerInfo(context, contactRef,
357 cr.query(contactRef, null, null, null, null));
358 } catch (RuntimeException re) {
Hall Liud2f962a2019-10-31 15:17:58 -0700359 Log.e(TAG, re, "Error getting caller info.");
Roshan Pius93018a42015-07-13 12:57:40 -0700360 }
361 }
362 return info;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700364
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 /**
366 * getCallerInfo given a phone number, look up in the call-log database
367 * for the matching caller id info.
368 * @param context the context used to get the ContentResolver
369 * @param number the phone number used to lookup caller id
370 * @return the CallerInfo which contains the caller id for the given
371 * number. The returned CallerInfo is null if no number is supplied. If
372 * a matching number is not found, then a generic caller info is returned,
373 * with all relevant fields empty or null.
Chen Xufba9ca42019-09-07 18:56:17 -0700374 *
375 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100377 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 public static CallerInfo getCallerInfo(Context context, String number) {
Hall Liud2f962a2019-10-31 15:17:58 -0700379 if (VDBG) Log.v(TAG, "getCallerInfo() based on number...");
David Brown94202fe2011-06-10 16:24:05 -0700380
Shishir Agrawal7ea3e8b2016-01-25 13:03:07 -0800381 int subId = SubscriptionManager.getDefaultSubscriptionId();
Wink Savillefb40dd42014-06-12 17:02:31 -0700382 return getCallerInfo(context, number, subId);
383 }
384
385 /**
386 * getCallerInfo given a phone number and subscription, look up in the call-log database
387 * for the matching caller id info.
388 * @param context the context used to get the ContentResolver
389 * @param number the phone number used to lookup caller id
390 * @param subId the subscription for checking for if voice mail number or not
391 * @return the CallerInfo which contains the caller id for the given
392 * number. The returned CallerInfo is null if no number is supplied. If
393 * a matching number is not found, then a generic caller info is returned,
394 * with all relevant fields empty or null.
Chen Xufba9ca42019-09-07 18:56:17 -0700395 *
396 * @hide
Wink Savillefb40dd42014-06-12 17:02:31 -0700397 */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100398 @UnsupportedAppUsage
Wink Saville63f03dd2014-10-23 10:44:45 -0700399 public static CallerInfo getCallerInfo(Context context, String number, int subId) {
Wink Savillefb40dd42014-06-12 17:02:31 -0700400
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 if (TextUtils.isEmpty(number)) {
402 return null;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700403 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404
Nicolas Catania60d45f02009-09-15 18:32:02 -0700405 // Change the callerInfo number ONLY if it is an emergency number
406 // or if it is the voicemail number. If it is either, take a
407 // shortcut and skip the query.
Yorke Lee282129f2014-06-05 08:41:47 -0700408 if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700409 return new CallerInfo().markAsEmergency(context);
Hall Liud2f962a2019-10-31 15:17:58 -0700410 } else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
411 return new CallerInfo().markAsVoiceMail(context, subId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 }
413
Makoto Onukia2295e62014-07-10 15:32:16 -0700414 Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
415 Uri.encode(number));
Wink Saville2563a3a2009-06-09 10:30:03 -0700416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 CallerInfo info = getCallerInfo(context, contactUri);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800418 info = doSecondaryLookupIfNecessary(context, number, info);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419
Wink Saville2563a3a2009-06-09 10:30:03 -0700420 // if no query results were returned with a viable number,
421 // fill in the original number value we used to query with.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 if (TextUtils.isEmpty(info.phoneNumber)) {
423 info.phoneNumber = number;
424 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700425
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 return info;
427 }
428
429 /**
Chen Xufba9ca42019-09-07 18:56:17 -0700430 * @return Name assocaited with this caller.
431 */
432 @Nullable
433 public String getName() {
434 return name;
435 }
436
437 /**
438 * Set caller Info Name.
439 * @param name caller Info Name
440 *
441 * @hide
442 */
443 public void setName(@Nullable String name) {
444 this.name = name;
445 }
446
447 /**
448 * @return Phone number assocaited with this caller.
449 */
450 @Nullable
451 public String getPhoneNumber() {
452 return phoneNumber;
453 }
454
455 /** @hide */
456 public void setPhoneNumber(String number) {
457 phoneNumber = number;
458 }
459
460 /**
461 * @return Contact ID, which will be 0 if a contact comes from the corp Contacts Provider.
462 */
463 public long getContactId() {
464 return contactIdOrZero;
465 }
466
467 /**
468 * @return Contact display photo URI. If a contact has no display photo but a thumbnail,
469 * it'll the thumbnail URI instead.
470 */
471 @Nullable
472 public Uri getContactDisplayPhotoUri() {
473 return contactDisplayPhotoUri;
474 }
475
476 /** @hide */
477 @VisibleForTesting
478 public void SetContactDisplayPhotoUri(Uri photoUri) {
479 contactDisplayPhotoUri = photoUri;
480 }
481
482 /**
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800483 * Performs another lookup if previous lookup fails and it's a SIP call
484 * and the peer's username is all numeric. Look up the username as it
485 * could be a PSTN number in the contact database.
486 *
487 * @param context the query context
488 * @param number the original phone number, could be a SIP URI
489 * @param previousResult the result of previous lookup
490 * @return previousResult if it's not the case
491 */
492 static CallerInfo doSecondaryLookupIfNecessary(Context context,
493 String number, CallerInfo previousResult) {
494 if (!previousResult.contactExists
495 && PhoneNumberUtils.isUriNumber(number)) {
David Brown158f1162011-11-16 22:10:56 -0800496 String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800497 if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
498 previousResult = getCallerInfo(context,
Makoto Onukia2295e62014-07-10 15:32:16 -0700499 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800500 Uri.encode(username)));
501 }
502 }
503 return previousResult;
504 }
505
Nicolas Cataniae2241582009-09-14 19:01:43 -0700506 // Accessors
507
508 /**
509 * @return true if the caller info is an emergency number.
Chen Xufba9ca42019-09-07 18:56:17 -0700510 * @hide
Nicolas Cataniae2241582009-09-14 19:01:43 -0700511 */
512 public boolean isEmergencyNumber() {
513 return mIsEmergency;
514 }
515
516 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700517 * @return true if the caller info is a voicemail number.
Chen Xufba9ca42019-09-07 18:56:17 -0700518 * @hide
Nicolas Catania60d45f02009-09-15 18:32:02 -0700519 */
520 public boolean isVoiceMailNumber() {
521 return mIsVoiceMail;
522 }
523
524 /**
Nicolas Cataniae2241582009-09-14 19:01:43 -0700525 * Mark this CallerInfo as an emergency call.
526 * @param context To lookup the localized 'Emergency Number' string.
527 * @return this instance.
528 */
529 // TODO: Note we're setting the phone number here (refer to
530 // javadoc comments at the top of CallerInfo class) to a localized
531 // string 'Emergency Number'. This is pretty bad because we are
532 // making UI work here instead of just packaging the data. We
533 // should set the phone number to the dialed number and name to
534 // 'Emergency Number' and let the UI make the decision about what
535 // should be displayed.
536 /* package */ CallerInfo markAsEmergency(Context context) {
537 phoneNumber = context.getString(
538 com.android.internal.R.string.emergency_call_dialog_number_for_display);
539 photoResource = com.android.internal.R.drawable.picture_emergency;
540 mIsEmergency = true;
541 return this;
542 }
543
Nicolas Catania60d45f02009-09-15 18:32:02 -0700544
Hall Liud2f962a2019-10-31 15:17:58 -0700545 /* package */ CallerInfo markAsVoiceMail(Context context, int subId) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700546 mIsVoiceMail = true;
547
548 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700549 phoneNumber = context.getSystemService(TelephonyManager.class)
550 .createForSubscriptionId(subId)
551 .getVoiceMailAlphaTag();
Nicolas Catania60d45f02009-09-15 18:32:02 -0700552 } catch (SecurityException se) {
553 // Should never happen: if this process does not have
554 // permission to retrieve VM tag, it should not have
555 // permission to retrieve VM number and would not call
556 // this method.
557 // Leave phoneNumber untouched.
Hall Liud2f962a2019-10-31 15:17:58 -0700558 Log.e(TAG, se, "Cannot access VoiceMail.");
Nicolas Catania60d45f02009-09-15 18:32:02 -0700559 }
560 // TODO: There is no voicemail picture?
561 // FIXME: FIND ANOTHER ICON
562 // photoResource = android.R.drawable.badge_voicemail;
563 return this;
564 }
565
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 private static String normalize(String s) {
567 if (s == null || s.length() > 0) {
568 return s;
569 } else {
570 return null;
571 }
572 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700573
574 /**
David Brown85e0ff82010-10-22 12:54:42 -0700575 * Returns the column index to use to find the "person_id" field in
576 * the specified cursor, based on the contact URI that was originally
577 * queried.
578 *
579 * This is a helper function for the getCallerInfo() method that takes
580 * a Cursor. Looking up the person_id is nontrivial (compared to all
581 * the other CallerInfo fields) since the column we need to use
582 * depends on what query we originally ran.
583 *
584 * Watch out: be sure to not do any database access in this method, since
585 * it's run from the UI thread (see comments below for more info.)
586 *
587 * @return the columnIndex to use (with cursor.getLong()) to get the
588 * person_id, or -1 if we couldn't figure out what colum to use.
589 *
590 * TODO: Add a unittest for this method. (This is a little tricky to
591 * test, since we'll need a live contacts database to test against,
592 * preloaded with at least some phone numbers and SIP addresses. And
593 * we'll probably have to hardcode the column indexes we expect, so
594 * the test might break whenever the contacts schema changes. But we
595 * can at least make sure we handle all the URI patterns we claim to,
596 * and that the mime types match what we expect...)
597 */
598 private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
599 // TODO: This is pretty ugly now, see bug 2269240 for
600 // more details. The column to use depends upon the type of URL:
601 // - content://com.android.contacts/data/phones ==> use the "contact_id" column
602 // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
603 // - content://com.android.contacts/data ==> use the "contact_id" column
604 // If it's none of the above, we leave columnIndex=-1 which means
605 // that the person_id field will be left unset.
606 //
607 // The logic here *used* to be based on the mime type of contactRef
608 // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
609 // RawContacts.CONTACT_ID column). But looking up the mime type requires
610 // a call to context.getContentResolver().getType(contactRef), which
611 // isn't safe to do from the UI thread since it can cause an ANR if
612 // the contacts provider is slow or blocked (like during a sync.)
613 //
614 // So instead, figure out the column to use for person_id by just
615 // looking at the URI itself.
616
Hall Liud2f962a2019-10-31 15:17:58 -0700617 if (VDBG) Log.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
David Brown85e0ff82010-10-22 12:54:42 -0700618 + contactRef + "'...");
619 // Warning: Do not enable the following logging (due to ANR risk.)
Hall Liud2f962a2019-10-31 15:17:58 -0700620 // if (VDBG) Log.v(TAG, "- MIME type: "
David Brown85e0ff82010-10-22 12:54:42 -0700621 // + context.getContentResolver().getType(contactRef));
622
623 String url = contactRef.toString();
624 String columnName = null;
625 if (url.startsWith("content://com.android.contacts/data/phones")) {
626 // Direct lookup in the Phone table.
627 // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
Hall Liud2f962a2019-10-31 15:17:58 -0700628 if (VDBG) Log.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700629 columnName = RawContacts.CONTACT_ID;
630 } else if (url.startsWith("content://com.android.contacts/data")) {
631 // Direct lookup in the Data table.
632 // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
Hall Liud2f962a2019-10-31 15:17:58 -0700633 if (VDBG) Log.v(TAG, "'data' URI; using Data.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700634 // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
635 columnName = Data.CONTACT_ID;
636 } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
637 // Lookup in the PhoneLookup table, which provides "fuzzy matching"
638 // for phone numbers.
639 // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
Hall Liud2f962a2019-10-31 15:17:58 -0700640 if (VDBG) Log.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
David Brown85e0ff82010-10-22 12:54:42 -0700641 columnName = PhoneLookup._ID;
642 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700643 Log.w(TAG, "Unexpected prefix for contactRef '" + url + "'");
David Brown85e0ff82010-10-22 12:54:42 -0700644 }
645 int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
Hall Liud2f962a2019-10-31 15:17:58 -0700646 if (VDBG) Log.v(TAG, "==> Using column '" + columnName
David Brown85e0ff82010-10-22 12:54:42 -0700647 + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
648 return columnIndex;
649 }
650
651 /**
David Brown94202fe2011-06-10 16:24:05 -0700652 * Updates this CallerInfo's geoDescription field, based on the raw
653 * phone number in the phoneNumber field.
654 *
655 * (Note that the various getCallerInfo() methods do *not* set the
656 * geoDescription automatically; you need to call this method
657 * explicitly to get it.)
658 *
659 * @param context the context used to look up the current locale / country
660 * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
661 * this specifies a fallback number to use instead.
Chen Xufba9ca42019-09-07 18:56:17 -0700662 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700663 */
664 public void updateGeoDescription(Context context, String fallbackNumber) {
665 String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
666 geoDescription = getGeoDescription(context, number);
667 }
668
669 /**
670 * @return a geographical description string for the specified number.
Shaopeng Jiae7135762011-08-12 13:25:41 +0200671 * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
Chen Xufba9ca42019-09-07 18:56:17 -0700672 *
673 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700674 */
Ceci Wu3b90d482016-08-25 14:48:19 +0800675 public static String getGeoDescription(Context context, String number) {
Hall Liud2f962a2019-10-31 15:17:58 -0700676 if (VDBG) Log.v(TAG, "getGeoDescription('" + number + "')...");
David Brown94202fe2011-06-10 16:24:05 -0700677
678 if (TextUtils.isEmpty(number)) {
679 return null;
680 }
681
682 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
683 PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
684
David Brown94202fe2011-06-10 16:24:05 -0700685 Locale locale = context.getResources().getConfiguration().locale;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200686 String countryIso = getCurrentCountryIso(context, locale);
David Brown94202fe2011-06-10 16:24:05 -0700687 PhoneNumber pn = null;
688 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700689 if (VDBG) Log.v(TAG, "parsing '" + number
David Browncec25c42011-06-23 14:17:27 -0700690 + "' for countryIso '" + countryIso + "'...");
David Brown94202fe2011-06-10 16:24:05 -0700691 pn = util.parse(number, countryIso);
Hall Liud2f962a2019-10-31 15:17:58 -0700692 if (VDBG) Log.v(TAG, "- parsed number: " + pn);
David Brown94202fe2011-06-10 16:24:05 -0700693 } catch (NumberParseException e) {
Hall Liud2f962a2019-10-31 15:17:58 -0700694 Log.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
695 + Log.pii(number) + "'");
David Brown94202fe2011-06-10 16:24:05 -0700696 }
697
698 if (pn != null) {
699 String description = geocoder.getDescriptionForNumber(pn, locale);
Hall Liud2f962a2019-10-31 15:17:58 -0700700 if (VDBG) Log.v(TAG, "- got description: '" + description + "'");
David Brown94202fe2011-06-10 16:24:05 -0700701 return description;
702 } else {
703 return null;
704 }
705 }
706
707 /**
Shaopeng Jia9683f992011-09-07 14:07:15 +0200708 * @return The ISO 3166-1 two letters country code of the country the user
709 * is in.
710 */
711 private static String getCurrentCountryIso(Context context, Locale locale) {
Jake Hamby7f5bee02013-10-08 16:31:16 -0700712 String countryIso = null;
713 CountryDetector detector = (CountryDetector) context.getSystemService(
714 Context.COUNTRY_DETECTOR);
715 if (detector != null) {
716 Country country = detector.detectCountry();
717 if (country != null) {
718 countryIso = country.getCountryIso();
719 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700720 Log.e(TAG, new Exception(), "CountryDetector.detectCountry() returned null.");
Jake Hamby7f5bee02013-10-08 16:31:16 -0700721 }
722 }
723 if (countryIso == null) {
724 countryIso = locale.getCountry();
Hall Liud2f962a2019-10-31 15:17:58 -0700725 Log.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
Jake Hamby7f5bee02013-10-08 16:31:16 -0700726 + countryIso);
727 }
728 return countryIso;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200729 }
730
Chen Xufba9ca42019-09-07 18:56:17 -0700731 /** @hide */
Jay Shraunera2c93482013-10-21 11:54:19 -0700732 protected static String getCurrentCountryIso(Context context) {
733 return getCurrentCountryIso(context, Locale.getDefault());
734 }
735
Shaopeng Jia9683f992011-09-07 14:07:15 +0200736 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700737 * @return a string debug representation of this instance.
738 */
Victor Chang9359ee12016-01-04 15:48:09 +0000739 @Override
Nicolas Catania60d45f02009-09-15 18:32:02 -0700740 public String toString() {
David Brown04639ba2010-11-30 15:31:15 -0800741 // Warning: never check in this file with VERBOSE_DEBUG = true
742 // because that will result in PII in the system log.
743 final boolean VERBOSE_DEBUG = false;
744
745 if (VERBOSE_DEBUG) {
746 return new StringBuilder(384)
David Brown94202fe2011-06-10 16:24:05 -0700747 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800748 .append("\nname: " + name)
749 .append("\nphoneNumber: " + phoneNumber)
David Brown94202fe2011-06-10 16:24:05 -0700750 .append("\nnormalizedNumber: " + normalizedNumber)
751 .append("\ngeoDescription: " + geoDescription)
David Brown04639ba2010-11-30 15:31:15 -0800752 .append("\ncnapName: " + cnapName)
753 .append("\nnumberPresentation: " + numberPresentation)
754 .append("\nnamePresentation: " + namePresentation)
755 .append("\ncontactExits: " + contactExists)
756 .append("\nphoneLabel: " + phoneLabel)
757 .append("\nnumberType: " + numberType)
758 .append("\nnumberLabel: " + numberLabel)
759 .append("\nphotoResource: " + photoResource)
Makoto Onukia2295e62014-07-10 15:32:16 -0700760 .append("\ncontactIdOrZero: " + contactIdOrZero)
David Brown04639ba2010-11-30 15:31:15 -0800761 .append("\nneedUpdate: " + needUpdate)
Makoto Onukia2295e62014-07-10 15:32:16 -0700762 .append("\ncontactRingtoneUri: " + contactRingtoneUri)
763 .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
David Brown04639ba2010-11-30 15:31:15 -0800764 .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
765 .append("\ncachedPhoto: " + cachedPhoto)
766 .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
767 .append("\nemergency: " + mIsEmergency)
768 .append("\nvoicemail " + mIsVoiceMail)
769 .append("\ncontactExists " + contactExists)
Victor Chang9359ee12016-01-04 15:48:09 +0000770 .append("\nuserType " + userType)
David Brown94202fe2011-06-10 16:24:05 -0700771 .append(" }")
David Brown04639ba2010-11-30 15:31:15 -0800772 .toString();
773 } else {
774 return new StringBuilder(128)
David Brown94202fe2011-06-10 16:24:05 -0700775 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800776 .append("name " + ((name == null) ? "null" : "non-null"))
777 .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
778 .append(" }")
779 .toString();
780 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700781 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700782}