blob: 448e696160f8663e82681dad5258b990d02e3b6e [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;
32import com.android.contacts.model.AccountTypeManager;
Gary Mai0a49afa2016-12-05 15:53:58 -080033import com.android.contacts.model.account.AccountWithDataSet;
34import com.android.contacts.model.dataitem.ImDataItem;
Yorke Lee2644d942013-10-28 11:05:43 -070035
Victor Changa4467422016-01-07 20:14:34 +000036import java.lang.annotation.Retention;
37import java.lang.annotation.RetentionPolicy;
Yorke Lee2644d942013-10-28 11:05:43 -070038import java.util.List;
39
40public class ContactsUtils {
41 private static final String TAG = "ContactsUtils";
42
Jay Shraunered1a3b22014-09-05 15:37:27 -070043 // Telecomm related schemes are in CallUtil
44 public static final String SCHEME_IMTO = "imto";
45 public static final String SCHEME_MAILTO = "mailto";
46 public static final String SCHEME_SMSTO = "smsto";
47
Jay Shrauner91125a62014-11-25 15:00:28 -080048 private static final int DEFAULT_THUMBNAIL_SIZE = 96;
49
Yorke Lee2644d942013-10-28 11:05:43 -070050 private static int sThumbnailSize = -1;
51
Tingting Wang159b1c32016-06-07 16:50:11 -070052 public static final boolean FLAG_N_FEATURE = Build.VERSION.SDK_INT >= 24;
Victor Changb83af232015-12-01 19:13:12 +000053
Yorke Lee2644d942013-10-28 11:05:43 -070054 // TODO find a proper place for the canonical version of these
55 public interface ProviderNames {
56 String YAHOO = "Yahoo";
57 String GTALK = "GTalk";
58 String MSN = "MSN";
59 String ICQ = "ICQ";
60 String AIM = "AIM";
61 String XMPP = "XMPP";
62 String JABBER = "JABBER";
63 String SKYPE = "SKYPE";
64 String QQ = "QQ";
65 }
66
67 /**
68 * This looks up the provider name defined in
69 * ProviderNames from the predefined IM protocol id.
70 * This is used for interacting with the IM application.
71 *
72 * @param protocol the protocol ID
73 * @return the provider name the IM app uses for the given protocol, or null if no
74 * provider is defined for the given protocol
75 * @hide
76 */
77 public static String lookupProviderNameFromId(int protocol) {
78 switch (protocol) {
79 case Im.PROTOCOL_GOOGLE_TALK:
80 return ProviderNames.GTALK;
81 case Im.PROTOCOL_AIM:
82 return ProviderNames.AIM;
83 case Im.PROTOCOL_MSN:
84 return ProviderNames.MSN;
85 case Im.PROTOCOL_YAHOO:
86 return ProviderNames.YAHOO;
87 case Im.PROTOCOL_ICQ:
88 return ProviderNames.ICQ;
89 case Im.PROTOCOL_JABBER:
90 return ProviderNames.JABBER;
91 case Im.PROTOCOL_SKYPE:
92 return ProviderNames.SKYPE;
93 case Im.PROTOCOL_QQ:
94 return ProviderNames.QQ;
95 }
96 return null;
97 }
98
Victor Changa4467422016-01-07 20:14:34 +000099
100 public static final long USER_TYPE_CURRENT = 0;
101 public static final long USER_TYPE_WORK = 1;
102
103 /**
104 * UserType indicates the user type of the contact. If the contact is from Work User (Work
105 * Profile in Android Multi-User System), it's {@link #USER_TYPE_WORK}, otherwise,
106 * {@link #USER_TYPE_CURRENT}. Please note that current user can be in work profile, where the
107 * dialer is running inside Work Profile.
108 */
109 @Retention(RetentionPolicy.SOURCE)
110 @IntDef({USER_TYPE_CURRENT, USER_TYPE_WORK})
111 public @interface UserType {}
112
Yorke Lee2644d942013-10-28 11:05:43 -0700113 /**
114 * Test if the given {@link CharSequence} contains any graphic characters,
115 * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
116 */
117 public static boolean isGraphic(CharSequence str) {
118 return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
119 }
120
121 /**
122 * Returns true if two objects are considered equal. Two null references are equal here.
123 */
Yorke Lee2644d942013-10-28 11:05:43 -0700124 public static boolean areObjectsEqual(Object a, Object b) {
125 return a == b || (a != null && a.equals(b));
126 }
127
128 /**
129 * Returns true if two {@link Intent}s are both null, or have the same action.
130 */
131 public static final boolean areIntentActionEqual(Intent a, Intent b) {
132 if (a == b) {
133 return true;
134 }
135 if (a == null || b == null) {
136 return false;
137 }
138 return TextUtils.equals(a.getAction(), b.getAction());
139 }
140
Yorke Lee2644d942013-10-28 11:05:43 -0700141 public static boolean areGroupWritableAccountsAvailable(Context context) {
142 final List<AccountWithDataSet> accounts =
143 AccountTypeManager.getInstance(context).getGroupWritableAccounts();
144 return !accounts.isEmpty();
145 }
146
147 /**
148 * Returns the size (width and height) of thumbnail pictures as configured in the provider. This
149 * can safely be called from the UI thread, as the provider can serve this without performing
150 * a database access
151 */
152 public static int getThumbnailSize(Context context) {
153 if (sThumbnailSize == -1) {
154 final Cursor c = context.getContentResolver().query(
155 DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
156 new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null);
Jay Shrauner91125a62014-11-25 15:00:28 -0800157 if (c != null) {
158 try {
159 if (c.moveToFirst()) {
160 sThumbnailSize = c.getInt(0);
161 }
162 } finally {
163 c.close();
164 }
Yorke Lee2644d942013-10-28 11:05:43 -0700165 }
166 }
Jay Shrauner91125a62014-11-25 15:00:28 -0800167 return sThumbnailSize != -1 ? sThumbnailSize : DEFAULT_THUMBNAIL_SIZE;
Yorke Lee2644d942013-10-28 11:05:43 -0700168 }
169
Paul Soulose98b88d2014-07-07 16:32:32 -0700170 private static Intent getCustomImIntent(ImDataItem im, int protocol) {
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700171 String host = im.getCustomProtocol();
172 final String data = im.getData();
173 if (TextUtils.isEmpty(data)) {
174 return null;
175 }
176 if (protocol != Im.PROTOCOL_CUSTOM) {
177 // Try bringing in a well-known host for specific protocols
178 host = ContactsUtils.lookupProviderNameFromId(protocol);
179 }
180 if (TextUtils.isEmpty(host)) {
181 return null;
182 }
183 final String authority = host.toLowerCase();
Jay Shraunered1a3b22014-09-05 15:37:27 -0700184 final Uri imUri = new Uri.Builder().scheme(SCHEME_IMTO).authority(
Paul Soulos1ff2fcb2014-07-07 11:46:49 -0700185 authority).appendPath(data).build();
186 final Intent intent = new Intent(Intent.ACTION_SENDTO, imUri);
187 return intent;
188 }
Paul Soulose98b88d2014-07-07 16:32:32 -0700189
190 /**
191 * Returns the proper Intent for an ImDatItem. If available, a secondary intent is stored
192 * in the second Pair slot
193 */
194 public static Pair<Intent, Intent> buildImIntent(Context context, ImDataItem im) {
195 Intent intent = null;
196 Intent secondaryIntent = null;
197 final boolean isEmail = im.isCreatedFromEmail();
198
199 if (!isEmail && !im.isProtocolValid()) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700200 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700201 }
202
203 final String data = im.getData();
204 if (TextUtils.isEmpty(data)) {
Jay Shrauner5c798a92014-09-02 15:38:19 -0700205 return new Pair<>(null, null);
Paul Soulose98b88d2014-07-07 16:32:32 -0700206 }
207
208 final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
209
210 if (protocol == Im.PROTOCOL_GOOGLE_TALK) {
211 final int chatCapability = im.getChatCapability();
212 if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
213 intent = new Intent(Intent.ACTION_SENDTO,
214 Uri.parse("xmpp:" + data + "?message"));
215 secondaryIntent = new Intent(Intent.ACTION_SENDTO,
216 Uri.parse("xmpp:" + data + "?call"));
217 } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
218 // Allow Talking and Texting
219 intent =
220 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
221 secondaryIntent =
222 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
223 } else {
224 intent =
225 new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
226 }
227 } else {
228 // Build an IM Intent
229 intent = getCustomImIntent(im, protocol);
230 }
231 return new Pair<>(intent, secondaryIntent);
232 }
Victor Changa4467422016-01-07 20:14:34 +0000233
234 /**
235 * Determine UserType from directory id and contact id.
236 *
237 * 3 types of query
238 *
239 * 1. 2 profile query: content://com.android.contacts/phone_lookup_enterprise/1234567890
240 * personal and work contact are mixed into one cursor. no directory id. contact_id indicates if
241 * it's work contact
242 *
243 * 2. work local query:
244 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000000
245 * either directory_id or contact_id is enough to identify work contact
246 *
247 * 3. work remote query:
248 * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000003
249 * contact_id is random. only directory_id is available
250 *
251 * Summary: If directory_id is not null, always use directory_id to identify work contact.
252 * (which is the case here) Otherwise, use contact_id.
253 *
254 * @param directoryId directory id of ContactsProvider query
255 * @param contactId contact id
256 * @return UserType indicates the user type of the contact. A directory id or contact id larger
257 * than a thredshold indicates that the contact is stored in Work Profile, but not in
258 * current user. It's a contract by ContactsProvider and check by
259 * Contacts.isEnterpriseDirectoryId and Contacts.isEnterpriseContactId. Currently, only
260 * 2 kinds of users can be detected from the directoryId and contactId as
261 * ContactsProvider can only access current and work user's contacts
262 */
263 public static @UserType long determineUserType(Long directoryId, Long contactId) {
264 // First check directory id
265 if (directoryId != null) {
266 return DirectoryCompat.isEnterpriseDirectoryId(directoryId) ? USER_TYPE_WORK
267 : USER_TYPE_CURRENT;
268 }
269 // Only check contact id if directory id is null
270 if (contactId != null && contactId != 0L
271 && ContactsCompat.isEnterpriseContactId(contactId)) {
272 return USER_TYPE_WORK;
273 } else {
274 return USER_TYPE_CURRENT;
275 }
276
277 }
Yorke Lee2644d942013-10-28 11:05:43 -0700278}