• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.contacts.detail;
18 
19 import com.google.common.collect.Iterables;
20 
21 import com.android.contacts.R;
22 import com.android.contacts.common.model.Contact;
23 import com.android.contacts.common.model.RawContact;
24 import com.android.contacts.common.model.dataitem.DataItem;
25 import com.android.contacts.common.model.dataitem.OrganizationDataItem;
26 import com.android.contacts.common.preference.ContactsPreferences;
27 import com.android.contacts.util.MoreMath;
28 
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.NameNotFoundException;
32 import android.content.res.Resources;
33 import android.content.res.Resources.NotFoundException;
34 import android.graphics.drawable.Drawable;
35 import android.net.Uri;
36 import android.provider.ContactsContract.DisplayNameSources;
37 import android.text.BidiFormatter;
38 import android.text.Html;
39 import android.text.TextDirectionHeuristics;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.widget.ImageView;
45 import android.widget.ListView;
46 import android.widget.TextView;
47 
48 import java.util.List;
49 
50 /**
51  * This class contains utility methods to bind high-level contact details
52  * (meaning name, phonetic name, job, and attribution) from a
53  * {@link Contact} data object to appropriate {@link View}s.
54  */
55 public class ContactDisplayUtils {
56     private static final String TAG = "ContactDisplayUtils";
57     private static BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
58 
59     /**
60      * 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.
62      */
getDisplayName(Context context, Contact contactData)63     public static CharSequence getDisplayName(Context context, Contact contactData) {
64         ContactsPreferences prefs = new ContactsPreferences(context);
65         final CharSequence displayName = contactData.getDisplayName();
66         if (prefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
67             if (!TextUtils.isEmpty(displayName)) {
68                 if (contactData.getDisplayNameSource() == DisplayNameSources.PHONE) {
69                     return sBidiFormatter.unicodeWrap(
70                             displayName.toString(), TextDirectionHeuristics.LTR);
71                 }
72                 return displayName;
73             }
74         } else {
75             final CharSequence altDisplayName = contactData.getAltDisplayName();
76             if (!TextUtils.isEmpty(altDisplayName)) {
77                 return altDisplayName;
78             }
79         }
80         return context.getResources().getString(R.string.missing_name);
81     }
82 
83     /**
84      * Returns the phonetic name of the contact or null if there isn't one.
85      */
getPhoneticName(Context context, Contact contactData)86     public static String getPhoneticName(Context context, Contact contactData) {
87         String phoneticName = contactData.getPhoneticName();
88         if (!TextUtils.isEmpty(phoneticName)) {
89             return phoneticName;
90         }
91         return null;
92     }
93 
94     /**
95      * 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.
97      */
getAttribution(Context context, Contact contactData)98     public static String getAttribution(Context context, Contact contactData) {
99         if (contactData.isDirectoryEntry()) {
100             String directoryDisplayName = contactData.getDirectoryDisplayName();
101             String directoryType = contactData.getDirectoryType();
102             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             }
110             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      */
getCompany(Context context, Contact contactData)119     public static String getCompany(Context context, Contact contactData) {
120         final boolean displayNameIsOrganization = contactData.getDisplayNameSource()
121                 == DisplayNameSources.ORGANIZATION;
122         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;
137                     } else {
138                         if (displayNameIsOrganization) {
139                             combined = title;
140                         } else {
141                             combined = context.getString(
142                                     R.string.organization_company_and_title,
143                                     company, title);
144                         }
145                     }
146                 }
147 
148                 if (!TextUtils.isEmpty(combined)) {
149                     return combined;
150                 }
151             }
152         }
153         return null;
154     }
155 
156     /**
157      * Sets the starred state of this contact.
158      */
configureStarredImageView(ImageView starredView, boolean isDirectoryEntry, boolean isUserProfile, boolean isStarred)159     public static void configureStarredImageView(ImageView starredView, boolean isDirectoryEntry,
160             boolean isUserProfile, boolean isStarred) {
161         // Check if the starred state should be visible
162         if (!isDirectoryEntry && !isUserProfile) {
163             starredView.setVisibility(View.VISIBLE);
164             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));
171         } else {
172             starredView.setVisibility(View.GONE);
173         }
174     }
175 
176     /**
177      * Sets the starred state of this contact.
178      */
configureStarredMenuItem(MenuItem starredMenuItem, boolean isDirectoryEntry, boolean isUserProfile, boolean isStarred)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
185                     ? R.drawable.ic_star_24dp
186                     : R.drawable.ic_star_outline_24dp;
187             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     /**
196      * Sets the display name of this contact to the given {@link TextView}. If
197      * there is none, then set the view to gone.
198      */
setDisplayName(Context context, Contact contactData, TextView textView)199     public static void setDisplayName(Context context, Contact contactData, TextView textView) {
200         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      */
setCompanyName(Context context, Contact contactData, TextView textView)210     public static void setCompanyName(Context context, Contact contactData, TextView textView) {
211         if (textView == null) {
212             return;
213         }
214         setDataOrHideIfNone(getCompany(context, contactData), textView);
215     }
216 
217     /**
218      * Sets the phonetic name of this contact to the given {@link TextView}. If
219      * there is none, then set the view to gone.
220      */
setPhoneticName(Context context, Contact contactData, TextView textView)221     public static void setPhoneticName(Context context, Contact contactData, TextView textView) {
222         if (textView == null) {
223             return;
224         }
225         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      */
setAttribution(Context context, Contact contactData, TextView textView)232     public static void setAttribution(Context context, Contact contactData, TextView textView) {
233         if (textView == null) {
234             return;
235         }
236         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      */
setDataOrHideIfNone(CharSequence textToDisplay, TextView textView)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 
253     private static Html.ImageGetter sImageGetter;
254 
getImageGetter(Context context)255     public static Html.ImageGetter getImageGetter(Context context) {
256         if (sImageGetter == null) {
257             sImageGetter = new DefaultImageGetter(context.getPackageManager());
258         }
259         return sImageGetter;
260     }
261 
262     /** 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 
DefaultImageGetter(PackageManager packageManager)269         public DefaultImageGetter(PackageManager packageManager) {
270             mPackageManager = packageManager;
271         }
272 
273         @Override
getDrawable(String source)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. */
getResourceDrawable(Resources resources, int resId)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. */
getResourcesForResourceName(String packageName)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     }
337 
338     /**
339      * Sets an alpha value on the view.
340      */
setAlphaOnViewBackground(View view, float alpha)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())
345             view.setBackgroundColor((int) (MoreMath.clamp(alpha, 0.0f, 1.0f) * 255) << 24);
346         }
347     }
348 
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      */
getFirstListItemOffset(ListView listView)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      */
requestToMoveToOffset(ListView listView, int offset)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     }
388 }
389