MoreContactUtils.java revision b2c826a4c935a42f458ab187b257cd0b6a47bfb4
1/*
2 * Copyright (C) 2012 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.contacts.common;
18
19import com.android.i18n.phonenumbers.NumberParseException;
20import com.android.i18n.phonenumbers.PhoneNumberUtil;
21
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Rect;
25import android.net.Uri;
26import android.provider.ContactsContract;
27import android.telephony.PhoneNumberUtils;
28import android.text.TextUtils;
29import android.view.View;
30import android.widget.TextView;
31
32import com.android.contacts.common.model.account.AccountType;
33
34/**
35 * Shared static contact utility methods.
36 */
37public class MoreContactUtils {
38
39    private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT);
40
41    /**
42     * Returns true if two data with mimetypes which represent values in contact entries are
43     * considered equal for collapsing in the GUI. For caller-id, use
44     * {@link android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)}
45     * instead
46     */
47    public static boolean shouldCollapse(CharSequence mimetype1, CharSequence data1,
48              CharSequence mimetype2, CharSequence data2) {
49        // different mimetypes? don't collapse
50        if (!TextUtils.equals(mimetype1, mimetype2)) return false;
51
52        // exact same string? good, bail out early
53        if (TextUtils.equals(data1, data2)) return true;
54
55        // so if either is null, these two must be different
56        if (data1 == null || data2 == null) return false;
57
58        // if this is not about phone numbers, we know this is not a match (of course, some
59        // mimetypes could have more sophisticated matching is the future, e.g. addresses)
60        if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
61                mimetype1)) {
62            return false;
63        }
64
65        return shouldCollapsePhoneNumbers(data1.toString(), data2.toString());
66    }
67
68    private static boolean shouldCollapsePhoneNumbers(String number1, String number2) {
69        // Now do the full phone number thing. split into parts, separated by waiting symbol
70        // and compare them individually
71        final String[] dataParts1 = number1.split(WAIT_SYMBOL_AS_STRING);
72        final String[] dataParts2 = number2.split(WAIT_SYMBOL_AS_STRING);
73        if (dataParts1.length != dataParts2.length) return false;
74        final PhoneNumberUtil util = PhoneNumberUtil.getInstance();
75        for (int i = 0; i < dataParts1.length; i++) {
76            // Match phone numbers represented by keypad letters, in which case prefer the
77            // phone number with letters.
78            final String dataPart1 = PhoneNumberUtils.convertKeypadLettersToDigits(dataParts1[i]);
79            final String dataPart2 = dataParts2[i];
80
81            // substrings equal? shortcut, don't parse
82            if (TextUtils.equals(dataPart1, dataPart2)) continue;
83
84            // do a full parse of the numbers
85            switch (util.isNumberMatch(dataPart1, dataPart2)) {
86                case NOT_A_NUMBER:
87                    // don't understand the numbers? let's play it safe
88                    return false;
89                case NO_MATCH:
90                    return false;
91                case EXACT_MATCH:
92                    break;
93                case NSN_MATCH:
94                    try {
95                        // For NANP phone numbers, match when one has +1 and the other does not.
96                        // In this case, prefer the +1 version.
97                        if (util.parse(dataPart1, null).getCountryCode() == 1) {
98                            break;
99                        }
100                    } catch (NumberParseException e) {
101                        // Ignore
102                    }
103                    return false;
104                case SHORT_NSN_MATCH:
105                    return false;
106                default:
107                    throw new IllegalStateException("Unknown result value from phone number " +
108                            "library");
109            }
110        }
111        return true;
112    }
113
114    /**
115     * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates
116     * that are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to
117     * how the target {@link android.graphics.Rect} is calculated in
118     * {@link android.provider.ContactsContract.QuickContact#showQuickContact}.
119     */
120    public static Rect getTargetRectFromView(Context context, View view) {
121        final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
122        final int[] pos = new int[2];
123        view.getLocationOnScreen(pos);
124
125        final Rect rect = new Rect();
126        rect.left = (int) (pos[0] * appScale + 0.5f);
127        rect.top = (int) (pos[1] * appScale + 0.5f);
128        rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f);
129        rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f);
130        return rect;
131    }
132
133    /**
134     * Returns a header view based on the R.layout.list_separator, where the
135     * containing {@link android.widget.TextView} is set using the given textResourceId.
136     */
137    public static View createHeaderView(Context context, int textResourceId) {
138        View view = View.inflate(context, R.layout.list_separator, null);
139        TextView textView = (TextView) view.findViewById(R.id.title);
140        textView.setText(context.getString(textResourceId));
141        return view;
142    }
143
144    /**
145     * Returns the intent to launch for the given invitable account type and contact lookup URI.
146     * This will return null if the account type is not invitable (i.e. there is no
147     * {@link AccountType#getInviteContactActivityClassName()} or
148     * {@link AccountType#syncAdapterPackageName}).
149     */
150    public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) {
151        String syncAdapterPackageName = accountType.syncAdapterPackageName;
152        String className = accountType.getInviteContactActivityClassName();
153        if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) {
154            return null;
155        }
156        Intent intent = new Intent();
157        intent.setClassName(syncAdapterPackageName, className);
158
159        intent.setAction(ContactsContract.Intents.INVITE_CONTACT);
160
161        // Data is the lookup URI.
162        intent.setData(lookupUri);
163        return intent;
164    }
165}
166