19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage com.android.internal.telephony; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Context; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.Cursor; 21265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawaimport android.graphics.Bitmap; 229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.graphics.drawable.Drawable; 237f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hambyimport android.location.Country; 2494202fe1217b9f63e1f5c314379a9f0021e96ea8David Brownimport android.location.CountryDetector; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.net.Uri; 263c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikovimport android.provider.ContactsContract.CommonDataKinds.Phone; 2785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brownimport android.provider.ContactsContract.Data; 2885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brownimport android.provider.ContactsContract.PhoneLookup; 2985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brownimport android.provider.ContactsContract.RawContacts; 309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.telephony.PhoneNumberUtils; 3185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brownimport android.telephony.TelephonyManager; 3285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brownimport android.text.TextUtils; 33599a90c2a02645a5f2d189b9065b863397a4076eWink Savilleimport android.telephony.Rlog; 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log; 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 36e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; 37e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.NumberParseException; 38e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.PhoneNumberUtil; 39e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; 4094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 4194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brownimport java.util.Locale; 4294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 432e27a0be78bd9510785789caa14baa029051ca20Wink Saville 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Looks up caller information for the given phone number. 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * {@hide} 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class CallerInfo { 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final String TAG = "CallerInfo"; 51599a90c2a02645a5f2d189b9065b863397a4076eWink Saville private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE); 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Please note that, any one of these member variables can be null, 559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * and any accesses to them should be prepared to handle such a case. 569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Also, it is implied that phoneNumber is more often populated than 589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * name is, (think of calls being dialed/received using numbers where 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * names are not known to the device), so phoneNumber should serve as 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * a dependable fallback when name is unavailable. 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * One other detail here is that this CallerInfo object reflects 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * information found on a connection, it is an OUTPUT that serves 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * mainly to display information to the user. In no way is this object 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * used as input to make a connection, so we can choose to display 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * whatever human-readable text makes sense to the user for a 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * connection. This is especially relevant for the phone number field, 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * since it is the one field that is most likely exposed to the user. 699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * As an example: 719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 1. User dials "911" 729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 2. Device recognizes that this is an emergency number 739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 3. We use the "Emergency Number" string instead of "911" in the 749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * phoneNumber field. 759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * What we're really doing here is treating phoneNumber as an essential 779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * field here, NOT name. We're NOT always guaranteed to have a name 789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * for a connection, but the number should be displayable. 799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public String name; 819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public String phoneNumber; 8294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown public String normalizedNumber; 8394202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown public String geoDescription; 84dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville 85dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville public String cnapName; 86dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville public int numberPresentation; 87dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville public int namePresentation; 88dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville public boolean contactExists; 89dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville 909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public String phoneLabel; 919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /* Split up the phoneLabel into number type and label name */ 929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public int numberType; 939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public String numberLabel; 942563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public int photoResource; 969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public long person_id; 979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public boolean needUpdate; 989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public Uri contactRefUri; 992563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 1002563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville // fields to hold individual contact preference data, 1019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // including the send to voicemail flag and the ringtone 1029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // uri reference. 1039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public Uri contactRingtoneUri; 1049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public boolean shouldSendToVoicemail; 1059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Drawable representing the caller image. This is essentially 1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * a cache for the image data tied into the connection / 109265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * callerinfo object. 110265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * 111265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * This might be a high resolution picture which is more suitable 112265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * for full-screen image view than for smaller icons used in some 113265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * kinds of notifications. 114265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * 115265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * The {@link #isCachedPhotoCurrent} flag indicates if the image 116265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * data needs to be reloaded. 1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public Drawable cachedPhoto; 119265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa /** 120265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * Bitmap representing the caller image which has possibly lower 121265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * resolution than {@link #cachedPhoto} and thus more suitable for 122265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * icons (like notification icons). 123265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * 124265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * In usual cases this is just down-scaled image of {@link #cachedPhoto}. 125265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * If the down-scaling fails, this will just become null. 126265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * 127265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * The {@link #isCachedPhotoCurrent} flag indicates if the image 128265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * data needs to be reloaded. 129265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa */ 130265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa public Bitmap cachedPhotoIcon; 131265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa /** 132265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * Boolean which indicates if {@link #cachedPhoto} and 133265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * {@link #cachedPhotoIcon} is fresh enough. If it is false, 134265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa * those images aren't pointing to valid objects. 135265974803a01b6a461a94f984bb6b1e5f1f48496Daisuke Miyakawa */ 1369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public boolean isCachedPhotoCurrent; 1379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 138e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania private boolean mIsEmergency; 13960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania private boolean mIsVoiceMail; 1409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public CallerInfo() { 142e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // TODO: Move all the basic initialization here? 143e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania mIsEmergency = false; 14460d45f0f0320801a16db2ad038453c098e98966cNicolas Catania mIsVoiceMail = false; 1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * getCallerInfo given a Cursor. 1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param context the context used to retrieve string constants 1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param contactRef the URI to attach to this CallerInfo object 1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param cursor the first object in the cursor is used to build the CallerInfo object. 1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return the CallerInfo which contains the caller id for the given 1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * number. The returned CallerInfo is null if no number is supplied. 1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) { 1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project CallerInfo info = new CallerInfo(); 1579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.photoResource = 0; 1589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.phoneLabel = null; 1599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.numberType = 0; 1609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.numberLabel = null; 1619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.cachedPhoto = null; 1629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.isCachedPhotoCurrent = false; 163dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville info.contactExists = false; 1642563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 165599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "getCallerInfo() based on cursor..."); 1662563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (cursor != null) { 1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (cursor.moveToFirst()) { 169c72509b4d815e23bfa563cfe96e04f54f2a221feNicolas Catania // TODO: photo_id is always available but not taken 170c72509b4d815e23bfa563cfe96e04f54f2a221feNicolas Catania // care of here. Maybe we should store it in the 171c72509b4d815e23bfa563cfe96e04f54f2a221feNicolas Catania // CallerInfo object as well. 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int columnIndex; 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Look for the name 1763c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME); 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (columnIndex != -1) { 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.name = cursor.getString(columnIndex); 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Look for the number 1823c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); 1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (columnIndex != -1) { 1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.phoneNumber = cursor.getString(columnIndex); 1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1862563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 1876a3d188f18b5ae278c802c8bbd1e0a44da555cdfBai Tao // Look for the normalized number 1886a3d188f18b5ae278c802c8bbd1e0a44da555cdfBai Tao columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER); 1896a3d188f18b5ae278c802c8bbd1e0a44da555cdfBai Tao if (columnIndex != -1) { 19094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown info.normalizedNumber = cursor.getString(columnIndex); 1916a3d188f18b5ae278c802c8bbd1e0a44da555cdfBai Tao } 1926a3d188f18b5ae278c802c8bbd1e0a44da555cdfBai Tao 1939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Look for the label/type combo 1943c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL); 1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (columnIndex != -1) { 1963c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE); 1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (typeColumnIndex != -1) { 1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.numberType = cursor.getInt(typeColumnIndex); 1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.numberLabel = cursor.getString(columnIndex); 2003c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov info.phoneLabel = Phone.getDisplayLabel(context, 2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.numberType, info.numberLabel) 2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project .toString(); 2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 20685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Look for the person_id. 20785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown columnIndex = getColumnIndexForPersonId(contactRef, cursor); 2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (columnIndex != -1) { 2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.person_id = cursor.getLong(columnIndex); 210599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "==> got info.person_id: " + info.person_id); 211c72509b4d815e23bfa563cfe96e04f54f2a221feNicolas Catania } else { 21285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // No valid columnIndex, so we can't look up person_id. 213599a90c2a02645a5f2d189b9065b863397a4076eWink Saville Rlog.w(TAG, "Couldn't find person_id column for " + contactRef); 21485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Watch out: this means that anything that depends on 21585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // person_id will be broken (like contact photo lookups in 21685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // the in-call UI, for example.) 2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2182563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // look for the custom ringtone, create from the string stored 2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // in the database. 2213c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE); 2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { 2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex)); 2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.contactRingtoneUri = null; 2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // look for the send to voicemail flag, set it to true only 2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // under certain circumstances. 2303c513ed95cee2e0bcd7208cb7e46307f09c907c9Dmitri Plotnikov columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL); 2312563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville info.shouldSendToVoicemail = (columnIndex != -1) && 2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ((cursor.getInt(columnIndex)) == 1); 233dda5391d5079537e275c9f4ed2637a1484d0e4e8Wink Saville info.contactExists = true; 2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project cursor.close(); 2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.needUpdate = false; 2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.name = normalize(info.name); 2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.contactRefUri = contactRef; 2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return info; 2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2442563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * getCallerInfo given a URI, look up in the call-log database 2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * for the uri unique key. 2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param context the context used to get the ContentResolver 2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param contactRef the URI used to lookup caller id 2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return the CallerInfo which contains the caller id for the given 2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * number. The returned CallerInfo is null if no number is supplied. 2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static CallerInfo getCallerInfo(Context context, Uri contactRef) { 2542563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2552563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville return getCallerInfo(context, contactRef, 2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project context.getContentResolver().query(contactRef, null, null, null, null)); 2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2582563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * getCallerInfo given a phone number, look up in the call-log database 2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * for the matching caller id info. 2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param context the context used to get the ContentResolver 2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param number the phone number used to lookup caller id 2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return the CallerInfo which contains the caller id for the given 2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * number. The returned CallerInfo is null if no number is supplied. If 2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * a matching number is not found, then a generic caller info is returned, 2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * with all relevant fields empty or null. 2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static CallerInfo getCallerInfo(Context context, String number) { 270599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "getCallerInfo() based on number..."); 27194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (TextUtils.isEmpty(number)) { 2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return null; 27460d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } 27560d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 27660d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // Change the callerInfo number ONLY if it is an emergency number 27760d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // or if it is the voicemail number. If it is either, take a 27860d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // shortcut and skip the query. 2796b7c3f8a1cd8b638defc28a3249746e99b8039aeShaopeng Jia if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) { 28060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania return new CallerInfo().markAsEmergency(context); 28160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } else if (PhoneNumberUtils.isVoiceMailNumber(number)) { 28260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania return new CallerInfo().markAsVoiceMail(); 2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 28584b4d37dd1e9269f73c2a9cacadcd88ec4256e3fDmitri Plotnikov Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); 2862563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project CallerInfo info = getCallerInfo(context, contactUri); 2886fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan info = doSecondaryLookupIfNecessary(context, number, info); 2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2902563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville // if no query results were returned with a viable number, 2912563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville // fill in the original number value we used to query with. 2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (TextUtils.isEmpty(info.phoneNumber)) { 2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project info.phoneNumber = number; 2949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2952563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville 2969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return info; 2979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3006fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * Performs another lookup if previous lookup fails and it's a SIP call 3016fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * and the peer's username is all numeric. Look up the username as it 3026fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * could be a PSTN number in the contact database. 3036fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * 3046fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * @param context the query context 3056fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * @param number the original phone number, could be a SIP URI 3066fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * @param previousResult the result of previous lookup 3076fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan * @return previousResult if it's not the case 3086fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan */ 3096fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan static CallerInfo doSecondaryLookupIfNecessary(Context context, 3106fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan String number, CallerInfo previousResult) { 3116fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan if (!previousResult.contactExists 3126fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan && PhoneNumberUtils.isUriNumber(number)) { 313158f116eb7fdc23a12d6822d34a549f33605bc8cDavid Brown String username = PhoneNumberUtils.getUsernameFromUriNumber(number); 3146fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan if (PhoneNumberUtils.isGlobalPhoneNumber(username)) { 3156fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan previousResult = getCallerInfo(context, 3166fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, 3176fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan Uri.encode(username))); 3186fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan } 3196fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan } 3206fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan return previousResult; 3216fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan } 3226fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan 3236fe795ecd35c4d49822d349424fc71b660577dfcHung-ying Tyan /** 3249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * getCallerId: a convenience method to get the caller id for a given 3259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * number. 3269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 3279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param context the context used to get the ContentResolver. 3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param number a phone number. 3299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return if the number belongs to a contact, the contact's name is 3309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * returned; otherwise, the number itself is returned. 3312563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville * 3322563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville * TODO NOTE: This MAY need to refer to the Asynchronous Query API 3332563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville * [startQuery()], instead of getCallerInfo, but since it looks like 3349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * it is only being used by the provider calls in the messaging app: 3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 1. android.provider.Telephony.Mms.getDisplayAddress() 3369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 2. android.provider.Telephony.Sms.getDisplayAddress() 3379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * We may not need to make the change. 3389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static String getCallerId(Context context, String number) { 3409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project CallerInfo info = getCallerInfo(context, number); 3419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String callerID = null; 3429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (info != null) { 3449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project String name = info.name; 3459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!TextUtils.isEmpty(name)) { 3479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project callerID = name; 3489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 3499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project callerID = number; 3509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return callerID; 3549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 356e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // Accessors 357e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania 358e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania /** 359e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania * @return true if the caller info is an emergency number. 360e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania */ 361e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania public boolean isEmergencyNumber() { 362e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania return mIsEmergency; 363e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania } 364e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania 365e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania /** 36660d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * @return true if the caller info is a voicemail number. 36760d45f0f0320801a16db2ad038453c098e98966cNicolas Catania */ 36860d45f0f0320801a16db2ad038453c098e98966cNicolas Catania public boolean isVoiceMailNumber() { 36960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania return mIsVoiceMail; 37060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } 37160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 37260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania /** 373e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania * Mark this CallerInfo as an emergency call. 374e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania * @param context To lookup the localized 'Emergency Number' string. 375e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania * @return this instance. 376e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania */ 377e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // TODO: Note we're setting the phone number here (refer to 378e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // javadoc comments at the top of CallerInfo class) to a localized 379e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // string 'Emergency Number'. This is pretty bad because we are 380e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // making UI work here instead of just packaging the data. We 381e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // should set the phone number to the dialed number and name to 382e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // 'Emergency Number' and let the UI make the decision about what 383e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania // should be displayed. 384e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania /* package */ CallerInfo markAsEmergency(Context context) { 385e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania phoneNumber = context.getString( 386e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania com.android.internal.R.string.emergency_call_dialog_number_for_display); 387e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania photoResource = com.android.internal.R.drawable.picture_emergency; 388e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania mIsEmergency = true; 389e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania return this; 390e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania } 391e22415817febc8d3229d1774f3b0dfda0fda8f46Nicolas Catania 39260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 39360d45f0f0320801a16db2ad038453c098e98966cNicolas Catania /** 39460d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * Mark this CallerInfo as a voicemail call. The voicemail label 39560d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * is obtained from the telephony manager. Caller must hold the 39660d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * READ_PHONE_STATE permission otherwise the phoneNumber will be 39760d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * set to null. 39860d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * @return this instance. 39960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania */ 40060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // TODO: As in the emergency number handling, we end up writing a 40160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // string in the phone number field. 40260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania /* package */ CallerInfo markAsVoiceMail() { 40360d45f0f0320801a16db2ad038453c098e98966cNicolas Catania mIsVoiceMail = true; 40460d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 40560d45f0f0320801a16db2ad038453c098e98966cNicolas Catania try { 40660d45f0f0320801a16db2ad038453c098e98966cNicolas Catania String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag(); 40760d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 40860d45f0f0320801a16db2ad038453c098e98966cNicolas Catania phoneNumber = voiceMailLabel; 40960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } catch (SecurityException se) { 41060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // Should never happen: if this process does not have 41160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // permission to retrieve VM tag, it should not have 41260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // permission to retrieve VM number and would not call 41360d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // this method. 41460d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // Leave phoneNumber untouched. 415599a90c2a02645a5f2d189b9065b863397a4076eWink Saville Rlog.e(TAG, "Cannot access VoiceMail.", se); 41660d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } 41760d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // TODO: There is no voicemail picture? 41860d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // FIXME: FIND ANOTHER ICON 41960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania // photoResource = android.R.drawable.badge_voicemail; 42060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania return this; 42160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } 42260d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 4239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static String normalize(String s) { 4249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (s == null || s.length() > 0) { 4259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return s; 4269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 4279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return null; 4289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 43060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania 43160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania /** 43285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * Returns the column index to use to find the "person_id" field in 43385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * the specified cursor, based on the contact URI that was originally 43485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * queried. 43585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * 43685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * This is a helper function for the getCallerInfo() method that takes 43785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * a Cursor. Looking up the person_id is nontrivial (compared to all 43885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * the other CallerInfo fields) since the column we need to use 43985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * depends on what query we originally ran. 44085e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * 44185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * Watch out: be sure to not do any database access in this method, since 44285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * it's run from the UI thread (see comments below for more info.) 44385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * 44485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * @return the columnIndex to use (with cursor.getLong()) to get the 44585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * person_id, or -1 if we couldn't figure out what colum to use. 44685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * 44785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * TODO: Add a unittest for this method. (This is a little tricky to 44885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * test, since we'll need a live contacts database to test against, 44985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * preloaded with at least some phone numbers and SIP addresses. And 45085e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * we'll probably have to hardcode the column indexes we expect, so 45185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * the test might break whenever the contacts schema changes. But we 45285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * can at least make sure we handle all the URI patterns we claim to, 45385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown * and that the mime types match what we expect...) 45485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown */ 45585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) { 45685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // TODO: This is pretty ugly now, see bug 2269240 for 45785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // more details. The column to use depends upon the type of URL: 45885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // - content://com.android.contacts/data/phones ==> use the "contact_id" column 45985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // - content://com.android.contacts/phone_lookup ==> use the "_ID" column 46085e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // - content://com.android.contacts/data ==> use the "contact_id" column 46185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // If it's none of the above, we leave columnIndex=-1 which means 46285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // that the person_id field will be left unset. 46385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // 46485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // The logic here *used* to be based on the mime type of contactRef 46585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the 46685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // RawContacts.CONTACT_ID column). But looking up the mime type requires 46785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // a call to context.getContentResolver().getType(contactRef), which 46885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // isn't safe to do from the UI thread since it can cause an ANR if 46985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // the contacts provider is slow or blocked (like during a sync.) 47085e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // 47185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // So instead, figure out the column to use for person_id by just 47285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // looking at the URI itself. 47385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown 474599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '" 47585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown + contactRef + "'..."); 47685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Warning: Do not enable the following logging (due to ANR risk.) 477599a90c2a02645a5f2d189b9065b863397a4076eWink Saville // if (VDBG) Rlog.v(TAG, "- MIME type: " 47885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // + context.getContentResolver().getType(contactRef)); 47985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown 48085e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown String url = contactRef.toString(); 48185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown String columnName = null; 48285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown if (url.startsWith("content://com.android.contacts/data/phones")) { 48385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Direct lookup in the Phone table. 48485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2") 485599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID"); 48685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown columnName = RawContacts.CONTACT_ID; 48785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown } else if (url.startsWith("content://com.android.contacts/data")) { 48885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Direct lookup in the Data table. 48985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data") 490599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "'data' URI; using Data.CONTACT_ID"); 49185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.) 49285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown columnName = Data.CONTACT_ID; 49385e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown } else if (url.startsWith("content://com.android.contacts/phone_lookup")) { 49485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // Lookup in the PhoneLookup table, which provides "fuzzy matching" 49585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // for phone numbers. 49685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup") 497599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID"); 49885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown columnName = PhoneLookup._ID; 49985e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown } else { 500599a90c2a02645a5f2d189b9065b863397a4076eWink Saville Rlog.w(TAG, "Unexpected prefix for contactRef '" + url + "'"); 50185e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown } 50285e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1; 503599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "==> Using column '" + columnName 50485e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown + "' (columnIndex = " + columnIndex + ") for person_id lookup..."); 50585e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown return columnIndex; 50685e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown } 50785e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown 50885e0ff8f3d6e66b0d943851f478863c7afa71e16David Brown /** 50994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * Updates this CallerInfo's geoDescription field, based on the raw 51094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * phone number in the phoneNumber field. 51194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * 51294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * (Note that the various getCallerInfo() methods do *not* set the 51394202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * geoDescription automatically; you need to call this method 51494202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * explicitly to get it.) 51594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * 51694202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * @param context the context used to look up the current locale / country 51794202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * @param fallbackNumber if this CallerInfo's phoneNumber field is empty, 51894202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * this specifies a fallback number to use instead. 51994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown */ 52094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown public void updateGeoDescription(Context context, String fallbackNumber) { 52194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber; 52294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown geoDescription = getGeoDescription(context, number); 52394202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } 52494202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 52594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown /** 52694202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown * @return a geographical description string for the specified number. 527e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jia * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder 52894202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown */ 52994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown private static String getGeoDescription(Context context, String number) { 530599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "getGeoDescription('" + number + "')..."); 53194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 53294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown if (TextUtils.isEmpty(number)) { 53394202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown return null; 53494202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } 53594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 53694202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown PhoneNumberUtil util = PhoneNumberUtil.getInstance(); 53794202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance(); 53894202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 53994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown Locale locale = context.getResources().getConfiguration().locale; 5409683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia String countryIso = getCurrentCountryIso(context, locale); 54194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown PhoneNumber pn = null; 54294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown try { 543599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "parsing '" + number 544cec25c4e8afdc56451f7405f8605c1d67433e2ffDavid Brown + "' for countryIso '" + countryIso + "'..."); 54594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown pn = util.parse(number, countryIso); 546599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "- parsed number: " + pn); 54794202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } catch (NumberParseException e) { 548599a90c2a02645a5f2d189b9065b863397a4076eWink Saville Rlog.w(TAG, "getGeoDescription: NumberParseException for incoming number '" + number + "'"); 54994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } 55094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 55194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown if (pn != null) { 55294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown String description = geocoder.getDescriptionForNumber(pn, locale); 553599a90c2a02645a5f2d189b9065b863397a4076eWink Saville if (VDBG) Rlog.v(TAG, "- got description: '" + description + "'"); 55494202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown return description; 55594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } else { 55694202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown return null; 55794202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } 55894202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown } 55994202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown 56094202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown /** 5619683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia * @return The ISO 3166-1 two letters country code of the country the user 5629683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia * is in. 5639683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia */ 5649683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia private static String getCurrentCountryIso(Context context, Locale locale) { 5657f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby String countryIso = null; 5667f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby CountryDetector detector = (CountryDetector) context.getSystemService( 5677f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby Context.COUNTRY_DETECTOR); 5687f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby if (detector != null) { 5697f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby Country country = detector.detectCountry(); 5707f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby if (country != null) { 5717f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby countryIso = country.getCountryIso(); 5727f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby } else { 5737f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby Rlog.e(TAG, "CountryDetector.detectCountry() returned null."); 5747f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby } 5757f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby } 5767f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby if (countryIso == null) { 5777f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby countryIso = locale.getCountry(); 5787f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby Rlog.w(TAG, "No CountryDetector; falling back to countryIso based on locale: " 5797f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby + countryIso); 5807f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby } 5817f5bee01ec5d0a01bf63313dfa0798ccdb1eac99Jake Hamby return countryIso; 5829683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia } 5839683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia 584a2c93480e35ec06e44f4ccfa8657eacf3353be46Jay Shrauner protected static String getCurrentCountryIso(Context context) { 585a2c93480e35ec06e44f4ccfa8657eacf3353be46Jay Shrauner return getCurrentCountryIso(context, Locale.getDefault()); 586a2c93480e35ec06e44f4ccfa8657eacf3353be46Jay Shrauner } 587a2c93480e35ec06e44f4ccfa8657eacf3353be46Jay Shrauner 5889683f990a282776ac8a588a9d5e1a73b61f43dcfShaopeng Jia /** 58960d45f0f0320801a16db2ad038453c098e98966cNicolas Catania * @return a string debug representation of this instance. 59060d45f0f0320801a16db2ad038453c098e98966cNicolas Catania */ 59160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania public String toString() { 59204639ba0a939988d00131e61458807dac650f9c3David Brown // Warning: never check in this file with VERBOSE_DEBUG = true 59304639ba0a939988d00131e61458807dac650f9c3David Brown // because that will result in PII in the system log. 59404639ba0a939988d00131e61458807dac650f9c3David Brown final boolean VERBOSE_DEBUG = false; 59504639ba0a939988d00131e61458807dac650f9c3David Brown 59604639ba0a939988d00131e61458807dac650f9c3David Brown if (VERBOSE_DEBUG) { 59704639ba0a939988d00131e61458807dac650f9c3David Brown return new StringBuilder(384) 59894202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown .append(super.toString() + " { ") 59904639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nname: " + name) 60004639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nphoneNumber: " + phoneNumber) 60194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown .append("\nnormalizedNumber: " + normalizedNumber) 60294202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown .append("\ngeoDescription: " + geoDescription) 60304639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncnapName: " + cnapName) 60404639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nnumberPresentation: " + numberPresentation) 60504639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nnamePresentation: " + namePresentation) 60604639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncontactExits: " + contactExists) 60704639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nphoneLabel: " + phoneLabel) 60804639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nnumberType: " + numberType) 60904639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nnumberLabel: " + numberLabel) 61004639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nphotoResource: " + photoResource) 61104639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nperson_id: " + person_id) 61204639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nneedUpdate: " + needUpdate) 61304639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncontactRefUri: " + contactRefUri) 61404639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncontactRingtoneUri: " + contactRefUri) 61504639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail) 61604639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncachedPhoto: " + cachedPhoto) 61704639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent) 61804639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nemergency: " + mIsEmergency) 61904639ba0a939988d00131e61458807dac650f9c3David Brown .append("\nvoicemail " + mIsVoiceMail) 62004639ba0a939988d00131e61458807dac650f9c3David Brown .append("\ncontactExists " + contactExists) 62194202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown .append(" }") 62204639ba0a939988d00131e61458807dac650f9c3David Brown .toString(); 62304639ba0a939988d00131e61458807dac650f9c3David Brown } else { 62404639ba0a939988d00131e61458807dac650f9c3David Brown return new StringBuilder(128) 62594202fe1217b9f63e1f5c314379a9f0021e96ea8David Brown .append(super.toString() + " { ") 62604639ba0a939988d00131e61458807dac650f9c3David Brown .append("name " + ((name == null) ? "null" : "non-null")) 62704639ba0a939988d00131e61458807dac650f9c3David Brown .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null")) 62804639ba0a939988d00131e61458807dac650f9c3David Brown .append(" }") 62904639ba0a939988d00131e61458807dac650f9c3David Brown .toString(); 63004639ba0a939988d00131e61458807dac650f9c3David Brown } 63160d45f0f0320801a16db2ad038453c098e98966cNicolas Catania } 6322563a3ac05dd3cf8a07203ae682c243f2e793137Wink Saville} 633