aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/providers/contacts/PhoneLookupWithStarPrefix.java
blob: 59e213bb768208910a0f12d78b68096a8bdcf5f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */
package com.android.providers.contacts;

import com.android.i18n.phonenumbers.Phonenumber;
import com.android.internal.annotations.VisibleForTesting;

import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;

/**
 * Helper class for PHONE_LOOKUP's that involve numbers with "*" prefixes.
 */
/* package-protected */ final class PhoneLookupWithStarPrefix {
    private static final String TAG = "PhoneLookupWSP";

    /**
     * Returns a cursor with a subset of the rows passed into this function. If {@param number}
     * starts with a "*" then only rows from {@param cursor} that have a number equal to
     * {@param number} will be returned. If {@param number} doesn't start with a "*", then
     * only rows from {@param cursor} that have numbers without starting "*" characters
     * will be returned.
     *
     * This function is used to resolve b/13195334.
     *
     * @param number unnormalized phone number.
     * @param cursor this function takes ownership of the cursor. The calling scope MUST NOT
     * use or close() the cursor passed into this function. The cursor must contain
     * PhoneLookup.NUMBER.
     *
     * @return a cursor that the calling context owns
     */
    public static Cursor removeNonStarMatchesFromCursor(String number, Cursor cursor) {

        // Close cursors that we don't return.
        Cursor unreturnedCursor = cursor;

        try {
            if (TextUtils.isEmpty(number)) {
                unreturnedCursor = null;
                return cursor;
            }

            final String queryPhoneNumberNormalized = normalizeNumberWithStar(number);
            if (!queryPhoneNumberNormalized.startsWith("*")
                    && !matchingNumberStartsWithStar(cursor)) {
                cursor.moveToPosition(-1);
                unreturnedCursor = null;
                return cursor;
            }

            final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames());

            // Close cursors that we don't return.
            Cursor unreturnedMatrixCursor = matrixCursor;

            try {
                cursor.moveToPosition(-1);
                while (cursor.moveToNext()) {
                    final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
                    final String matchingNumberNormalized
                            = normalizeNumberWithStar(cursor.getString(numberIndex));
                    if (!matchingNumberNormalized.startsWith("*")
                            && !queryPhoneNumberNormalized.startsWith("*")
                            || matchingNumberNormalized.equals(queryPhoneNumberNormalized)) {
                        // Copy row from cursor into matrixCursor
                        final MatrixCursor.RowBuilder b = matrixCursor.newRow();
                        for (int column = 0; column < cursor.getColumnCount(); column++) {
                            b.add(cursor.getColumnName(column), cursorValue(cursor, column));
                        }
                    }
                }
                unreturnedMatrixCursor = null;
                return matrixCursor;
            } finally {
                if (unreturnedMatrixCursor != null) {
                    unreturnedMatrixCursor.close();
                }
            }
        } finally {
            if (unreturnedCursor != null) {
                unreturnedCursor.close();
            }
        }
    }

    @VisibleForTesting
    static String normalizeNumberWithStar(String phoneNumber) {
        if (TextUtils.isEmpty(phoneNumber)) {
            return phoneNumber;
        }
        if (phoneNumber.startsWith("*")) {
            // Use PhoneNumberUtils.normalizeNumber() to normalize the rest of the number after
            // the leading "*". Strip out the "+" since "+"s are only allowed as leading
            // characters. NOTE: This statement has poor performance. Fortunately, it won't be
            // called very often.
            return "*" + PhoneNumberUtils.normalizeNumber(
                    phoneNumber.substring(1).replace("+", ""));
        }
        return PhoneNumberUtils.normalizeNumber(phoneNumber);
    }

    /**
     * @return whether {@param cursor} contain any numbers that start with "*"
     */
    private static boolean matchingNumberStartsWithStar(Cursor cursor) {
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
            final String phoneNumber = normalizeNumberWithStar(cursor.getString(numberIndex));
            if (phoneNumber != null && phoneNumber.startsWith("*")) {
                return true;
            }
        }
        return false;
    }

    private static Object cursorValue(Cursor cursor, int column) {
        switch(cursor.getType(column)) {
            case Cursor.FIELD_TYPE_BLOB:
                return cursor.getBlob(column);
            case Cursor.FIELD_TYPE_INTEGER:
                return cursor.getInt(column);
            case Cursor.FIELD_TYPE_FLOAT:
                return cursor.getFloat(column);
            case Cursor.FIELD_TYPE_STRING:
                return cursor.getString(column);
            case Cursor.FIELD_TYPE_NULL:
                return null;
            default:
                Log.d(TAG, "Invalid value in cursor: " + cursor.getType(column));
                return null;
        }
    }

    /**
     * Check each phone number in the given cursor to detemine if it's a match with the given phone
     * number. Return the matching ones in a new cursor.
     * @param number phone number to be match
     * @param cursor contains a series of numbers to be matched
     * @param defaultCountryIso The lowercase two letter ISO 3166-1 country code. It is recommended
     *                         to pass in {@link TelephonyManager#getNetworkCountryIso()}.
     * @return A new cursor with all matching phone numbers.
     */
    public static Cursor removeNoMatchPhoneNumber(String number, Cursor cursor,
            String defaultCountryIso) {
        if (number == null) {
            return cursor;
        }

        final MatrixCursor matrixCursor = new MatrixCursor(cursor.getColumnNames());
        try {
            cursor.moveToPosition(-1);
            while (cursor.moveToNext()) {
                final int numberIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
                final String numberToMatch = cursor.getString(numberIndex);
                if (PhoneNumberUtils.areSamePhoneNumber(number, numberToMatch, defaultCountryIso)) {
                    final MatrixCursor.RowBuilder b = matrixCursor.newRow();
                    for (int column = 0; column < cursor.getColumnCount(); column++) {
                        b.add(cursor.getColumnName(column), cursorValue(cursor, column));
                    }
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return matrixCursor;
    }
}