ContactInfoHelper.java revision 58ebe5d3fac837625b69fe1565427f17025bd780
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;
23import android.provider.ContactsContract.CommonDataKinds.Phone;
24import android.provider.ContactsContract.Contacts;
25import android.provider.ContactsContract.Directory;
26import android.provider.ContactsContract.DisplayNameSources;
27import android.provider.ContactsContract.PhoneLookup;
28import android.telephony.PhoneNumberUtils;
29import android.text.TextUtils;
30import android.util.Log;
31
32import com.android.contacts.common.util.Constants;
33import com.android.contacts.common.util.UriUtils;
34import com.android.dialer.service.CachedNumberLookupService;
35import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
36import com.android.dialerbind.ServiceFactory;
37
38import org.json.JSONException;
39import org.json.JSONObject;
40
41/**
42 * Utility class to look up the contact information for a given number.
43 */
44public class ContactInfoHelper {
45    private final Context mContext;
46    private final String mCurrentCountryIso;
47
48    private static final CachedNumberLookupService mCachedNumberLookupService =
49            ServiceFactory.newCachedNumberLookupService();
50
51    public ContactInfoHelper(Context context, String currentCountryIso) {
52        mContext = context;
53        mCurrentCountryIso = currentCountryIso;
54    }
55
56    /**
57     * Returns the contact information for the given number.
58     * <p>
59     * If the number does not match any contact, returns a contact info containing only the number
60     * and the formatted number.
61     * <p>
62     * If an error occurs during the lookup, it returns null.
63     *
64     * @param number the number to look up
65     * @param countryIso the country associated with this number
66     */
67    public ContactInfo lookupNumber(String number, String countryIso) {
68        final ContactInfo info;
69
70        // Determine the contact info.
71        if (PhoneNumberUtils.isUriNumber(number)) {
72            // This "number" is really a SIP address.
73            ContactInfo sipInfo = queryContactInfoForSipAddress(number);
74            if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
75                // Check whether the "username" part of the SIP address is
76                // actually the phone number of a contact.
77                String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
78                if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
79                    sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
80                }
81            }
82            info = sipInfo;
83        } else {
84            // Look for a contact that has the given phone number.
85            ContactInfo phoneInfo = queryContactInfoForPhoneNumber(number, countryIso);
86
87            if (phoneInfo == null || phoneInfo == ContactInfo.EMPTY) {
88                // Check whether the phone number has been saved as an "Internet call" number.
89                phoneInfo = queryContactInfoForSipAddress(number);
90            }
91            info = phoneInfo;
92        }
93
94        final ContactInfo updatedInfo;
95        if (info == null) {
96            // The lookup failed.
97            updatedInfo = null;
98        } else {
99            // If we did not find a matching contact, generate an empty contact info for the number.
100            if (info == ContactInfo.EMPTY) {
101                // Did not find a matching contact.
102                updatedInfo = new ContactInfo();
103                updatedInfo.number = number;
104                updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
105                updatedInfo.lookupUri = createTemporaryContactUri(number);
106            } else {
107                updatedInfo = info;
108            }
109        }
110        return updatedInfo;
111    }
112
113    /**
114     * Creates a JSON-encoded lookup uri for a unknown number without an associated contact
115     *
116     * @param number - Unknown phone number
117     * @return JSON-encoded URI that can be used to perform a lookup when clicking
118     * on the quick contact card.
119     */
120    private static Uri createTemporaryContactUri(String number) {
121        try {
122            final JSONObject contactRows = new JSONObject()
123                    .put(Phone.CONTENT_ITEM_TYPE, new JSONObject()
124                            .put(Phone.NUMBER, number)
125                                    .put(Phone.TYPE, Phone.TYPE_CUSTOM));
126
127            final String jsonString = new JSONObject()
128                    .put(Contacts.DISPLAY_NAME, number)
129                            .put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.PHONE)
130                            .put(Contacts.CONTENT_ITEM_TYPE, contactRows)
131                            .toString();
132
133            return Contacts.CONTENT_LOOKUP_URI.buildUpon()
134                    .appendPath(Constants.LOOKUP_URI_ENCODED)
135                    .appendQueryParameter(Constants.LOOKUP_URI_JSON, jsonString)
136                    .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
137                            String.valueOf(Long.MAX_VALUE))
138                    .build();
139        } catch (JSONException e) {
140            return null;
141        }
142    }
143
144    /**
145     * Looks up a contact using the given URI.
146     * <p>
147     * It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is
148     * found, or the {@link ContactInfo} for the given contact.
149     * <p>
150     * The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned
151     * value.
152     */
153    private ContactInfo lookupContactFromUri(Uri uri) {
154        final ContactInfo info;
155        Cursor phonesCursor =
156                mContext.getContentResolver().query(
157                        uri, PhoneQuery._PROJECTION, null, null, null);
158
159        if (phonesCursor != null) {
160            try {
161                if (phonesCursor.moveToFirst()) {
162                    info = new ContactInfo();
163                    long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
164                    String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
165                    info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
166                    info.name = phonesCursor.getString(PhoneQuery.NAME);
167                    info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
168                    info.label = phonesCursor.getString(PhoneQuery.LABEL);
169                    info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
170                    info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
171                    info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
172                    info.photoUri =
173                            UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI));
174                    info.formattedNumber = null;
175                } else {
176                    info = ContactInfo.EMPTY;
177                }
178            } finally {
179                phonesCursor.close();
180            }
181        } else {
182            // Failed to fetch the data, ignore this request.
183            info = null;
184        }
185        return info;
186    }
187
188    /**
189     * Determines the contact information for the given SIP address.
190     * <p>
191     * It returns the contact info if found.
192     * <p>
193     * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
194     * <p>
195     * If the lookup fails for some other reason, it returns null.
196     */
197    private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
198        final ContactInfo info;
199
200        // "contactNumber" is a SIP address, so use the PhoneLookup table with the SIP parameter.
201        Uri.Builder uriBuilder = PhoneLookup.CONTENT_FILTER_URI.buildUpon();
202        uriBuilder.appendPath(Uri.encode(sipAddress));
203        uriBuilder.appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1");
204        return lookupContactFromUri(uriBuilder.build());
205    }
206
207    /**
208     * Determines the contact information for the given phone number.
209     * <p>
210     * It returns the contact info if found.
211     * <p>
212     * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
213     * <p>
214     * If the lookup fails for some other reason, it returns null.
215     */
216    private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
217        String contactNumber = number;
218        if (!TextUtils.isEmpty(countryIso)) {
219            // Normalize the number: this is needed because the PhoneLookup query below does not
220            // accept a country code as an input.
221            String numberE164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
222            if (!TextUtils.isEmpty(numberE164)) {
223                // Only use it if the number could be formatted to E164.
224                contactNumber = numberE164;
225            }
226        }
227
228        // The "contactNumber" is a regular phone number, so use the PhoneLookup table.
229        Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(contactNumber));
230        ContactInfo info = lookupContactFromUri(uri);
231        if (info != null && info != ContactInfo.EMPTY) {
232            info.formattedNumber = formatPhoneNumber(number, null, countryIso);
233        } else if (mCachedNumberLookupService != null) {
234            info = mCachedNumberLookupService.lookupCachedContactFromNumber(mContext, number);
235        }
236        return info;
237    }
238
239    /**
240     * Format the given phone number
241     *
242     * @param number the number to be formatted.
243     * @param normalizedNumber the normalized number of the given number.
244     * @param countryIso the ISO 3166-1 two letters country code, the country's
245     *        convention will be used to format the number if the normalized
246     *        phone is null.
247     *
248     * @return the formatted number, or the given number if it was formatted.
249     */
250    private String formatPhoneNumber(String number, String normalizedNumber,
251            String countryIso) {
252        if (TextUtils.isEmpty(number)) {
253            return "";
254        }
255        // If "number" is really a SIP address, don't try to do any formatting at all.
256        if (PhoneNumberUtils.isUriNumber(number)) {
257            return number;
258        }
259        if (TextUtils.isEmpty(countryIso)) {
260            countryIso = mCurrentCountryIso;
261        }
262        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
263    }
264}
265