blob: 69a824311166a269aba3ada3c52e28addadf19ab [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;
Aravind Sreekumar71212852018-04-06 15:47:45 -070026import androidx.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)
Tor Norbyefa04f702017-11-17 11:23:19 -0800107 // TODO: Switch to @LongDef once @LongDef is available in the support library
108 @IntDef({(int)USER_TYPE_CURRENT, (int)USER_TYPE_WORK})
Victor Changa4467422016-01-07 20:14:34 +0000109 public @interface UserType {}
110
Yorke Lee2644d942013-10-28 11:05:43 -0700111 /**
112 * Test if the given {@link CharSequence} contains any graphic characters,
113 * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
114 */
115 public static boolean isGraphic(CharSequence str) {
116 return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
117 }
118
119 /**
120 * Returns true if two objects are considered equal. Two null references are equal here.
121 */
Yorke Lee2644d942013-10-28 11:05:43 -0700122 public static boolean areObjectsEqual(Object a, Object b) {
123 return a == b || (a != null && a.equals(b));
124 }
125
126 /**
127 * Returns true if two {@link Intent}s are both null, or have the same action.
128 */
129 public static final boolean areIntentActionEqual(Intent a, Intent b) {
130 if (a == b) {
131 return true;
132 }
133 if (a == null || b == null) {
134 return false;
135 }
136 return TextUtils.equals(a.getAction(), b.getAction());
137 }
138
Yorke Lee2644d942013-10-28 11:05:43 -0700139 /**
140 * Returns the size (width and height) of thumbnail pictures as configured in the provider. This
141 * can safely be called from the UI thread, as the provider can serve this without performing
142 * a database access
143 */
144 public static int getThumbnailSize(Context context) {
145 if (sThumbnailSize == -1) {
146 final Cursor c = context.getContentResolver().query(
147 DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
148 new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null);
Jay Shrauner91125a62014-11-25 15:00:28 -0800149 if (c != null) {
150 try {
151 if (c.moveToFirst()) {
152 sThumbnailSize = c.getInt(0);
153 }
154 } finally {
155 c.close();
156 }
Yorke Lee2644d942013-10-28 11:05:43 -0700157 }
158 }
Jay Shrauner91125a62014-11-25 15:00:28 -0800159 return sThumbnailSize != -1 ? sThumbnailSize : DEFAULT_THUMBNAIL_SIZE;
Yorke Lee2644d942013-10-28 11:05:43 -0700160 }
161
Paul Soulose98b88d2014-07-07 16:32:32 -0700162 private static Intent getCustomImIntent(ImDataItem im, int protocol) {
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700163 String host = im.getCustomProtocol();
164 final String data = im.getData();
165 if (TextUtils.isEmpty(data)) {
166 return null;
167 }
168 if (protocol != Im.PROTOCOL_CUSTOM) {
169 // Try bringing in a well-known host for specific protocols
170 host = ContactsUtils.lookupProviderNameFromId(protocol);
171 }
172 if (TextUtils.isEmpty(host)) {
173 return null;
174 }
175 final String authority = host.toLowerCase();
Jay Shraunered1a3b22014-09-05 15:37:27 -0700176 final Uri imUri = new Uri.Builder().scheme(SCHEME_IMTO).authority(
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700177 authority).appendPath(data).build();
178 final Intent intent = new Intent(Intent.ACTION_SENDTO, imUri);
179 return intent;
180 }
Paul Soulose98b88d2014-07-07 16:32:32 -0700181
182 /**
183 * Returns the proper Intent for an ImDatItem. If available, a secondary intent is stored
184 * in the second Pair slot
185 */
186 public static Pair<Intent, Intent> buildImIntent(Context context, ImDataItem im) {
187 Intent intent = null;
188 Intent secondaryIntent = null;
189 final boolean isEmail = im.isCreatedFromEmail();
190
191 if (!isEmail && !im.isProtocolValid()) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700192 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700193 }
194
195 final String data = im.getData();
196 if (TextUtils.isEmpty(data)) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700197 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700198 }
199
200 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
201
202 if (protocol == Im.PROTOCOL_GOOGLE_TALK) {
203 final int chatCapability = im.getChatCapability();
204 if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
205 intent = new Intent(Intent.ACTION_SENDTO,
206 Uri.parse("xmpp:" + data + "?message"));
207 secondaryIntent = new Intent(Intent.ACTION_SENDTO,
208 Uri.parse("xmpp:" + data + "?call"));
209 } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
210 // Allow Talking and Texting
211 intent =
212 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
213 secondaryIntent =
214 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
215 } else {
216 intent =
217 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
218 }
219 } else {
220 // Build an IM Intent
221 intent = getCustomImIntent(im, protocol);
222 }
223 return new Pair<>(intent, secondaryIntent);
224 }
Victor Changa4467422016-01-07 20:14:34 +0000225
226 /**
227 * Determine UserType from directory id and contact id.
228 *
229 * 3 types of query
230 *
231 * 1. 2 profile query: content://com.android.contacts/phone_lookup_enterprise/1234567890
232 * personal and work contact are mixed into one cursor. no directory id. contact_id indicates if
233 * it's work contact
234 *
235 * 2. work local query:
236 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000000
237 * either directory_id or contact_id is enough to identify work contact
238 *
239 * 3. work remote query:
240 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000003
241 * contact_id is random. only directory_id is available
242 *
243 * Summary: If directory_id is not null, always use directory_id to identify work contact.
244 * (which is the case here) Otherwise, use contact_id.
245 *
246 * @param directoryId directory id of ContactsProvider query
247 * @param contactId contact id
248 * @return UserType indicates the user type of the contact. A directory id or contact id larger
249 * than a thredshold indicates that the contact is stored in Work Profile, but not in
250 * current user. It's a contract by ContactsProvider and check by
251 * Contacts.isEnterpriseDirectoryId and Contacts.isEnterpriseContactId. Currently, only
252 * 2 kinds of users can be detected from the directoryId and contactId as
253 * ContactsProvider can only access current and work user's contacts
254 */
255 public static @UserType long determineUserType(Long directoryId, Long contactId) {
256 // First check directory id
257 if (directoryId != null) {
258 return DirectoryCompat.isEnterpriseDirectoryId(directoryId) ? USER_TYPE_WORK
259 : USER_TYPE_CURRENT;
260 }
261 // Only check contact id if directory id is null
262 if (contactId != null && contactId != 0L
263 && ContactsCompat.isEnterpriseContactId(contactId)) {
264 return USER_TYPE_WORK;
265 } else {
266 return USER_TYPE_CURRENT;
267 }
268
269 }
Yorke Lee2644d942013-10-28 11:05:43 -0700270}