blob: 85e60261b2b27c3a5e2e1ce9e79885f40627aa8d [file] [log] [blame]
Katherine Kuan79700882011-06-14 17:40:33 -07001/*
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.contacts.detail;
18
Brian Attwellfc50bdd2014-02-04 21:14:02 -080019import com.google.common.collect.Iterables;
20
21import com.android.contacts.R;
22import com.android.contacts.common.model.Contact;
23import com.android.contacts.common.model.RawContact;
24import com.android.contacts.common.model.dataitem.DataItem;
25import com.android.contacts.common.model.dataitem.OrganizationDataItem;
26import com.android.contacts.common.preference.ContactsPreferences;
27import com.android.contacts.util.MoreMath;
28
Katherine Kuan79700882011-06-14 17:40:33 -070029import android.content.Context;
Flavio Lerda65ca8b22011-08-09 20:46:23 +010030import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.content.res.Resources;
33import android.content.res.Resources.NotFoundException;
Flavio Lerda65ca8b22011-08-09 20:46:23 +010034import android.graphics.drawable.Drawable;
Dave Santoro39156002011-07-19 01:18:14 -070035import android.net.Uri;
Katherine Kuan79700882011-06-14 17:40:33 -070036import android.provider.ContactsContract.DisplayNameSources;
Brian Attwellc62cc792014-10-02 12:35:07 -070037import android.text.BidiFormatter;
Dave Santoro39156002011-07-19 01:18:14 -070038import android.text.Html;
Brian Attwellc62cc792014-10-02 12:35:07 -070039import android.text.TextDirectionHeuristics;
Katherine Kuan79700882011-06-14 17:40:33 -070040import android.text.TextUtils;
Flavio Lerda65ca8b22011-08-09 20:46:23 +010041import android.util.Log;
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -080042import android.view.MenuItem;
Katherine Kuan79700882011-06-14 17:40:33 -070043import android.view.View;
Katherine Kuan79700882011-06-14 17:40:33 -070044import android.widget.ImageView;
Katherine Kuan6c0470e2011-08-17 12:33:18 -070045import android.widget.ListView;
Katherine Kuan79700882011-06-14 17:40:33 -070046import android.widget.TextView;
47
Dave Santoro39156002011-07-19 01:18:14 -070048import java.util.List;
49
Katherine Kuan79700882011-06-14 17:40:33 -070050/**
51 * This class contains utility methods to bind high-level contact details
52 * (meaning name, phonetic name, job, and attribution) from a
Maurice Chu851222a2012-06-21 11:43:08 -070053 * {@link Contact} data object to appropriate {@link View}s.
Katherine Kuan79700882011-06-14 17:40:33 -070054 */
Paul Soulos333091a2014-07-22 13:54:41 -070055public class ContactDisplayUtils {
56 private static final String TAG = "ContactDisplayUtils";
Brian Attwellc62cc792014-10-02 12:35:07 -070057 private static BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
Flavio Lerda65ca8b22011-08-09 20:46:23 +010058
Makoto Onukif748d592011-08-18 17:22:39 -070059 /**
Makoto Onuki599700f2011-08-08 18:43:20 -070060 * Returns the display name of the contact, using the current display order setting.
61 * Returns res/string/missing_name if there is no display name.
Katherine Kuan79700882011-06-14 17:40:33 -070062 */
Maurice Chu851222a2012-06-21 11:43:08 -070063 public static CharSequence getDisplayName(Context context, Contact contactData) {
Katherine Kuan79700882011-06-14 17:40:33 -070064 ContactsPreferences prefs = new ContactsPreferences(context);
Alon Alberte09b9912013-09-05 12:58:45 -070065 final CharSequence displayName = contactData.getDisplayName();
Yorke Leec9bb2172014-07-10 11:38:34 -070066 if (prefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
Alon Alberte09b9912013-09-05 12:58:45 -070067 if (!TextUtils.isEmpty(displayName)) {
Brian Attwellc62cc792014-10-02 12:35:07 -070068 if (contactData.getDisplayNameSource() == DisplayNameSources.PHONE) {
69 return sBidiFormatter.unicodeWrap(
70 displayName.toString(), TextDirectionHeuristics.LTR);
71 }
Alon Alberte09b9912013-09-05 12:58:45 -070072 return displayName;
Katherine Kuan79700882011-06-14 17:40:33 -070073 }
Katherine Kuan81281ee2011-07-28 16:20:59 -070074 } else {
Alon Alberte09b9912013-09-05 12:58:45 -070075 final CharSequence altDisplayName = contactData.getAltDisplayName();
76 if (!TextUtils.isEmpty(altDisplayName)) {
77 return altDisplayName;
78 }
Katherine Kuan79700882011-06-14 17:40:33 -070079 }
Alon Alberte09b9912013-09-05 12:58:45 -070080 return context.getResources().getString(R.string.missing_name);
Katherine Kuan79700882011-06-14 17:40:33 -070081 }
82
83 /**
84 * Returns the phonetic name of the contact or null if there isn't one.
85 */
Maurice Chu851222a2012-06-21 11:43:08 -070086 public static String getPhoneticName(Context context, Contact contactData) {
Katherine Kuan79700882011-06-14 17:40:33 -070087 String phoneticName = contactData.getPhoneticName();
88 if (!TextUtils.isEmpty(phoneticName)) {
89 return phoneticName;
90 }
91 return null;
92 }
93
94 /**
Katherine Kuanafd283a2011-08-22 16:55:19 -070095 * Returns the attribution string for the contact, which may specify the contact directory that
96 * the contact came from. Returns null if there is none applicable.
Katherine Kuan79700882011-06-14 17:40:33 -070097 */
Maurice Chu851222a2012-06-21 11:43:08 -070098 public static String getAttribution(Context context, Contact contactData) {
Katherine Kuanafd283a2011-08-22 16:55:19 -070099 if (contactData.isDirectoryEntry()) {
Katherine Kuan79700882011-06-14 17:40:33 -0700100 String directoryDisplayName = contactData.getDirectoryDisplayName();
101 String directoryType = contactData.getDirectoryType();
Yorke Lee2e385832013-09-09 14:42:01 -0700102 final String displayName;
103 if (!TextUtils.isEmpty(directoryDisplayName)) {
104 displayName = directoryDisplayName;
105 } else if (!TextUtils.isEmpty(directoryType)) {
106 displayName = directoryType;
107 } else {
108 return null;
109 }
Katherine Kuan79700882011-06-14 17:40:33 -0700110 return context.getString(R.string.contact_directory_description, displayName);
111 }
112 return null;
113 }
114
115 /**
116 * Returns the organization of the contact. If several organizations are given,
117 * the first one is used. Returns null if not applicable.
118 */
Maurice Chu851222a2012-06-21 11:43:08 -0700119 public static String getCompany(Context context, Contact contactData) {
Katherine Kuan79700882011-06-14 17:40:33 -0700120 final boolean displayNameIsOrganization = contactData.getDisplayNameSource()
121 == DisplayNameSources.ORGANIZATION;
Maurice Chu851222a2012-06-21 11:43:08 -0700122 for (RawContact rawContact : contactData.getRawContacts()) {
123 for (DataItem dataItem : Iterables.filter(
124 rawContact.getDataItems(), OrganizationDataItem.class)) {
125 OrganizationDataItem organization = (OrganizationDataItem) dataItem;
126 final String company = organization.getCompany();
127 final String title = organization.getTitle();
128 final String combined;
129 // We need to show company and title in a combined string. However, if the
130 // DisplayName is already the organization, it mirrors company or (if company
131 // is empty title). Make sure we don't show what's already shown as DisplayName
132 if (TextUtils.isEmpty(company)) {
133 combined = displayNameIsOrganization ? null : title;
134 } else {
135 if (TextUtils.isEmpty(title)) {
136 combined = displayNameIsOrganization ? null : company;
Katherine Kuan79700882011-06-14 17:40:33 -0700137 } else {
Maurice Chu851222a2012-06-21 11:43:08 -0700138 if (displayNameIsOrganization) {
139 combined = title;
Katherine Kuan79700882011-06-14 17:40:33 -0700140 } else {
Maurice Chu851222a2012-06-21 11:43:08 -0700141 combined = context.getString(
142 R.string.organization_company_and_title,
143 company, title);
Katherine Kuan79700882011-06-14 17:40:33 -0700144 }
145 }
Maurice Chu851222a2012-06-21 11:43:08 -0700146 }
Katherine Kuan79700882011-06-14 17:40:33 -0700147
Maurice Chu851222a2012-06-21 11:43:08 -0700148 if (!TextUtils.isEmpty(combined)) {
149 return combined;
Katherine Kuan79700882011-06-14 17:40:33 -0700150 }
151 }
152 }
153 return null;
154 }
155
Katherine Kuan79700882011-06-14 17:40:33 -0700156 /**
Katherine Kuan79700882011-06-14 17:40:33 -0700157 * Sets the starred state of this contact.
158 */
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -0800159 public static void configureStarredImageView(ImageView starredView, boolean isDirectoryEntry,
160 boolean isUserProfile, boolean isStarred) {
Katherine Kuan79700882011-06-14 17:40:33 -0700161 // Check if the starred state should be visible
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -0800162 if (!isDirectoryEntry && !isUserProfile) {
Katherine Kuan79700882011-06-14 17:40:33 -0700163 starredView.setVisibility(View.VISIBLE);
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -0800164 final int resId = isStarred
165 ? R.drawable.btn_star_on_normal_holo_light
166 : R.drawable.btn_star_off_normal_holo_light;
167 starredView.setImageResource(resId);
168 starredView.setTag(isStarred);
169 starredView.setContentDescription(starredView.getResources().getString(
170 isStarred ? R.string.menu_removeStar : R.string.menu_addStar));
Katherine Kuan79700882011-06-14 17:40:33 -0700171 } else {
172 starredView.setVisibility(View.GONE);
173 }
174 }
175
176 /**
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -0800177 * Sets the starred state of this contact.
178 */
179 public static void configureStarredMenuItem(MenuItem starredMenuItem, boolean isDirectoryEntry,
180 boolean isUserProfile, boolean isStarred) {
181 // Check if the starred state should be visible
182 if (!isDirectoryEntry && !isUserProfile) {
183 starredMenuItem.setVisible(true);
184 final int resId = isStarred
Brian Attwelld28851f2014-06-10 13:25:07 -0700185 ? R.drawable.ic_star_24dp
186 : R.drawable.ic_star_outline_24dp;
Daniel Lehmannc42ea4e2012-02-16 21:22:37 -0800187 starredMenuItem.setIcon(resId);
188 starredMenuItem.setChecked(isStarred);
189 starredMenuItem.setTitle(isStarred ? R.string.menu_removeStar : R.string.menu_addStar);
190 } else {
191 starredMenuItem.setVisible(false);
192 }
193 }
194
195 /**
Katherine Kuan2eb969c2011-06-28 11:43:15 -0700196 * Sets the display name of this contact to the given {@link TextView}. If
197 * there is none, then set the view to gone.
198 */
Maurice Chu851222a2012-06-21 11:43:08 -0700199 public static void setDisplayName(Context context, Contact contactData, TextView textView) {
Katherine Kuan2eb969c2011-06-28 11:43:15 -0700200 if (textView == null) {
201 return;
202 }
203 setDataOrHideIfNone(getDisplayName(context, contactData), textView);
204 }
205
206 /**
207 * Sets the company and job title of this contact to the given {@link TextView}. If
208 * there is none, then set the view to gone.
209 */
Maurice Chu851222a2012-06-21 11:43:08 -0700210 public static void setCompanyName(Context context, Contact contactData, TextView textView) {
Katherine Kuan2eb969c2011-06-28 11:43:15 -0700211 if (textView == null) {
212 return;
213 }
214 setDataOrHideIfNone(getCompany(context, contactData), textView);
215 }
216
217 /**
Katherine Kuan79700882011-06-14 17:40:33 -0700218 * Sets the phonetic name of this contact to the given {@link TextView}. If
219 * there is none, then set the view to gone.
220 */
Maurice Chu851222a2012-06-21 11:43:08 -0700221 public static void setPhoneticName(Context context, Contact contactData, TextView textView) {
Katherine Kuan2eb969c2011-06-28 11:43:15 -0700222 if (textView == null) {
223 return;
224 }
Katherine Kuan79700882011-06-14 17:40:33 -0700225 setDataOrHideIfNone(getPhoneticName(context, contactData), textView);
226 }
227
228 /**
229 * Sets the attribution contact to the given {@link TextView}. If
230 * there is none, then set the view to gone.
231 */
Maurice Chu851222a2012-06-21 11:43:08 -0700232 public static void setAttribution(Context context, Contact contactData, TextView textView) {
Katherine Kuan2eb969c2011-06-28 11:43:15 -0700233 if (textView == null) {
234 return;
235 }
Katherine Kuan79700882011-06-14 17:40:33 -0700236 setDataOrHideIfNone(getAttribution(context, contactData), textView);
237 }
238
239 /**
240 * Helper function to display the given text in the {@link TextView} or
241 * hides the {@link TextView} if the text is empty or null.
242 */
243 private static void setDataOrHideIfNone(CharSequence textToDisplay, TextView textView) {
244 if (!TextUtils.isEmpty(textToDisplay)) {
245 textView.setText(textToDisplay);
246 textView.setVisibility(View.VISIBLE);
247 } else {
248 textView.setText(null);
249 textView.setVisibility(View.GONE);
250 }
251 }
252
Makoto Onukida9cdc12012-02-27 16:11:50 -0800253 private static Html.ImageGetter sImageGetter;
254
255 public static Html.ImageGetter getImageGetter(Context context) {
256 if (sImageGetter == null) {
257 sImageGetter = new DefaultImageGetter(context.getPackageManager());
258 }
259 return sImageGetter;
260 }
261
Flavio Lerda65ca8b22011-08-09 20:46:23 +0100262 /** Fetcher for images from resources to be included in HTML text. */
263 private static class DefaultImageGetter implements Html.ImageGetter {
264 /** The scheme used to load resources. */
265 private static final String RES_SCHEME = "res";
266
267 private final PackageManager mPackageManager;
268
269 public DefaultImageGetter(PackageManager packageManager) {
270 mPackageManager = packageManager;
271 }
272
273 @Override
274 public Drawable getDrawable(String source) {
275 // Returning null means that a default image will be used.
276 Uri uri;
277 try {
278 uri = Uri.parse(source);
279 } catch (Throwable e) {
280 Log.d(TAG, "Could not parse image source: " + source);
281 return null;
282 }
283 if (!RES_SCHEME.equals(uri.getScheme())) {
284 Log.d(TAG, "Image source does not correspond to a resource: " + source);
285 return null;
286 }
287 // The URI authority represents the package name.
288 String packageName = uri.getAuthority();
289
290 Resources resources = getResourcesForResourceName(packageName);
291 if (resources == null) {
292 Log.d(TAG, "Could not parse image source: " + source);
293 return null;
294 }
295
296 List<String> pathSegments = uri.getPathSegments();
297 if (pathSegments.size() != 1) {
298 Log.d(TAG, "Could not parse image source: " + source);
299 return null;
300 }
301
302 final String name = pathSegments.get(0);
303 final int resId = resources.getIdentifier(name, "drawable", packageName);
304
305 if (resId == 0) {
306 // Use the default image icon in this case.
307 Log.d(TAG, "Cannot resolve resource identifier: " + source);
308 return null;
309 }
310
311 try {
312 return getResourceDrawable(resources, resId);
313 } catch (NotFoundException e) {
314 Log.d(TAG, "Resource not found: " + source, e);
315 return null;
316 }
317 }
318
319 /** Returns the drawable associated with the given id. */
320 private Drawable getResourceDrawable(Resources resources, int resId)
321 throws NotFoundException {
322 Drawable drawable = resources.getDrawable(resId);
323 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
324 return drawable;
325 }
326
327 /** Returns the {@link Resources} of the package of the given resource name. */
328 private Resources getResourcesForResourceName(String packageName) {
329 try {
330 return mPackageManager.getResourcesForApplication(packageName);
331 } catch (NameNotFoundException e) {
332 Log.d(TAG, "Could not find package: " + packageName);
333 return null;
334 }
335 }
336 }
Katherine Kuan25594d62011-08-12 16:05:15 -0700337
338 /**
339 * Sets an alpha value on the view.
340 */
341 public static void setAlphaOnViewBackground(View view, float alpha) {
342 if (view != null) {
343 // Convert alpha layer to a black background HEX color with an alpha value for better
344 // performance (i.e. use setBackgroundColor() instead of setAlpha())
Josh Gargus187c8162012-03-13 17:06:53 -0700345 view.setBackgroundColor((int) (MoreMath.clamp(alpha, 0.0f, 1.0f) * 255) << 24);
Katherine Kuan25594d62011-08-12 16:05:15 -0700346 }
347 }
Katherine Kuan6c0470e2011-08-17 12:33:18 -0700348
349 /**
350 * Returns the top coordinate of the first item in the {@link ListView}. If the first item
351 * in the {@link ListView} is not visible or there are no children in the list, then return
352 * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the
353 * list cannot have a positive offset.
354 */
355 public static int getFirstListItemOffset(ListView listView) {
356 if (listView == null || listView.getChildCount() == 0 ||
357 listView.getFirstVisiblePosition() != 0) {
358 return Integer.MIN_VALUE;
359 }
360 return listView.getChildAt(0).getTop();
361 }
362
363 /**
364 * Tries to scroll the first item in the list to the given offset (this can be a no-op if the
365 * list is already in the correct position).
366 * @param listView that should be scrolled
367 * @param offset which should be <= 0
368 */
369 public static void requestToMoveToOffset(ListView listView, int offset) {
370 // We try to offset the list if the first item in the list is showing (which is presumed
371 // to have a larger height than the desired offset). If the first item in the list is not
372 // visible, then we simply do not scroll the list at all (since it can get complicated to
373 // compute how many items in the list will equal the given offset). Potentially
374 // some animation elsewhere will make the transition smoother for the user to compensate
375 // for this simplification.
376 if (listView == null || listView.getChildCount() == 0 ||
377 listView.getFirstVisiblePosition() != 0 || offset > 0) {
378 return;
379 }
380
381 // As an optimization, check if the first item is already at the given offset.
382 if (listView.getChildAt(0).getTop() == offset) {
383 return;
384 }
385
386 listView.setSelectionFromTop(0, offset);
387 }
Flavio Lerda22cb6632011-08-03 22:59:58 +0100388}