blob: 69890b416ffadc7271a2c6f26ba32090f35af386 [file] [log] [blame]
Yorke Lee2644d942013-10-28 11:05:43 -07001/*
2 * Copyright (C) 2009 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
Gary Mai0a49afa2016-12-05 15:53:58 -080017package com.android.contacts;
Yorke Lee2644d942013-10-28 11:05:43 -070018
19import android.content.Context;
20import android.content.Intent;
21import android.database.Cursor;
Paul Soulos1ff2fcb2014-07-07 11:46:49 -070022import android.net.Uri;
Victor Changb83af232015-12-01 19:13:12 +000023import android.os.Build;
Yorke Lee2644d942013-10-28 11:05:43 -070024import android.provider.ContactsContract.CommonDataKinds.Im;
25import android.provider.ContactsContract.DisplayPhoto;
Gary Mai0a49afa2016-12-05 15:53:58 -080026import android.support.annotation.IntDef;
Yorke Lee2644d942013-10-28 11:05:43 -070027import android.text.TextUtils;
Paul Soulose98b88d2014-07-07 16:32:32 -070028import android.util.Pair;
Yorke Lee2644d942013-10-28 11:05:43 -070029
Gary Mai69c182a2016-12-05 13:07:03 -080030import com.android.contacts.compat.ContactsCompat;
31import com.android.contacts.compat.DirectoryCompat;
Gary Mai0a49afa2016-12-05 15:53:58 -080032import com.android.contacts.model.dataitem.ImDataItem;
Yorke Lee2644d942013-10-28 11:05:43 -070033
Victor Changa4467422016-01-07 20:14:34 +000034import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
Yorke Lee2644d942013-10-28 11:05:43 -070036
37public class ContactsUtils {
38 private static final String TAG = "ContactsUtils";
39
Jay Shraunered1a3b22014-09-05 15:37:27 -070040 // Telecomm related schemes are in CallUtil
41 public static final String SCHEME_IMTO = "imto";
42 public static final String SCHEME_MAILTO = "mailto";
43 public static final String SCHEME_SMSTO = "smsto";
44
Jay Shrauner91125a62014-11-25 15:00:28 -080045 private static final int DEFAULT_THUMBNAIL_SIZE = 96;
46
Yorke Lee2644d942013-10-28 11:05:43 -070047 private static int sThumbnailSize = -1;
48
Tingting Wang159b1c32016-06-07 16:50:11 -070049 public static final boolean FLAG_N_FEATURE = Build.VERSION.SDK_INT >= 24;
Victor Changb83af232015-12-01 19:13:12 +000050
Yorke Lee2644d942013-10-28 11:05:43 -070051 // TODO find a proper place for the canonical version of these
52 public interface ProviderNames {
53 String YAHOO = "Yahoo";
54 String GTALK = "GTalk";
55 String MSN = "MSN";
56 String ICQ = "ICQ";
57 String AIM = "AIM";
58 String XMPP = "XMPP";
59 String JABBER = "JABBER";
60 String SKYPE = "SKYPE";
61 String QQ = "QQ";
62 }
63
64 /**
65 * This looks up the provider name defined in
66 * ProviderNames from the predefined IM protocol id.
67 * This is used for interacting with the IM application.
68 *
69 * @param protocol the protocol ID
70 * @return the provider name the IM app uses for the given protocol, or null if no
71 * provider is defined for the given protocol
72 * @hide
73 */
74 public static String lookupProviderNameFromId(int protocol) {
75 switch (protocol) {
76 case Im.PROTOCOL_GOOGLE_TALK:
77 return ProviderNames.GTALK;
78 case Im.PROTOCOL_AIM:
79 return ProviderNames.AIM;
80 case Im.PROTOCOL_MSN:
81 return ProviderNames.MSN;
82 case Im.PROTOCOL_YAHOO:
83 return ProviderNames.YAHOO;
84 case Im.PROTOCOL_ICQ:
85 return ProviderNames.ICQ;
86 case Im.PROTOCOL_JABBER:
87 return ProviderNames.JABBER;
88 case Im.PROTOCOL_SKYPE:
89 return ProviderNames.SKYPE;
90 case Im.PROTOCOL_QQ:
91 return ProviderNames.QQ;
92 }
93 return null;
94 }
95
Victor Changa4467422016-01-07 20:14:34 +000096
97 public static final long USER_TYPE_CURRENT = 0;
98 public static final long USER_TYPE_WORK = 1;
99
100 /**
101 * UserType indicates the user type of the contact. If the contact is from Work User (Work
102 * Profile in Android Multi-User System), it's {@link #USER_TYPE_WORK}, otherwise,
103 * {@link #USER_TYPE_CURRENT}. Please note that current user can be in work profile, where the
104 * dialer is running inside Work Profile.
105 */
106 @Retention(RetentionPolicy.SOURCE)
107 @IntDef({USER_TYPE_CURRENT, USER_TYPE_WORK})
108 public @interface UserType {}
109
Yorke Lee2644d942013-10-28 11:05:43 -0700110 /**
111 * Test if the given {@link CharSequence} contains any graphic characters,
112 * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
113 */
114 public static boolean isGraphic(CharSequence str) {
115 return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
116 }
117
118 /**
119 * Returns true if two objects are considered equal. Two null references are equal here.
120 */
Yorke Lee2644d942013-10-28 11:05:43 -0700121 public static boolean areObjectsEqual(Object a, Object b) {
122 return a == b || (a != null && a.equals(b));
123 }
124
125 /**
126 * Returns true if two {@link Intent}s are both null, or have the same action.
127 */
128 public static final boolean areIntentActionEqual(Intent a, Intent b) {
129 if (a == b) {
130 return true;
131 }
132 if (a == null || b == null) {
133 return false;
134 }
135 return TextUtils.equals(a.getAction(), b.getAction());
136 }
137
Yorke Lee2644d942013-10-28 11:05:43 -0700138 /**
139 * Returns the size (width and height) of thumbnail pictures as configured in the provider. This
140 * can safely be called from the UI thread, as the provider can serve this without performing
141 * a database access
142 */
143 public static int getThumbnailSize(Context context) {
144 if (sThumbnailSize == -1) {
145 final Cursor c = context.getContentResolver().query(
146 DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
147 new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null);
Jay Shrauner91125a62014-11-25 15:00:28 -0800148 if (c != null) {
149 try {
150 if (c.moveToFirst()) {
151 sThumbnailSize = c.getInt(0);
152 }
153 } finally {
154 c.close();
155 }
Yorke Lee2644d942013-10-28 11:05:43 -0700156 }
157 }
Jay Shrauner91125a62014-11-25 15:00:28 -0800158 return sThumbnailSize != -1 ? sThumbnailSize : DEFAULT_THUMBNAIL_SIZE;
Yorke Lee2644d942013-10-28 11:05:43 -0700159 }
160
Paul Soulose98b88d2014-07-07 16:32:32 -0700161 private static Intent getCustomImIntent(ImDataItem im, int protocol) {
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700162 String host = im.getCustomProtocol();
163 final String data = im.getData();
164 if (TextUtils.isEmpty(data)) {
165 return null;
166 }
167 if (protocol != Im.PROTOCOL_CUSTOM) {
168 // Try bringing in a well-known host for specific protocols
169 host = ContactsUtils.lookupProviderNameFromId(protocol);
170 }
171 if (TextUtils.isEmpty(host)) {
172 return null;
173 }
174 final String authority = host.toLowerCase();
Jay Shraunered1a3b22014-09-05 15:37:27 -0700175 final Uri imUri = new Uri.Builder().scheme(SCHEME_IMTO).authority(
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700176 authority).appendPath(data).build();
177 final Intent intent = new Intent(Intent.ACTION_SENDTO, imUri);
178 return intent;
179 }
Paul Soulose98b88d2014-07-07 16:32:32 -0700180
181 /**
182 * Returns the proper Intent for an ImDatItem. If available, a secondary intent is stored
183 * in the second Pair slot
184 */
185 public static Pair<Intent, Intent> buildImIntent(Context context, ImDataItem im) {
186 Intent intent = null;
187 Intent secondaryIntent = null;
188 final boolean isEmail = im.isCreatedFromEmail();
189
190 if (!isEmail && !im.isProtocolValid()) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700191 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700192 }
193
194 final String data = im.getData();
195 if (TextUtils.isEmpty(data)) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700196 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700197 }
198
199 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
200
201 if (protocol == Im.PROTOCOL_GOOGLE_TALK) {
202 final int chatCapability = im.getChatCapability();
203 if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
204 intent = new Intent(Intent.ACTION_SENDTO,
205 Uri.parse("xmpp:" + data + "?message"));
206 secondaryIntent = new Intent(Intent.ACTION_SENDTO,
207 Uri.parse("xmpp:" + data + "?call"));
208 } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
209 // Allow Talking and Texting
210 intent =
211 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
212 secondaryIntent =
213 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
214 } else {
215 intent =
216 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
217 }
218 } else {
219 // Build an IM Intent
220 intent = getCustomImIntent(im, protocol);
221 }
222 return new Pair<>(intent, secondaryIntent);
223 }
Victor Changa4467422016-01-07 20:14:34 +0000224
225 /**
226 * Determine UserType from directory id and contact id.
227 *
228 * 3 types of query
229 *
230 * 1. 2 profile query: content://com.android.contacts/phone_lookup_enterprise/1234567890
231 * personal and work contact are mixed into one cursor. no directory id. contact_id indicates if
232 * it's work contact
233 *
234 * 2. work local query:
235 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000000
236 * either directory_id or contact_id is enough to identify work contact
237 *
238 * 3. work remote query:
239 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000003
240 * contact_id is random. only directory_id is available
241 *
242 * Summary: If directory_id is not null, always use directory_id to identify work contact.
243 * (which is the case here) Otherwise, use contact_id.
244 *
245 * @param directoryId directory id of ContactsProvider query
246 * @param contactId contact id
247 * @return UserType indicates the user type of the contact. A directory id or contact id larger
248 * than a thredshold indicates that the contact is stored in Work Profile, but not in
249 * current user. It's a contract by ContactsProvider and check by
250 * Contacts.isEnterpriseDirectoryId and Contacts.isEnterpriseContactId. Currently, only
251 * 2 kinds of users can be detected from the directoryId and contactId as
252 * ContactsProvider can only access current and work user's contacts
253 */
254 public static @UserType long determineUserType(Long directoryId, Long contactId) {
255 // First check directory id
256 if (directoryId != null) {
257 return DirectoryCompat.isEnterpriseDirectoryId(directoryId) ? USER_TYPE_WORK
258 : USER_TYPE_CURRENT;
259 }
260 // Only check contact id if directory id is null
261 if (contactId != null && contactId != 0L
262 && ContactsCompat.isEnterpriseContactId(contactId)) {
263 return USER_TYPE_WORK;
264 } else {
265 return USER_TYPE_CURRENT;
266 }
267
268 }
Yorke Lee2644d942013-10-28 11:05:43 -0700269}