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 */
16package com.android.providers.contacts;
17
18import com.android.internal.annotations.VisibleForTesting;
19
20import android.database.Cursor;
21import android.database.MatrixCursor;
22import android.provider.ContactsContract.PhoneLookup;
23import android.telephony.PhoneNumberUtils;
24import android.text.TextUtils;
25import 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     */
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
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     */
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.startsWith("*")) {
128                return true;
129            }
130        }
131        return false;
132    }
133
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