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