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