blob: 2983e6339d4bbbf45696ec6b4eafd6ee3868ee44 [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 Satayev2ebb31c2020-01-08 12:24:36 +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 Inwood8e742f92020-10-27 11:47:29 +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 Satayev2ebb31c2020-01-08 12:24:36 +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 Inwood8e742f92020-10-27 11:47:29 +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 Inwood8e742f92020-10-27 11:47:29 +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 Inwood8e742f92020-10-27 11:47:29 +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 Inwood8e742f92020-10-27 11:47:29 +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.
Taesu Lee902b89d2020-10-07 14:55:25 +0900409 TelephonyManager tm = context.getSystemService(TelephonyManager.class);
410 if (tm.isEmergencyNumber(number)) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700411 return new CallerInfo().markAsEmergency(context);
Hall Liud2f962a2019-10-31 15:17:58 -0700412 } else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
413 return new CallerInfo().markAsVoiceMail(context, subId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 }
415
Makoto Onukia2295e62014-07-10 15:32:16 -0700416 Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
417 Uri.encode(number));
Wink Saville2563a3a2009-06-09 10:30:03 -0700418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 CallerInfo info = getCallerInfo(context, contactUri);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800420 info = doSecondaryLookupIfNecessary(context, number, info);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421
Wink Saville2563a3a2009-06-09 10:30:03 -0700422 // if no query results were returned with a viable number,
423 // fill in the original number value we used to query with.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 if (TextUtils.isEmpty(info.phoneNumber)) {
425 info.phoneNumber = number;
426 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 return info;
429 }
430
431 /**
Chen Xufba9ca42019-09-07 18:56:17 -0700432 * @return Name assocaited with this caller.
433 */
434 @Nullable
435 public String getName() {
436 return name;
437 }
438
439 /**
440 * Set caller Info Name.
441 * @param name caller Info Name
442 *
443 * @hide
444 */
445 public void setName(@Nullable String name) {
446 this.name = name;
447 }
448
449 /**
450 * @return Phone number assocaited with this caller.
451 */
452 @Nullable
453 public String getPhoneNumber() {
454 return phoneNumber;
455 }
456
457 /** @hide */
458 public void setPhoneNumber(String number) {
459 phoneNumber = number;
460 }
461
462 /**
463 * @return Contact ID, which will be 0 if a contact comes from the corp Contacts Provider.
464 */
465 public long getContactId() {
466 return contactIdOrZero;
467 }
468
469 /**
470 * @return Contact display photo URI. If a contact has no display photo but a thumbnail,
471 * it'll the thumbnail URI instead.
472 */
473 @Nullable
474 public Uri getContactDisplayPhotoUri() {
475 return contactDisplayPhotoUri;
476 }
477
478 /** @hide */
479 @VisibleForTesting
480 public void SetContactDisplayPhotoUri(Uri photoUri) {
481 contactDisplayPhotoUri = photoUri;
482 }
483
484 /**
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800485 * Performs another lookup if previous lookup fails and it's a SIP call
486 * and the peer's username is all numeric. Look up the username as it
487 * could be a PSTN number in the contact database.
488 *
489 * @param context the query context
490 * @param number the original phone number, could be a SIP URI
491 * @param previousResult the result of previous lookup
492 * @return previousResult if it's not the case
493 */
494 static CallerInfo doSecondaryLookupIfNecessary(Context context,
495 String number, CallerInfo previousResult) {
496 if (!previousResult.contactExists
497 && PhoneNumberUtils.isUriNumber(number)) {
David Brown158f1162011-11-16 22:10:56 -0800498 String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800499 if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
500 previousResult = getCallerInfo(context,
Makoto Onukia2295e62014-07-10 15:32:16 -0700501 Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
Hung-ying Tyan6fe795e2010-10-20 11:12:02 +0800502 Uri.encode(username)));
503 }
504 }
505 return previousResult;
506 }
507
Nicolas Cataniae2241582009-09-14 19:01:43 -0700508 // Accessors
509
510 /**
511 * @return true if the caller info is an emergency number.
Chen Xufba9ca42019-09-07 18:56:17 -0700512 * @hide
Nicolas Cataniae2241582009-09-14 19:01:43 -0700513 */
514 public boolean isEmergencyNumber() {
515 return mIsEmergency;
516 }
517
518 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700519 * @return true if the caller info is a voicemail number.
Chen Xufba9ca42019-09-07 18:56:17 -0700520 * @hide
Nicolas Catania60d45f02009-09-15 18:32:02 -0700521 */
522 public boolean isVoiceMailNumber() {
523 return mIsVoiceMail;
524 }
525
526 /**
Nicolas Cataniae2241582009-09-14 19:01:43 -0700527 * Mark this CallerInfo as an emergency call.
528 * @param context To lookup the localized 'Emergency Number' string.
529 * @return this instance.
530 */
531 // TODO: Note we're setting the phone number here (refer to
532 // javadoc comments at the top of CallerInfo class) to a localized
533 // string 'Emergency Number'. This is pretty bad because we are
534 // making UI work here instead of just packaging the data. We
535 // should set the phone number to the dialed number and name to
536 // 'Emergency Number' and let the UI make the decision about what
537 // should be displayed.
538 /* package */ CallerInfo markAsEmergency(Context context) {
539 phoneNumber = context.getString(
540 com.android.internal.R.string.emergency_call_dialog_number_for_display);
541 photoResource = com.android.internal.R.drawable.picture_emergency;
542 mIsEmergency = true;
543 return this;
544 }
545
Nicolas Catania60d45f02009-09-15 18:32:02 -0700546
Hall Liud2f962a2019-10-31 15:17:58 -0700547 /* package */ CallerInfo markAsVoiceMail(Context context, int subId) {
Nicolas Catania60d45f02009-09-15 18:32:02 -0700548 mIsVoiceMail = true;
549
550 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700551 phoneNumber = context.getSystemService(TelephonyManager.class)
552 .createForSubscriptionId(subId)
553 .getVoiceMailAlphaTag();
Nicolas Catania60d45f02009-09-15 18:32:02 -0700554 } catch (SecurityException se) {
555 // Should never happen: if this process does not have
556 // permission to retrieve VM tag, it should not have
557 // permission to retrieve VM number and would not call
558 // this method.
559 // Leave phoneNumber untouched.
Hall Liud2f962a2019-10-31 15:17:58 -0700560 Log.e(TAG, se, "Cannot access VoiceMail.");
Nicolas Catania60d45f02009-09-15 18:32:02 -0700561 }
562 // TODO: There is no voicemail picture?
563 // FIXME: FIND ANOTHER ICON
564 // photoResource = android.R.drawable.badge_voicemail;
565 return this;
566 }
567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 private static String normalize(String s) {
569 if (s == null || s.length() > 0) {
570 return s;
571 } else {
572 return null;
573 }
574 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700575
576 /**
David Brown85e0ff82010-10-22 12:54:42 -0700577 * Returns the column index to use to find the "person_id" field in
578 * the specified cursor, based on the contact URI that was originally
579 * queried.
580 *
581 * This is a helper function for the getCallerInfo() method that takes
582 * a Cursor. Looking up the person_id is nontrivial (compared to all
583 * the other CallerInfo fields) since the column we need to use
584 * depends on what query we originally ran.
585 *
586 * Watch out: be sure to not do any database access in this method, since
587 * it's run from the UI thread (see comments below for more info.)
588 *
589 * @return the columnIndex to use (with cursor.getLong()) to get the
590 * person_id, or -1 if we couldn't figure out what colum to use.
591 *
592 * TODO: Add a unittest for this method. (This is a little tricky to
593 * test, since we'll need a live contacts database to test against,
594 * preloaded with at least some phone numbers and SIP addresses. And
595 * we'll probably have to hardcode the column indexes we expect, so
596 * the test might break whenever the contacts schema changes. But we
597 * can at least make sure we handle all the URI patterns we claim to,
598 * and that the mime types match what we expect...)
599 */
600 private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
601 // TODO: This is pretty ugly now, see bug 2269240 for
602 // more details. The column to use depends upon the type of URL:
603 // - content://com.android.contacts/data/phones ==> use the "contact_id" column
604 // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
605 // - content://com.android.contacts/data ==> use the "contact_id" column
606 // If it's none of the above, we leave columnIndex=-1 which means
607 // that the person_id field will be left unset.
608 //
609 // The logic here *used* to be based on the mime type of contactRef
610 // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
611 // RawContacts.CONTACT_ID column). But looking up the mime type requires
612 // a call to context.getContentResolver().getType(contactRef), which
613 // isn't safe to do from the UI thread since it can cause an ANR if
614 // the contacts provider is slow or blocked (like during a sync.)
615 //
616 // So instead, figure out the column to use for person_id by just
617 // looking at the URI itself.
618
Hall Liud2f962a2019-10-31 15:17:58 -0700619 if (VDBG) Log.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
David Brown85e0ff82010-10-22 12:54:42 -0700620 + contactRef + "'...");
621 // Warning: Do not enable the following logging (due to ANR risk.)
Hall Liud2f962a2019-10-31 15:17:58 -0700622 // if (VDBG) Log.v(TAG, "- MIME type: "
David Brown85e0ff82010-10-22 12:54:42 -0700623 // + context.getContentResolver().getType(contactRef));
624
625 String url = contactRef.toString();
626 String columnName = null;
627 if (url.startsWith("content://com.android.contacts/data/phones")) {
628 // Direct lookup in the Phone table.
629 // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
Hall Liud2f962a2019-10-31 15:17:58 -0700630 if (VDBG) Log.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700631 columnName = RawContacts.CONTACT_ID;
632 } else if (url.startsWith("content://com.android.contacts/data")) {
633 // Direct lookup in the Data table.
634 // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
Hall Liud2f962a2019-10-31 15:17:58 -0700635 if (VDBG) Log.v(TAG, "'data' URI; using Data.CONTACT_ID");
David Brown85e0ff82010-10-22 12:54:42 -0700636 // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
637 columnName = Data.CONTACT_ID;
638 } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
639 // Lookup in the PhoneLookup table, which provides "fuzzy matching"
640 // for phone numbers.
641 // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
Hall Liud2f962a2019-10-31 15:17:58 -0700642 if (VDBG) Log.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
David Brown85e0ff82010-10-22 12:54:42 -0700643 columnName = PhoneLookup._ID;
644 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700645 Log.w(TAG, "Unexpected prefix for contactRef '" + url + "'");
David Brown85e0ff82010-10-22 12:54:42 -0700646 }
647 int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
Hall Liud2f962a2019-10-31 15:17:58 -0700648 if (VDBG) Log.v(TAG, "==> Using column '" + columnName
David Brown85e0ff82010-10-22 12:54:42 -0700649 + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
650 return columnIndex;
651 }
652
653 /**
David Brown94202fe2011-06-10 16:24:05 -0700654 * Updates this CallerInfo's geoDescription field, based on the raw
655 * phone number in the phoneNumber field.
656 *
657 * (Note that the various getCallerInfo() methods do *not* set the
658 * geoDescription automatically; you need to call this method
659 * explicitly to get it.)
660 *
661 * @param context the context used to look up the current locale / country
662 * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
663 * this specifies a fallback number to use instead.
Chen Xufba9ca42019-09-07 18:56:17 -0700664 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700665 */
666 public void updateGeoDescription(Context context, String fallbackNumber) {
667 String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
668 geoDescription = getGeoDescription(context, number);
669 }
670
671 /**
672 * @return a geographical description string for the specified number.
Shaopeng Jiae7135762011-08-12 13:25:41 +0200673 * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
Chen Xufba9ca42019-09-07 18:56:17 -0700674 *
675 * @hide
David Brown94202fe2011-06-10 16:24:05 -0700676 */
Ceci Wu3b90d482016-08-25 14:48:19 +0800677 public static String getGeoDescription(Context context, String number) {
Hall Liud2f962a2019-10-31 15:17:58 -0700678 if (VDBG) Log.v(TAG, "getGeoDescription('" + number + "')...");
David Brown94202fe2011-06-10 16:24:05 -0700679
680 if (TextUtils.isEmpty(number)) {
681 return null;
682 }
683
684 PhoneNumberUtil util = PhoneNumberUtil.getInstance();
685 PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
686
David Brown94202fe2011-06-10 16:24:05 -0700687 Locale locale = context.getResources().getConfiguration().locale;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200688 String countryIso = getCurrentCountryIso(context, locale);
David Brown94202fe2011-06-10 16:24:05 -0700689 PhoneNumber pn = null;
690 try {
Hall Liud2f962a2019-10-31 15:17:58 -0700691 if (VDBG) Log.v(TAG, "parsing '" + number
David Browncec25c42011-06-23 14:17:27 -0700692 + "' for countryIso '" + countryIso + "'...");
David Brown94202fe2011-06-10 16:24:05 -0700693 pn = util.parse(number, countryIso);
Hall Liud2f962a2019-10-31 15:17:58 -0700694 if (VDBG) Log.v(TAG, "- parsed number: " + pn);
David Brown94202fe2011-06-10 16:24:05 -0700695 } catch (NumberParseException e) {
Hall Liud2f962a2019-10-31 15:17:58 -0700696 Log.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
697 + Log.pii(number) + "'");
David Brown94202fe2011-06-10 16:24:05 -0700698 }
699
700 if (pn != null) {
701 String description = geocoder.getDescriptionForNumber(pn, locale);
Hall Liud2f962a2019-10-31 15:17:58 -0700702 if (VDBG) Log.v(TAG, "- got description: '" + description + "'");
David Brown94202fe2011-06-10 16:24:05 -0700703 return description;
704 } else {
705 return null;
706 }
707 }
708
709 /**
Shaopeng Jia9683f992011-09-07 14:07:15 +0200710 * @return The ISO 3166-1 two letters country code of the country the user
711 * is in.
712 */
713 private static String getCurrentCountryIso(Context context, Locale locale) {
Jake Hamby7f5bee02013-10-08 16:31:16 -0700714 String countryIso = null;
715 CountryDetector detector = (CountryDetector) context.getSystemService(
716 Context.COUNTRY_DETECTOR);
717 if (detector != null) {
718 Country country = detector.detectCountry();
719 if (country != null) {
720 countryIso = country.getCountryIso();
721 } else {
Hall Liud2f962a2019-10-31 15:17:58 -0700722 Log.e(TAG, new Exception(), "CountryDetector.detectCountry() returned null.");
Jake Hamby7f5bee02013-10-08 16:31:16 -0700723 }
724 }
725 if (countryIso == null) {
726 countryIso = locale.getCountry();
Hall Liud2f962a2019-10-31 15:17:58 -0700727 Log.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
Jake Hamby7f5bee02013-10-08 16:31:16 -0700728 + countryIso);
729 }
730 return countryIso;
Shaopeng Jia9683f992011-09-07 14:07:15 +0200731 }
732
Chen Xufba9ca42019-09-07 18:56:17 -0700733 /** @hide */
Jay Shraunera2c93482013-10-21 11:54:19 -0700734 protected static String getCurrentCountryIso(Context context) {
735 return getCurrentCountryIso(context, Locale.getDefault());
736 }
737
Shaopeng Jia9683f992011-09-07 14:07:15 +0200738 /**
Nicolas Catania60d45f02009-09-15 18:32:02 -0700739 * @return a string debug representation of this instance.
740 */
Victor Chang9359ee12016-01-04 15:48:09 +0000741 @Override
Nicolas Catania60d45f02009-09-15 18:32:02 -0700742 public String toString() {
David Brown04639ba2010-11-30 15:31:15 -0800743 // Warning: never check in this file with VERBOSE_DEBUG = true
744 // because that will result in PII in the system log.
745 final boolean VERBOSE_DEBUG = false;
746
747 if (VERBOSE_DEBUG) {
748 return new StringBuilder(384)
David Brown94202fe2011-06-10 16:24:05 -0700749 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800750 .append("\nname: " + name)
751 .append("\nphoneNumber: " + phoneNumber)
David Brown94202fe2011-06-10 16:24:05 -0700752 .append("\nnormalizedNumber: " + normalizedNumber)
753 .append("\ngeoDescription: " + geoDescription)
David Brown04639ba2010-11-30 15:31:15 -0800754 .append("\ncnapName: " + cnapName)
755 .append("\nnumberPresentation: " + numberPresentation)
756 .append("\nnamePresentation: " + namePresentation)
757 .append("\ncontactExits: " + contactExists)
758 .append("\nphoneLabel: " + phoneLabel)
759 .append("\nnumberType: " + numberType)
760 .append("\nnumberLabel: " + numberLabel)
761 .append("\nphotoResource: " + photoResource)
Makoto Onukia2295e62014-07-10 15:32:16 -0700762 .append("\ncontactIdOrZero: " + contactIdOrZero)
David Brown04639ba2010-11-30 15:31:15 -0800763 .append("\nneedUpdate: " + needUpdate)
Makoto Onukia2295e62014-07-10 15:32:16 -0700764 .append("\ncontactRingtoneUri: " + contactRingtoneUri)
765 .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
David Brown04639ba2010-11-30 15:31:15 -0800766 .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
767 .append("\ncachedPhoto: " + cachedPhoto)
768 .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
769 .append("\nemergency: " + mIsEmergency)
770 .append("\nvoicemail " + mIsVoiceMail)
771 .append("\ncontactExists " + contactExists)
Victor Chang9359ee12016-01-04 15:48:09 +0000772 .append("\nuserType " + userType)
David Brown94202fe2011-06-10 16:24:05 -0700773 .append(" }")
David Brown04639ba2010-11-30 15:31:15 -0800774 .toString();
775 } else {
776 return new StringBuilder(128)
David Brown94202fe2011-06-10 16:24:05 -0700777 .append(super.toString() + " { ")
David Brown04639ba2010-11-30 15:31:15 -0800778 .append("name " + ((name == null) ? "null" : "non-null"))
779 .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
780 .append(" }")
781 .toString();
782 }
Nicolas Catania60d45f02009-09-15 18:32:02 -0700783 }
Wink Saville2563a3a2009-06-09 10:30:03 -0700784}