1 /* 2 * Copyright (C) 2014 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 package com.android.providers.contacts; 17 18 import com.android.i18n.phonenumbers.Phonenumber; 19 import com.android.internal.annotations.VisibleForTesting; 20 21 import android.database.Cursor; 22 import android.database.MatrixCursor; 23 import android.provider.ContactsContract.PhoneLookup; 24 import android.telephony.PhoneNumberUtils; 25 import android.text.TextUtils; 26 import android.util.Log; 27 28 /** 29 * Helper class for PHONE_LOOKUP's that involve numbers with "*" prefixes. 30 */ 31 /* package-protected */ final class PhoneLookupWithStarPrefix { 32 private static final String TAG = "PhoneLookupWSP"; 33 34 /** 35 * Returns a cursor with a subset of the rows passed into this function. If {@param number} 36 * starts with a "*" then only rows from {@param cursor} that have a number equal to 37 * {@param number} will be returned. If {@param number} doesn't start with a "*", then 38 * only rows from {@param cursor} that have numbers without starting "*" characters 39 * will be returned. 40 * 41 * This function is used to resolve b/13195334. 42 * 43 * @param number unnormalized phone number. 44 * @param cursor this function takes ownership of the cursor. The calling scope MUST NOT 45 * use or close() the cursor passed into this function. The cursor must contain 46 * PhoneLookup.NUMBER. 47 * 48 * @return a cursor that the calling context owns 49 */ removeNonStarMatchesFromCursor(String number, Cursor cursor)50 public static Cursor removeNonStarMatchesFromCursor(String number, Cursor cursor) { 51 52 // Close cursors that we don't return. 53 Cursor unreturnedCursor = cursor; 54 55 try { 56 if (TextUtils.isEmpty(number)) { 57 unreturnedCursor = null; 58 return cursor; 59 } 60 61 final String queryPhoneNumberNormalized = normalizeNumberWithStar(number); 62 if (!queryPhoneNumberNormalized.startsWith("*") 63 && !matchingNumberStartsWithStar(cursor)) { 64 cursor.moveToPosition(-1); 65 unreturnedCursor = null; 66 return cursor; 67 } 68 69 final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); 70 71 // Close cursors that we don't return. 72 Cursor unreturnedMatrixCursor = matrixCursor; 73 74 try { 75 cursor.moveToPosition(-1); 76 while (cursor.moveToNext()) { 77 final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); 78 final String matchingNumberNormalized 79 = normalizeNumberWithStar(cursor.getString(numberIndex)); 80 if (!matchingNumberNormalized.startsWith("*") 81 && !queryPhoneNumberNormalized.startsWith("*") 82 || matchingNumberNormalized.equals(queryPhoneNumberNormalized)) { 83 // Copy row from cursor into matrixCursor 84 final MatrixCursor.RowBuilder b = matrixCursor.newRow(); 85 for (int column = 0; column < cursor.getColumnCount(); column++) { 86 b.add(cursor.getColumnName(column), cursorValue(cursor, column)); 87 } 88 } 89 } 90 unreturnedMatrixCursor = null; 91 return matrixCursor; 92 } finally { 93 if (unreturnedMatrixCursor != null) { 94 unreturnedMatrixCursor.close(); 95 } 96 } 97 } finally { 98 if (unreturnedCursor != null) { 99 unreturnedCursor.close(); 100 } 101 } 102 } 103 104 @VisibleForTesting normalizeNumberWithStar(String phoneNumber)105 static String normalizeNumberWithStar(String phoneNumber) { 106 if (TextUtils.isEmpty(phoneNumber)) { 107 return phoneNumber; 108 } 109 if (phoneNumber.startsWith("*")) { 110 // Use PhoneNumberUtils.normalizeNumber() to normalize the rest of the number after 111 // the leading "*". Strip out the "+" since "+"s are only allowed as leading 112 // characters. NOTE: This statement has poor performance. Fortunately, it won't be 113 // called very often. 114 return "*" + PhoneNumberUtils.normalizeNumber( 115 phoneNumber.substring(1).replace("+", "")); 116 } 117 return PhoneNumberUtils.normalizeNumber(phoneNumber); 118 } 119 120 /** 121 * @return whether {@param cursor} contain any numbers that start with "*" 122 */ matchingNumberStartsWithStar(Cursor cursor)123 private static boolean matchingNumberStartsWithStar(Cursor cursor) { 124 cursor.moveToPosition(-1); 125 while (cursor.moveToNext()) { 126 final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); 127 final String phoneNumber = normalizeNumberWithStar(cursor.getString(numberIndex)); 128 if (phoneNumber != null && phoneNumber.startsWith("*")) { 129 return true; 130 } 131 } 132 return false; 133 } 134 cursorValue(Cursor cursor, int column)135 private static Object cursorValue(Cursor cursor, int column) { 136 switch(cursor.getType(column)) { 137 case Cursor.FIELD_TYPE_BLOB: 138 return cursor.getBlob(column); 139 case Cursor.FIELD_TYPE_INTEGER: 140 return cursor.getInt(column); 141 case Cursor.FIELD_TYPE_FLOAT: 142 return cursor.getFloat(column); 143 case Cursor.FIELD_TYPE_STRING: 144 return cursor.getString(column); 145 case Cursor.FIELD_TYPE_NULL: 146 return null; 147 default: 148 Log.d(TAG, "Invalid value in cursor: " + cursor.getType(column)); 149 return null; 150 } 151 } 152 153 /** 154 * Check each phone number in the given cursor to detemine if it's a match with the given phone 155 * number. Return the matching ones in a new cursor. 156 * @param number phone number to be match 157 * @param cursor contains a series of numbers to be matched 158 * @param defaultCountryIso The lowercase two letter ISO 3166-1 country code. It is recommended 159 * to pass in {@link TelephonyManager#getNetworkCountryIso()}. 160 * @return A new cursor with all matching phone numbers. 161 */ removeNoMatchPhoneNumber(String number, Cursor cursor, String defaultCountryIso)162 public static Cursor removeNoMatchPhoneNumber(String number, Cursor cursor, 163 String defaultCountryIso) { 164 if (number == null) { 165 return cursor; 166 } 167 168 final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames()); 169 try { 170 cursor.moveToPosition(-1); 171 while (cursor.moveToNext()) { 172 final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); 173 final String numberToMatch = cursor.getString(numberIndex); 174 if (PhoneNumberUtils.areSamePhoneNumber(number, numberToMatch, defaultCountryIso)) { 175 final MatrixCursor.RowBuilder b = matrixCursor.newRow(); 176 for (int column = 0; column < cursor.getColumnCount(); column++) { 177 b.add(cursor.getColumnName(column), cursorValue(cursor, column)); 178 } 179 } 180 } 181 } finally { 182 if (cursor != null) { 183 cursor.close(); 184 } 185 } 186 return matrixCursor; 187 } 188 } 189