1/*
2 * Copyright (C) 2011 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
17package com.android.dialer.calllog;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.net.Uri;
22import android.provider.ContactsContract.Contacts;
23import android.provider.ContactsContract.PhoneLookup;
24import android.telephony.PhoneNumberUtils;
25import android.text.TextUtils;
26
27import com.android.contacts.common.util.UriUtils;
28
29/**
30 * Utility class to look up the contact information for a given number.
31 */
32public class ContactInfoHelper {
33    private final Context mContext;
34    private final String mCurrentCountryIso;
35
36    public ContactInfoHelper(Context context, String currentCountryIso) {
37        mContext = context;
38        mCurrentCountryIso = currentCountryIso;
39    }
40
41    /**
42     * Returns the contact information for the given number.
43     * <p>
44     * If the number does not match any contact, returns a contact info containing only the number
45     * and the formatted number.
46     * <p>
47     * If an error occurs during the lookup, it returns null.
48     *
49     * @param number the number to look up
50     * @param countryIso the country associated with this number
51     */
52    public ContactInfo lookupNumber(String number, String countryIso) {
53        final ContactInfo info;
54
55        // Determine the contact info.
56        if (PhoneNumberUtils.isUriNumber(number)) {
57            // This "number" is really a SIP address.
58            ContactInfo sipInfo = queryContactInfoForSipAddress(number);
59            if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
60                // Check whether the "username" part of the SIP address is
61                // actually the phone number of a contact.
62                String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
63                if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
64                    sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
65                }
66            }
67            info = sipInfo;
68        } else {
69            // Look for a contact that has the given phone number.
70            ContactInfo phoneInfo = queryContactInfoForPhoneNumber(number, countryIso);
71
72            if (phoneInfo == null || phoneInfo == ContactInfo.EMPTY) {
73                // Check whether the phone number has been saved as an "Internet call" number.
74                phoneInfo = queryContactInfoForSipAddress(number);
75            }
76            info = phoneInfo;
77        }
78
79        final ContactInfo updatedInfo;
80        if (info == null) {
81            // The lookup failed.
82            updatedInfo = null;
83        } else {
84            // If we did not find a matching contact, generate an empty contact info for the number.
85            if (info == ContactInfo.EMPTY) {
86                // Did not find a matching contact.
87                updatedInfo = new ContactInfo();
88                updatedInfo.number = number;
89                updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
90            } else {
91                updatedInfo = info;
92            }
93        }
94        return updatedInfo;
95    }
96
97    /**
98     * Looks up a contact using the given URI.
99     * <p>
100     * It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is
101     * found, or the {@link ContactInfo} for the given contact.
102     * <p>
103     * The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned
104     * value.
105     */
106    private ContactInfo lookupContactFromUri(Uri uri) {
107        final ContactInfo info;
108        Cursor phonesCursor =
109                mContext.getContentResolver().query(
110                        uri, PhoneQuery._PROJECTION, null, null, null);
111
112        if (phonesCursor != null) {
113            try {
114                if (phonesCursor.moveToFirst()) {
115                    info = new ContactInfo();
116                    long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
117                    String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
118                    info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
119                    info.name = phonesCursor.getString(PhoneQuery.NAME);
120                    info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
121                    info.label = phonesCursor.getString(PhoneQuery.LABEL);
122                    info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
123                    info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
124                    info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
125                    info.photoUri =
126                            UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI));
127                    info.formattedNumber = null;
128                } else {
129                    info = ContactInfo.EMPTY;
130                }
131            } finally {
132                phonesCursor.close();
133            }
134        } else {
135            // Failed to fetch the data, ignore this request.
136            info = null;
137        }
138        return info;
139    }
140
141    /**
142     * Determines the contact information for the given SIP address.
143     * <p>
144     * It returns the contact info if found.
145     * <p>
146     * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
147     * <p>
148     * If the lookup fails for some other reason, it returns null.
149     */
150    private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
151        final ContactInfo info;
152
153        // "contactNumber" is a SIP address, so use the PhoneLookup table with the SIP parameter.
154        Uri.Builder uriBuilder = PhoneLookup.CONTENT_FILTER_URI.buildUpon();
155        uriBuilder.appendPath(Uri.encode(sipAddress));
156        uriBuilder.appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1");
157        return lookupContactFromUri(uriBuilder.build());
158    }
159
160    /**
161     * Determines the contact information for the given phone number.
162     * <p>
163     * It returns the contact info if found.
164     * <p>
165     * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
166     * <p>
167     * If the lookup fails for some other reason, it returns null.
168     */
169    private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
170        String contactNumber = number;
171        if (!TextUtils.isEmpty(countryIso)) {
172            // Normalize the number: this is needed because the PhoneLookup query below does not
173            // accept a country code as an input.
174            String numberE164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
175            if (!TextUtils.isEmpty(numberE164)) {
176                // Only use it if the number could be formatted to E164.
177                contactNumber = numberE164;
178            }
179        }
180
181        // The "contactNumber" is a regular phone number, so use the PhoneLookup table.
182        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(contactNumber));
183        ContactInfo info = lookupContactFromUri(uri);
184        if (info != null && info != ContactInfo.EMPTY) {
185            info.formattedNumber = formatPhoneNumber(number, null, countryIso);
186        }
187        return info;
188    }
189
190    /**
191     * Format the given phone number
192     *
193     * @param number the number to be formatted.
194     * @param normalizedNumber the normalized number of the given number.
195     * @param countryIso the ISO 3166-1 two letters country code, the country's
196     *        convention will be used to format the number if the normalized
197     *        phone is null.
198     *
199     * @return the formatted number, or the given number if it was formatted.
200     */
201    private String formatPhoneNumber(String number, String normalizedNumber,
202            String countryIso) {
203        if (TextUtils.isEmpty(number)) {
204            return "";
205        }
206        // If "number" is really a SIP address, don't try to do any formatting at all.
207        if (PhoneNumberUtils.isUriNumber(number)) {
208            return number;
209        }
210        if (TextUtils.isEmpty(countryIso)) {
211            countryIso = mCurrentCountryIso;
212        }
213        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
214    }
215}
216