blob: a63ee4664378c3ce43680d4661a65a4a1ce87f6a [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;
Mathew Inwood5d123b62020-11-04 09:29:36 +000030import android.os.Build;
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -070031import android.provider.ContactsContract.CommonDataKinds.Phone;
Makoto Onukia2295e62014-07-10 15:32:16 -070032import android.provider.ContactsContract.Contacts;
David Brown85e0ff82010-10-22 12:54:42 -070033import android.provider.ContactsContract.Data;
34import android.provider.ContactsContract.PhoneLookup;
35import android.provider.ContactsContract.RawContacts;
Hall Liud2f962a2019-10-31 15:17:58 -070036import android.telephony.PhoneNumberUtils;
37import android.telephony.SubscriptionManager;
38import android.telephony.TelephonyManager;
David Brown85e0ff82010-10-22 12:54:42 -070039import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Shaopeng Jiae7135762011-08-12 13:25:41 +020041import com.android.i18n.phonenumbers.NumberParseException;
42import com.android.i18n.phonenumbers.PhoneNumberUtil;
43import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
Hall Liu7d02a832018-11-21 14:40:19 -080044import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
Austin Wanga63a2c02019-12-19 06:38:19 +000045import com.android.internal.annotations.VisibleForTesting;
Artur Satayev53ada2a2019-12-10 17:47:56 +000046
David Brown94202fe2011-06-10 16:24:05 -070047import java.util.Locale;
48
Wink Saville2e27a0b2010-10-07 08:28:34 -070049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050/**
51 * Looks up caller information for the given phone number.
52 *
53 * {@hide}
54 */
55public class CallerInfo {
56 private static final String TAG = "CallerInfo";
Hall Liud2f962a2019-10-31 15:17:58 -070057 private static final boolean VDBG = Log.VERBOSE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058
Chen Xufba9ca42019-09-07 18:56:17 -070059 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +000060 public static final long USER_TYPE_CURRENT = 0;
Chen Xufba9ca42019-09-07 18:56:17 -070061 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +000062 public static final long USER_TYPE_WORK = 1;
63
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 /**
65 * Please note that, any one of these member variables can be null,
66 * and any accesses to them should be prepared to handle such a case.
67 *
68 * Also, it is implied that phoneNumber is more often populated than
69 * name is, (think of calls being dialed/received using numbers where
70 * names are not known to the device), so phoneNumber should serve as
71 * a dependable fallback when name is unavailable.
72 *
73 * One other detail here is that this CallerInfo object reflects
74 * information found on a connection, it is an OUTPUT that serves
75 * mainly to display information to the user. In no way is this object
76 * used as input to make a connection, so we can choose to display
77 * whatever human-readable text makes sense to the user for a
78 * connection. This is especially relevant for the phone number field,
79 * since it is the one field that is most likely exposed to the user.
80 *
81 * As an example:
82 * 1. User dials "911"
83 * 2. Device recognizes that this is an emergency number
84 * 3. We use the "Emergency Number" string instead of "911" in the
85 * phoneNumber field.
86 *
87 * What we're really doing here is treating phoneNumber as an essential
88 * field here, NOT name. We're NOT always guaranteed to have a name
89 * for a connection, but the number should be displayable.
90 */
Chen Xufba9ca42019-09-07 18:56:17 -070091 private String name;
92 private String phoneNumber;
93 /** @hide */
David Brown94202fe2011-06-10 16:24:05 -070094 public String normalizedNumber;
Chen Xufba9ca42019-09-07 18:56:17 -070095 /** @hide */
David Brown94202fe2011-06-10 16:24:05 -070096 public String geoDescription;
Chen Xufba9ca42019-09-07 18:56:17 -070097 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -070098 public String cnapName;
Chen Xufba9ca42019-09-07 18:56:17 -070099 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -0700100 public int numberPresentation;
Chen Xufba9ca42019-09-07 18:56:17 -0700101 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -0700102 public int namePresentation;
Chen Xufba9ca42019-09-07 18:56:17 -0700103 /** @hide */
Wink Savilledda53912009-05-28 17:32:34 -0700104 public boolean contactExists;
Chen Xufba9ca42019-09-07 18:56:17 -0700105 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 public String phoneLabel;
Chen Xufba9ca42019-09-07 18:56:17 -0700107 /**
108 * Split up the phoneLabel into number type and label name.
109 * @hide
110 */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100111 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 public int numberType;
Chen Xufba9ca42019-09-07 18:56:17 -0700113 /** @hide */
Mathew Inwoodb4936a32018-08-21 16:40:35 +0100114 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 public String numberLabel;
Chen Xufba9ca42019-09-07 18:56:17 -0700116 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 public int photoResource;
Makoto Onukia2295e62014-07-10 15:32:16 -0700118
119 // Contact ID, which will be 0 if a contact comes from the corp CP2.
Chen Xufba9ca42019-09-07 18:56:17 -0700120 private long contactIdOrZero;
121 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 public boolean needUpdate;
Chen Xufba9ca42019-09-07 18:56:17 -0700123 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 public Uri contactRefUri;
Chen Xufba9ca42019-09-07 18:56:17 -0700125 /** @hide */
Yorke Lee282f3682014-08-29 18:39:16 -0700126 public String lookupKey;
Chen Xufba9ca42019-09-07 18:56:17 -0700127 /** @hide */
Hall Liu7d02a832018-11-21 14:40:19 -0800128 public ComponentName preferredPhoneAccountComponent;
Chen Xufba9ca42019-09-07 18:56:17 -0700129 /** @hide */
Hall Liu7d02a832018-11-21 14:40:19 -0800130 public String preferredPhoneAccountId;
Chen Xufba9ca42019-09-07 18:56:17 -0700131 /** @hide */
Victor Chang9359ee12016-01-04 15:48:09 +0000132 public long userType;
133
Makoto Onukia2295e62014-07-10 15:32:16 -0700134 /**
135 * Contact display photo URI. If a contact has no display photo but a thumbnail, it'll be
136 * the thumbnail URI instead.
137 */
Chen Xufba9ca42019-09-07 18:56:17 -0700138 private Uri contactDisplayPhotoUri;
Makoto Onukia2295e62014-07-10 15:32:16 -0700139
Wink Saville2563a3a2009-06-09 10:30:03 -0700140 // fields to hold individual contact preference data,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 // including the send to voicemail flag and the ringtone
142 // uri reference.
Chen Xufba9ca42019-09-07 18:56:17 -0700143 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 public Uri contactRingtoneUri;
Chen Xufba9ca42019-09-07 18:56:17 -0700145 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 public boolean shouldSendToVoicemail;
147
148 /**
149 * Drawable representing the caller image. This is essentially
150 * a cache for the image data tied into the connection /
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700151 * callerinfo object.
152 *
153 * This might be a high resolution picture which is more suitable
154 * for full-screen image view than for smaller icons used in some
155 * kinds of notifications.
156 *
157 * The {@link #isCachedPhotoCurrent} flag indicates if the image
158 * data needs to be reloaded.
Chen Xufba9ca42019-09-07 18:56:17 -0700159 *
160 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 */
162 public Drawable cachedPhoto;
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700163 /**
164 * Bitmap representing the caller image which has possibly lower
165 * resolution than {@link #cachedPhoto} and thus more suitable for
166 * icons (like notification icons).
167 *
168 * In usual cases this is just down-scaled image of {@link #cachedPhoto}.
169 * If the down-scaling fails, this will just become null.
170 *
171 * The {@link #isCachedPhotoCurrent} flag indicates if the image
172 * data needs to be reloaded.
Chen Xufba9ca42019-09-07 18:56:17 -0700173 *
174 * @hide
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700175 */
176 public Bitmap cachedPhotoIcon;
177 /**
178 * Boolean which indicates if {@link #cachedPhoto} and
179 * {@link #cachedPhotoIcon} is fresh enough. If it is false,
180 * those images aren't pointing to valid objects.
Chen Xufba9ca42019-09-07 18:56:17 -0700181 *
182 * @hide
Daisuke Miyakawa26597482012-04-16 14:18:09 -0700183 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 public boolean isCachedPhotoCurrent;
185
Nicolas Cataniae2241582009-09-14 19:01:43 -0700186 private boolean mIsEmergency;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700187 private boolean mIsVoiceMail;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188
Chen Xufba9ca42019-09-07 18:56:17 -0700189 /** @hide */
Mathew Inwood5d123b62020-11-04 09:29:36 +0000190 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 public CallerInfo() {
Nicolas Cataniae2241582009-09-14 19:01:43 -0700192 // TODO: Move all the basic initialization here?
193 mIsEmergency = false;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700194 mIsVoiceMail = false;
Victor Chang9359ee12016-01-04 15:48:09 +0000195 userType = USER_TYPE_CURRENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 }
197
198 /**
199 * getCallerInfo given a Cursor.
200 * @param context the context used to retrieve string constants
201 * @param contactRef the URI to attach to this CallerInfo object
202 * @param cursor the first object in the cursor is used to build the CallerInfo object.
203 * @return the CallerInfo which contains the caller id for the given
204 * number. The returned CallerInfo is null if no number is supplied.
Chen Xufba9ca42019-09-07 18:56:17 -0700205 *
206 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 */
208 public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 CallerInfo info = new CallerInfo();
210 info.photoResource = 0;
211 info.phoneLabel = null;
212 info.numberType = 0;
213 info.numberLabel = null;
214 info.cachedPhoto = null;
215 info.isCachedPhotoCurrent = false;
Wink Savilledda53912009-05-28 17:32:34 -0700216 info.contactExists = false;
Victor Chang9359ee12016-01-04 15:48:09 +0000217 info.userType = USER_TYPE_CURRENT;
Wink Saville2563a3a2009-06-09 10:30:03 -0700218
Hall Liud2f962a2019-10-31 15:17:58 -0700219 if (VDBG) Log.v(TAG, "getCallerInfo() based on cursor...");
Wink Saville2563a3a2009-06-09 10:30:03 -0700220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 if (cursor != null) {
222 if (cursor.moveToFirst()) {
Nicolas Cataniac72509b2010-01-05 16:03:08 -0800223 // TODO: photo_id is always available but not taken
224 // care of here. Maybe we should store it in the
225 // CallerInfo object as well.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226
227 int columnIndex;
228
229 // Look for the name
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700230 columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 if (columnIndex != -1) {
232 info.name = cursor.getString(columnIndex);
233 }
234
235 // Look for the number
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700236 columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 if (columnIndex != -1) {
238 info.phoneNumber = cursor.getString(columnIndex);
239 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700240
Bai Tao6a3d1882010-08-31 17:46:33 +0800241 // Look for the normalized number
242 columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
243 if (columnIndex != -1) {
David Brown94202fe2011-06-10 16:24:05 -0700244 info.normalizedNumber = cursor.getString(columnIndex);
Bai Tao6a3d1882010-08-31 17:46:33 +0800245 }
246
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 // Look for the label/type combo
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700248 columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 if (columnIndex != -1) {
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700250 int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 if (typeColumnIndex != -1) {
252 info.numberType = cursor.getInt(typeColumnIndex);
253 info.numberLabel = cursor.getString(columnIndex);
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700254 info.phoneLabel = Phone.getDisplayLabel(context,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 info.numberType, info.numberLabel)
256 .toString();
257 }
258 }
259
David Brown85e0ff82010-10-22 12:54:42 -0700260 // Look for the person_id.
261 columnIndex = getColumnIndexForPersonId(contactRef, cursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 if (columnIndex != -1) {
Makoto Onukia2295e62014-07-10 15:32:16 -0700263 final long contactId = cursor.getLong(columnIndex);
Makoto Onuki0e917332014-08-26 14:06:30 -0700264 if (contactId != 0 && !Contacts.isEnterpriseContactId(contactId)) {
Makoto Onukia2295e62014-07-10 15:32:16 -0700265 info.contactIdOrZero = contactId;
266 if (VDBG) {
Hall Liud2f962a2019-10-31 15:17:58 -0700267 Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
Makoto Onukia2295e62014-07-10 15:32:16 -0700268 }
269 }
Victor Chang9359ee12016-01-04 15:48:09 +0000270 if (Contacts.isEnterpriseContactId(contactId)) {
271 info.userType = USER_TYPE_WORK;
272 }
Nicolas Cataniac72509b2010-01-05 16:03:08 -0800273 } else {
David Brown85e0ff82010-10-22 12:54:42 -0700274 // No valid columnIndex, so we can't look up person_id.
Hall Liud2f962a2019-10-31 15:17:58 -0700275 Log.w(TAG, "Couldn't find contact_id column for " + contactRef);
David Brown85e0ff82010-10-22 12:54:42 -0700276 // Watch out: this means that anything that depends on
277 // person_id will be broken (like contact photo lookups in
278 // the in-call UI, for example.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700280
Yorke Lee282f3682014-08-29 18:39:16 -0700281 // Contact lookupKey
282 columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
283 if (columnIndex != -1) {
284 info.lookupKey = cursor.getString(columnIndex);
285 }
286
Makoto Onukia2295e62014-07-10 15:32:16 -0700287 // Display photo URI.
288 columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
289 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
290 info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
291 } else {
292 info.contactDisplayPhotoUri = null;
293 }
294
Hall Liu7d02a832018-11-21 14:40:19 -0800295 columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME);
296 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
297 info.preferredPhoneAccountComponent =
298 ComponentName.unflattenFromString(cursor.getString(columnIndex));
299 }
300
301 columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_ID);
302 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
303 info.preferredPhoneAccountId = cursor.getString(columnIndex);
304 }
305
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 // look for the custom ringtone, create from the string stored
307 // in the database.
Wenyi Wang750bb852015-09-15 16:46:29 -0700308 // An empty string ("") in the database indicates a silent ringtone,
309 // and we set contactRingtoneUri = Uri.EMPTY, so that no ringtone will be played.
310 // {null} in the database indicates the default ringtone,
311 // and we set contactRingtoneUri = null, so that default ringtone will be played.
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700312 columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
Wenyi Wang750bb852015-09-15 16:46:29 -0700314 if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
315 info.contactRingtoneUri = Uri.EMPTY;
316 } else {
317 info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 } else {
320 info.contactRingtoneUri = null;
321 }
322
323 // look for the send to voicemail flag, set it to true only
324 // under certain circumstances.
Dmitri Plotnikov3c513ed2009-08-19 15:56:30 -0700325 columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
Wink Saville2563a3a2009-06-09 10:30:03 -0700326 info.shouldSendToVoicemail = (columnIndex != -1) &&
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 ((cursor.getInt(columnIndex)) == 1);
Wink Savilledda53912009-05-28 17:32:34 -0700328 info.contactExists = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
330 cursor.close();
Wink Savillefb40dd42014-06-12 17:02:31 -0700331 cursor = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 }
333
334 info.needUpdate = false;
335 info.name = normalize(info.name);
336 info.contactRefUri = contactRef;
337
338 return info;
339 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 /**
342 * getCallerInfo given a URI, look up in the call-log database
343 * for the uri unique key.
344 * @param context the context used to get the ContentResolver
345 * @param contactRef the URI used to lookup caller id
346 * @return the CallerInfo which contains the caller id for the given
347 * number. The returned CallerInfo is null if no number is supplied.
Chen Xufba9ca42019-09-07 18:56:17 -0700348 *
349 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 */
Mathew Inwood5d123b62020-11-04 09:29:36 +0000351 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
Roshan Pius93018a42015-07-13 12:57:40 -0700353 CallerInfo info = null;
354 ContentResolver cr = CallerInfoAsyncQuery.getCurrentProfileContentResolver(context);
355 if (cr != null) {
356 try {
357 info = getCallerInfo(context, contactRef,
358 cr.query(contactRef, null, null, null, null));
359 } catch (RuntimeException re) {
Hall Liud2f962a2019-10-31 15:17:58 -0700360 Log.e(TAG, re, "Error getting caller info.");
Roshan Pius93018a42015-07-13 12:57:40 -0700361 }
362 }
363 return info;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 /**
367 * getCallerInfo given a phone number, look up in the call-log database
368 * for the matching caller id info.
369 * @param context the context used to get the ContentResolver
370 * @param number the phone number used to lookup caller id
371 * @return the CallerInfo which contains the caller id for the given
372 * number. The returned CallerInfo is null if no number is supplied. If
373 * a matching number is not found, then a generic caller info is returned,
374 * with all relevant fields empty or null.
Chen Xufba9ca42019-09-07 18:56:17 -0700375 *
376 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 */
Mathew Inwood5d123b62020-11-04 09:29:36 +0000378 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 public static CallerInfo getCallerInfo(Context context, String number) {
Hall Liud2f962a2019-10-31 15:17:58 -0700380 if (VDBG) Log.v(TAG, "getCallerInfo() based on number...");
David Brown94202fe2011-06-10 16:24:05 -0700381
Shishir Agrawal7ea3e8b2016-01-25 13:03:07 -0800382 int subId = SubscriptionManager.getDefaultSubscriptionId();
Wink Savillefb40dd42014-06-12 17:02:31 -0700383 return getCallerInfo(context, number, subId);
384 }
385
386 /**
387 * getCallerInfo given a phone number and subscription, look up in the call-log database
388 * for the matching caller id info.
389 * @param context the context used to get the ContentResolver
390 * @param number the phone number used to lookup caller id
391 * @param subId the subscription for checking for if voice mail number or not
392 * @return the CallerInfo which contains the caller id for the given
393 * number. The returned CallerInfo is null if no number is supplied. If
394 * a matching number is not found, then a generic caller info is returned,
395 * with all relevant fields empty or null.
Chen Xufba9ca42019-09-07 18:56:17 -0700396 *
397 * @hide
Wink Savillefb40dd42014-06-12 17:02:31 -0700398 */
Mathew Inwood5d123b62020-11-04 09:29:36 +0000399 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Wink Saville63f03dd2014-10-23 10:44:45 -0700400 public static CallerInfo getCallerInfo(Context context, String number, int subId) {
Wink Savillefb40dd42014-06-12 17:02:31 -0700401
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 if (TextUtils.isEmpty(number)) {
403 return null;
Nicolas Catania60d45f02009-09-15 18:32:02 -0700404 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405
Nicolas Catania60d45f02009-09-15 18:32:02 -0700406 // Change the callerInfo number ONLY if it is an emergency number
407 // or if it is the voicemail number. If it is either, take a
408 // shortcut and skip the query.
Shuo Qian68417632021-05-10 22:54:23 +0000409 if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700410 return new CallerInfo().markAsEmergency(context);
Hall Liud2f962a2019-10-31 15:17:58 -0700411 } else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
412 return new CallerInfo().markAsVoiceMail(context, subId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 }
414
Makoto Onukia2295e62014-07-10 15:32:16 -0700415 Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
416 Uri.encode(number));
Wink Saville2563a3a2009-06-09 10:30:03 -0700417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 CallerInfo info = getCallerInfo(context, contactUri);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800419 info = doSecondaryLookupIfNecessary(context, number, info);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420
Wink Saville2563a3a2009-06-09 10:30:03 -0700421 // if no query results were returned with a viable number,
422 // fill in the original number value we used to query with.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 if (TextUtils.isEmpty(info.phoneNumber)) {
424 info.phoneNumber = number;
425 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 return info;
428 }
429
430 /**
Chen Xufba9ca42019-09-07 18:56:17 -0700431 * @return Name assocaited with this caller.
432 */
433 @Nullable
434 public String getName() {
435 return name;
436 }
437
438 /**
439 * Set caller Info Name.
440 * @param name caller Info Name
441 *
442 * @hide
443 */
444 public void setName(@Nullable String name) {
445 this.name = name;
446 }
447
448 /**
449 * @return Phone number assocaited with this caller.
450 */
451 @Nullable
452 public String getPhoneNumber() {
453 return phoneNumber;
454 }
455
456 /** @hide */
457 public void setPhoneNumber(String number) {
458 phoneNumber = number;
459 }
460
461 /**
462 * @return Contact ID, which will be 0 if a contact comes from the corp Contacts Provider.
463 */
464 public long getContactId() {
465 return contactIdOrZero;
466 }
467
468 /**
469 * @return Contact display photo URI. If a contact has no display photo but a thumbnail,
470 * it'll the thumbnail URI instead.
471 */
472 @Nullable
473 public Uri getContactDisplayPhotoUri() {
474 return contactDisplayPhotoUri;
475 }
476
477 /** @hide */
478 @VisibleForTesting
479 public void SetContactDisplayPhotoUri(Uri photoUri) {
480 contactDisplayPhotoUri = photoUri;
481 }
482
483 /**
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800484 * Performs another lookup if previous lookup fails and it's a SIP call
485 * and the peer's username is all numeric. Look up the username as it
486 * could be a PSTN number in the contact database.
487 *
488 * @param context the query context
489 * @param number the original phone number, could be a SIP URI
490 * @param previousResult the result of previous lookup
491 * @return previousResult if it's not the case
492 */
493 static CallerInfo doSecondaryLookupIfNecessary(Context context,
494 String number, CallerInfo previousResult) {
495 if (!previousResult.contactExists
496 && PhoneNumberUtils.isUriNumber(number)) {
David Brown158f1162011-11-16 22:10:56 -0800497 String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800498 if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
499 previousResult = getCallerInfo(context,
Makoto Onukia2295e62014-07-10 15:32:16 -0700500 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800501 Uri.encode(username)));
502 }
503 }
504 return previousResult;
505 }
506
Nicolas Cataniae2241582009-09-14 19:01:43 -0700507 // Accessors
508
509 /**
510 * @return true if the caller info is an emergency number.
Chen Xufba9ca42019-09-07 18:56:17 -0700511 * @hide
Nicolas Cataniae2241582009-09-14 19:01:43 -0700512 */
513 public boolean isEmergencyNumber() {
514 return mIsEmergency;
515 }
516
517 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700518 * @return true if the caller info is a voicemail number.
Chen Xufba9ca42019-09-07 18:56:17 -0700519 * @hide
Nicolas Catania60d45f02009-09-15 18:32:02 -0700520 */
521 public boolean isVoiceMailNumber() {
522 return mIsVoiceMail;
523 }
524
525 /**
Nicolas Cataniae2241582009-09-14 19:01:43 -0700526 * Mark this CallerInfo as an emergency call.
527 * @param context To lookup the localized 'Emergency Number' string.
528 * @return this instance.
529 */
530 // TODO: Note we're setting the phone number here (refer to
531 // javadoc comments at the top of CallerInfo class) to a localized
532 // string 'Emergency Number'. This is pretty bad because we are
533 // making UI work here instead of just packaging the data. We
534 // should set the phone number to the dialed number and name to
535 // 'Emergency Number' and let the UI make the decision about what
536 // should be displayed.
537 /* package */ CallerInfo markAsEmergency(Context context) {
538 phoneNumber = context.getString(
539 com.android.internal.R.string.emergency_call_dialog_number_for_display);
540 photoResource = com.android.internal.R.drawable.picture_emergency;
541 mIsEmergency = true;
542 return this;
543 }
544
Nicolas Catania60d45f02009-09-15 18:32:02 -0700545
Hall Liud2f962a2019-10-31 15:17:58 -0700546 /* package */ CallerInfo markAsVoiceMail(Context context, int subId) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700547 mIsVoiceMail = true;
548
549 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700550 phoneNumber = context.getSystemService(TelephonyManager.class)
551 .createForSubscriptionId(subId)
552 .getVoiceMailAlphaTag();
Nicolas Catania60d45f02009-09-15 18:32:02 -0700553 } catch (SecurityException se) {
554 // Should never happen: if this process does not have
555 // permission to retrieve VM tag, it should not have
556 // permission to retrieve VM number and would not call
557 // this method.
558 // Leave phoneNumber untouched.
Hall Liud2f962a2019-10-31 15:17:58 -0700559 Log.e(TAG, se, "Cannot access VoiceMail.");
Nicolas Catania60d45f02009-09-15 18:32:02 -0700560 }
561 // TODO: There is no voicemail picture?
562 // FIXME: FIND ANOTHER ICON
563 // photoResource = android.R.drawable.badge_voicemail;
564 return this;
565 }
566
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 private static String normalize(String s) {
568 if (s == null || s.length() > 0) {
569 return s;
570 } else {
571 return null;
572 }
573 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700574
575 /**
David Brown85e0ff82010-10-22 12:54:42 -0700576 * Returns the column index to use to find the "person_id" field in
577 * the specified cursor, based on the contact URI that was originally
578 * queried.
579 *
580 * This is a helper function for the getCallerInfo() method that takes
581 * a Cursor. Looking up the person_id is nontrivial (compared to all
582 * the other CallerInfo fields) since the column we need to use
583 * depends on what query we originally ran.
584 *
585 * Watch out: be sure to not do any database access in this method, since
586 * it's run from the UI thread (see comments below for more info.)
587 *
588 * @return the columnIndex to use (with cursor.getLong()) to get the
589 * person_id, or -1 if we couldn't figure out what colum to use.
590 *
591 * TODO: Add a unittest for this method. (This is a little tricky to
592 * test, since we'll need a live contacts database to test against,
593 * preloaded with at least some phone numbers and SIP addresses. And
594 * we'll probably have to hardcode the column indexes we expect, so
595 * the test might break whenever the contacts schema changes. But we
596 * can at least make sure we handle all the URI patterns we claim to,
597 * and that the mime types match what we expect...)
598 */
599 private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
600 // TODO: This is pretty ugly now, see bug 2269240 for
601 // more details. The column to use depends upon the type of URL:
602 // - content://com.android.contacts/data/phones ==> use the "contact_id" column
603 // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
604 // - content://com.android.contacts/data ==> use the "contact_id" column
605 // If it's none of the above, we leave columnIndex=-1 which means
606 // that the person_id field will be left unset.
607 //
608 // The logic here *used* to be based on the mime type of contactRef
609 // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
610 // RawContacts.CONTACT_ID column). But looking up the mime type requires
611 // a call to context.getContentResolver().getType(contactRef), which
612 // isn't safe to do from the UI thread since it can cause an ANR if
613 // the contacts provider is slow or blocked (like during a sync.)
614 //
615 // So instead, figure out the column to use for person_id by just
616 // looking at the URI itself.
617
Hall Liud2f962a2019-10-31 15:17:58 -0700618 if (VDBG) Log.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
David Brown85e0ff82010-10-22 12:54:42 -0700619 + contactRef + "'...");
620 // Warning: Do not enable the following logging (due to ANR risk.)
Hall Liud2f962a2019-10-31 15:17:58 -0700621 // if (VDBG) Log.v(TAG, "- MIME type: "
David Brown85e0ff82010-10-22 12:54:42 -0700622 // + context.getContentResolver().getType(contactRef));
623
624 String url = contactRef.toString();
625 String columnName = null;
626 if (url.startsWith("content://com.android.contacts/data/phones")) {
627 // Direct lookup in the Phone table.
628 // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
Hall Liud2f962a2019-10-31 15:17:58 -0700629 if (VDBG) Log.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700630 columnName = RawContacts.CONTACT_ID;
631 } else if (url.startsWith("content://com.android.contacts/data")) {
632 // Direct lookup in the Data table.
633 // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
Hall Liud2f962a2019-10-31 15:17:58 -0700634 if (VDBG) Log.v(TAG, "'data' URI; using Data.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700635 // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
636 columnName = Data.CONTACT_ID;
637 } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
638 // Lookup in the PhoneLookup table, which provides "fuzzy matching"
639 // for phone numbers.
640 // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
Hall Liud2f962a2019-10-31 15:17:58 -0700641 if (VDBG) Log.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
David Brown85e0ff82010-10-22 12:54:42 -0700642 columnName = PhoneLookup._ID;
643 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700644 Log.w(TAG, "Unexpected prefix for contactRef '" + url + "'");
David Brown85e0ff82010-10-22 12:54:42 -0700645 }
646 int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
Hall Liud2f962a2019-10-31 15:17:58 -0700647 if (VDBG) Log.v(TAG, "==> Using column '" + columnName
David Brown85e0ff82010-10-22 12:54:42 -0700648 + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
649 return columnIndex;
650 }
651
652 /**
David Brown94202fe2011-06-10 16:24:05 -0700653 * Updates this CallerInfo's geoDescription field, based on the raw
654 * phone number in the phoneNumber field.
655 *
656 * (Note that the various getCallerInfo() methods do *not* set the
657 * geoDescription automatically; you need to call this method
658 * explicitly to get it.)
659 *
660 * @param context the context used to look up the current locale / country
661 * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
662 * this specifies a fallback number to use instead.
Chen Xufba9ca42019-09-07 18:56:17 -0700663 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700664 */
665 public void updateGeoDescription(Context context, String fallbackNumber) {
666 String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
667 geoDescription = getGeoDescription(context, number);
668 }
669
670 /**
671 * @return a geographical description string for the specified number.
Shaopeng Jiae7135762011-08-12 13:25:41 +0200672 * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
Chen Xufba9ca42019-09-07 18:56:17 -0700673 *
674 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700675 */
Ceci Wu3b90d482016-08-25 14:48:19 +0800676 public static String getGeoDescription(Context context, String number) {
Hall Liud2f962a2019-10-31 15:17:58 -0700677 if (VDBG) Log.v(TAG, "getGeoDescription('" + number + "')...");
David Brown94202fe2011-06-10 16:24:05 -0700678
679 if (TextUtils.isEmpty(number)) {
680 return null;
681 }
682
683 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
684 PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
685
David Brown94202fe2011-06-10 16:24:05 -0700686 Locale locale = context.getResources().getConfiguration().locale;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200687 String countryIso = getCurrentCountryIso(context, locale);
David Brown94202fe2011-06-10 16:24:05 -0700688 PhoneNumber pn = null;
689 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700690 if (VDBG) Log.v(TAG, "parsing '" + number
David Browncec25c42011-06-23 14:17:27 -0700691 + "' for countryIso '" + countryIso + "'...");
David Brown94202fe2011-06-10 16:24:05 -0700692 pn = util.parse(number, countryIso);
Hall Liud2f962a2019-10-31 15:17:58 -0700693 if (VDBG) Log.v(TAG, "- parsed number: " + pn);
David Brown94202fe2011-06-10 16:24:05 -0700694 } catch (NumberParseException e) {
Hall Liud2f962a2019-10-31 15:17:58 -0700695 Log.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
696 + Log.pii(number) + "'");
David Brown94202fe2011-06-10 16:24:05 -0700697 }
698
699 if (pn != null) {
700 String description = geocoder.getDescriptionForNumber(pn, locale);
Hall Liud2f962a2019-10-31 15:17:58 -0700701 if (VDBG) Log.v(TAG, "- got description: '" + description + "'");
David Brown94202fe2011-06-10 16:24:05 -0700702 return description;
703 } else {
704 return null;
705 }
706 }
707
708 /**
Shaopeng Jia9683f992011-09-07 14:07:15 +0200709 * @return The ISO 3166-1 two letters country code of the country the user
710 * is in.
711 */
712 private static String getCurrentCountryIso(Context context, Locale locale) {
Jake Hamby7f5bee02013-10-08 16:31:16 -0700713 String countryIso = null;
714 CountryDetector detector = (CountryDetector) context.getSystemService(
715 Context.COUNTRY_DETECTOR);
716 if (detector != null) {
717 Country country = detector.detectCountry();
718 if (country != null) {
719 countryIso = country.getCountryIso();
720 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700721 Log.e(TAG, new Exception(), "CountryDetector.detectCountry() returned null.");
Jake Hamby7f5bee02013-10-08 16:31:16 -0700722 }
723 }
724 if (countryIso == null) {
725 countryIso = locale.getCountry();
Hall Liud2f962a2019-10-31 15:17:58 -0700726 Log.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
Jake Hamby7f5bee02013-10-08 16:31:16 -0700727 + countryIso);
728 }
729 return countryIso;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200730 }
731
Chen Xufba9ca42019-09-07 18:56:17 -0700732 /** @hide */
Jay Shraunera2c93482013-10-21 11:54:19 -0700733 protected static String getCurrentCountryIso(Context context) {
734 return getCurrentCountryIso(context, Locale.getDefault());
735 }
736
Shaopeng Jia9683f992011-09-07 14:07:15 +0200737 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700738 * @return a string debug representation of this instance.
739 */
Victor Chang9359ee12016-01-04 15:48:09 +0000740 @Override
Nicolas Catania60d45f02009-09-15 18:32:02 -0700741 public String toString() {
David Brown04639ba2010-11-30 15:31:15 -0800742 // Warning: never check in this file with VERBOSE_DEBUG = true
743 // because that will result in PII in the system log.
744 final boolean VERBOSE_DEBUG = false;
745
746 if (VERBOSE_DEBUG) {
747 return new StringBuilder(384)
David Brown94202fe2011-06-10 16:24:05 -0700748 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800749 .append("\nname: " + name)
750 .append("\nphoneNumber: " + phoneNumber)
David Brown94202fe2011-06-10 16:24:05 -0700751 .append("\nnormalizedNumber: " + normalizedNumber)
752 .append("\ngeoDescription: " + geoDescription)
David Brown04639ba2010-11-30 15:31:15 -0800753 .append("\ncnapName: " + cnapName)
754 .append("\nnumberPresentation: " + numberPresentation)
755 .append("\nnamePresentation: " + namePresentation)
756 .append("\ncontactExits: " + contactExists)
757 .append("\nphoneLabel: " + phoneLabel)
758 .append("\nnumberType: " + numberType)
759 .append("\nnumberLabel: " + numberLabel)
760 .append("\nphotoResource: " + photoResource)
Makoto Onukia2295e62014-07-10 15:32:16 -0700761 .append("\ncontactIdOrZero: " + contactIdOrZero)
David Brown04639ba2010-11-30 15:31:15 -0800762 .append("\nneedUpdate: " + needUpdate)
Makoto Onukia2295e62014-07-10 15:32:16 -0700763 .append("\ncontactRingtoneUri: " + contactRingtoneUri)
764 .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
David Brown04639ba2010-11-30 15:31:15 -0800765 .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
766 .append("\ncachedPhoto: " + cachedPhoto)
767 .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
768 .append("\nemergency: " + mIsEmergency)
769 .append("\nvoicemail " + mIsVoiceMail)
770 .append("\ncontactExists " + contactExists)
Victor Chang9359ee12016-01-04 15:48:09 +0000771 .append("\nuserType " + userType)
David Brown94202fe2011-06-10 16:24:05 -0700772 .append(" }")
David Brown04639ba2010-11-30 15:31:15 -0800773 .toString();
774 } else {
775 return new StringBuilder(128)
David Brown94202fe2011-06-10 16:24:05 -0700776 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800777 .append("name " + ((name == null) ? "null" : "non-null"))
778 .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
779 .append(" }")
780 .toString();
781 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700782 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700783}