| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.contacts.detail; |
| |
| import com.google.common.collect.Iterables; |
| |
| import com.android.contacts.R; |
| import com.android.contacts.common.model.Contact; |
| import com.android.contacts.common.model.RawContact; |
| import com.android.contacts.common.model.dataitem.DataItem; |
| import com.android.contacts.common.model.dataitem.OrganizationDataItem; |
| import com.android.contacts.common.preference.ContactsPreferences; |
| import com.android.contacts.util.MoreMath; |
| |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Resources; |
| import android.content.res.Resources.NotFoundException; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.provider.ContactsContract.DisplayNameSources; |
| import android.text.BidiFormatter; |
| import android.text.Html; |
| import android.text.TextDirectionHeuristics; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| |
| import java.util.List; |
| |
| /** |
| * This class contains utility methods to bind high-level contact details |
| * (meaning name, phonetic name, job, and attribution) from a |
| * {@link Contact} data object to appropriate {@link View}s. |
| */ |
| public class ContactDisplayUtils { |
| private static final String TAG = "ContactDisplayUtils"; |
| private static BidiFormatter sBidiFormatter = BidiFormatter.getInstance(); |
| |
| /** |
| * Returns the display name of the contact, using the current display order setting. |
| * Returns res/string/missing_name if there is no display name. |
| */ |
| public static CharSequence getDisplayName(Context context, Contact contactData) { |
| ContactsPreferences prefs = new ContactsPreferences(context); |
| final CharSequence displayName = contactData.getDisplayName(); |
| if (prefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) { |
| if (!TextUtils.isEmpty(displayName)) { |
| if (contactData.getDisplayNameSource() == DisplayNameSources.PHONE) { |
| return sBidiFormatter.unicodeWrap( |
| displayName.toString(), TextDirectionHeuristics.LTR); |
| } |
| return displayName; |
| } |
| } else { |
| final CharSequence altDisplayName = contactData.getAltDisplayName(); |
| if (!TextUtils.isEmpty(altDisplayName)) { |
| return altDisplayName; |
| } |
| } |
| return context.getResources().getString(R.string.missing_name); |
| } |
| |
| /** |
| * Returns the phonetic name of the contact or null if there isn't one. |
| */ |
| public static String getPhoneticName(Context context, Contact contactData) { |
| String phoneticName = contactData.getPhoneticName(); |
| if (!TextUtils.isEmpty(phoneticName)) { |
| return phoneticName; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the attribution string for the contact, which may specify the contact directory that |
| * the contact came from. Returns null if there is none applicable. |
| */ |
| public static String getAttribution(Context context, Contact contactData) { |
| if (contactData.isDirectoryEntry()) { |
| String directoryDisplayName = contactData.getDirectoryDisplayName(); |
| String directoryType = contactData.getDirectoryType(); |
| final String displayName; |
| if (!TextUtils.isEmpty(directoryDisplayName)) { |
| displayName = directoryDisplayName; |
| } else if (!TextUtils.isEmpty(directoryType)) { |
| displayName = directoryType; |
| } else { |
| return null; |
| } |
| return context.getString(R.string.contact_directory_description, displayName); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the organization of the contact. If several organizations are given, |
| * the first one is used. Returns null if not applicable. |
| */ |
| public static String getCompany(Context context, Contact contactData) { |
| final boolean displayNameIsOrganization = contactData.getDisplayNameSource() |
| == DisplayNameSources.ORGANIZATION; |
| for (RawContact rawContact : contactData.getRawContacts()) { |
| for (DataItem dataItem : Iterables.filter( |
| rawContact.getDataItems(), OrganizationDataItem.class)) { |
| OrganizationDataItem organization = (OrganizationDataItem) dataItem; |
| final String company = organization.getCompany(); |
| final String title = organization.getTitle(); |
| final String combined; |
| // We need to show company and title in a combined string. However, if the |
| // DisplayName is already the organization, it mirrors company or (if company |
| // is empty title). Make sure we don't show what's already shown as DisplayName |
| if (TextUtils.isEmpty(company)) { |
| combined = displayNameIsOrganization ? null : title; |
| } else { |
| if (TextUtils.isEmpty(title)) { |
| combined = displayNameIsOrganization ? null : company; |
| } else { |
| if (displayNameIsOrganization) { |
| combined = title; |
| } else { |
| combined = context.getString( |
| R.string.organization_company_and_title, |
| company, title); |
| } |
| } |
| } |
| |
| if (!TextUtils.isEmpty(combined)) { |
| return combined; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the starred state of this contact. |
| */ |
| public static void configureStarredImageView(ImageView starredView, boolean isDirectoryEntry, |
| boolean isUserProfile, boolean isStarred) { |
| // Check if the starred state should be visible |
| if (!isDirectoryEntry && !isUserProfile) { |
| starredView.setVisibility(View.VISIBLE); |
| final int resId = isStarred |
| ? R.drawable.btn_star_on_normal_holo_light |
| : R.drawable.btn_star_off_normal_holo_light; |
| starredView.setImageResource(resId); |
| starredView.setTag(isStarred); |
| starredView.setContentDescription(starredView.getResources().getString( |
| isStarred ? R.string.menu_removeStar : R.string.menu_addStar)); |
| } else { |
| starredView.setVisibility(View.GONE); |
| } |
| } |
| |
| /** |
| * Sets the starred state of this contact. |
| */ |
| public static void configureStarredMenuItem(MenuItem starredMenuItem, boolean isDirectoryEntry, |
| boolean isUserProfile, boolean isStarred) { |
| // Check if the starred state should be visible |
| if (!isDirectoryEntry && !isUserProfile) { |
| starredMenuItem.setVisible(true); |
| final int resId = isStarred |
| ? R.drawable.ic_star_24dp |
| : R.drawable.ic_star_outline_24dp; |
| starredMenuItem.setIcon(resId); |
| starredMenuItem.setChecked(isStarred); |
| starredMenuItem.setTitle(isStarred ? R.string.menu_removeStar : R.string.menu_addStar); |
| } else { |
| starredMenuItem.setVisible(false); |
| } |
| } |
| |
| /** |
| * Sets the display name of this contact to the given {@link TextView}. If |
| * there is none, then set the view to gone. |
| */ |
| public static void setDisplayName(Context context, Contact contactData, TextView textView) { |
| if (textView == null) { |
| return; |
| } |
| setDataOrHideIfNone(getDisplayName(context, contactData), textView); |
| } |
| |
| /** |
| * Sets the company and job title of this contact to the given {@link TextView}. If |
| * there is none, then set the view to gone. |
| */ |
| public static void setCompanyName(Context context, Contact contactData, TextView textView) { |
| if (textView == null) { |
| return; |
| } |
| setDataOrHideIfNone(getCompany(context, contactData), textView); |
| } |
| |
| /** |
| * Sets the phonetic name of this contact to the given {@link TextView}. If |
| * there is none, then set the view to gone. |
| */ |
| public static void setPhoneticName(Context context, Contact contactData, TextView textView) { |
| if (textView == null) { |
| return; |
| } |
| setDataOrHideIfNone(getPhoneticName(context, contactData), textView); |
| } |
| |
| /** |
| * Sets the attribution contact to the given {@link TextView}. If |
| * there is none, then set the view to gone. |
| */ |
| public static void setAttribution(Context context, Contact contactData, TextView textView) { |
| if (textView == null) { |
| return; |
| } |
| setDataOrHideIfNone(getAttribution(context, contactData), textView); |
| } |
| |
| /** |
| * Helper function to display the given text in the {@link TextView} or |
| * hides the {@link TextView} if the text is empty or null. |
| */ |
| private static void setDataOrHideIfNone(CharSequence textToDisplay, TextView textView) { |
| if (!TextUtils.isEmpty(textToDisplay)) { |
| textView.setText(textToDisplay); |
| textView.setVisibility(View.VISIBLE); |
| } else { |
| textView.setText(null); |
| textView.setVisibility(View.GONE); |
| } |
| } |
| |
| private static Html.ImageGetter sImageGetter; |
| |
| public static Html.ImageGetter getImageGetter(Context context) { |
| if (sImageGetter == null) { |
| sImageGetter = new DefaultImageGetter(context.getPackageManager()); |
| } |
| return sImageGetter; |
| } |
| |
| /** Fetcher for images from resources to be included in HTML text. */ |
| private static class DefaultImageGetter implements Html.ImageGetter { |
| /** The scheme used to load resources. */ |
| private static final String RES_SCHEME = "res"; |
| |
| private final PackageManager mPackageManager; |
| |
| public DefaultImageGetter(PackageManager packageManager) { |
| mPackageManager = packageManager; |
| } |
| |
| @Override |
| public Drawable getDrawable(String source) { |
| // Returning null means that a default image will be used. |
| Uri uri; |
| try { |
| uri = Uri.parse(source); |
| } catch (Throwable e) { |
| Log.d(TAG, "Could not parse image source: " + source); |
| return null; |
| } |
| if (!RES_SCHEME.equals(uri.getScheme())) { |
| Log.d(TAG, "Image source does not correspond to a resource: " + source); |
| return null; |
| } |
| // The URI authority represents the package name. |
| String packageName = uri.getAuthority(); |
| |
| Resources resources = getResourcesForResourceName(packageName); |
| if (resources == null) { |
| Log.d(TAG, "Could not parse image source: " + source); |
| return null; |
| } |
| |
| List<String> pathSegments = uri.getPathSegments(); |
| if (pathSegments.size() != 1) { |
| Log.d(TAG, "Could not parse image source: " + source); |
| return null; |
| } |
| |
| final String name = pathSegments.get(0); |
| final int resId = resources.getIdentifier(name, "drawable", packageName); |
| |
| if (resId == 0) { |
| // Use the default image icon in this case. |
| Log.d(TAG, "Cannot resolve resource identifier: " + source); |
| return null; |
| } |
| |
| try { |
| return getResourceDrawable(resources, resId); |
| } catch (NotFoundException e) { |
| Log.d(TAG, "Resource not found: " + source, e); |
| return null; |
| } |
| } |
| |
| /** Returns the drawable associated with the given id. */ |
| private Drawable getResourceDrawable(Resources resources, int resId) |
| throws NotFoundException { |
| Drawable drawable = resources.getDrawable(resId); |
| drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); |
| return drawable; |
| } |
| |
| /** Returns the {@link Resources} of the package of the given resource name. */ |
| private Resources getResourcesForResourceName(String packageName) { |
| try { |
| return mPackageManager.getResourcesForApplication(packageName); |
| } catch (NameNotFoundException e) { |
| Log.d(TAG, "Could not find package: " + packageName); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Sets an alpha value on the view. |
| */ |
| public static void setAlphaOnViewBackground(View view, float alpha) { |
| if (view != null) { |
| // Convert alpha layer to a black background HEX color with an alpha value for better |
| // performance (i.e. use setBackgroundColor() instead of setAlpha()) |
| view.setBackgroundColor((int) (MoreMath.clamp(alpha, 0.0f, 1.0f) * 255) << 24); |
| } |
| } |
| |
| /** |
| * Returns the top coordinate of the first item in the {@link ListView}. If the first item |
| * in the {@link ListView} is not visible or there are no children in the list, then return |
| * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the |
| * list cannot have a positive offset. |
| */ |
| public static int getFirstListItemOffset(ListView listView) { |
| if (listView == null || listView.getChildCount() == 0 || |
| listView.getFirstVisiblePosition() != 0) { |
| return Integer.MIN_VALUE; |
| } |
| return listView.getChildAt(0).getTop(); |
| } |
| |
| /** |
| * Tries to scroll the first item in the list to the given offset (this can be a no-op if the |
| * list is already in the correct position). |
| * @param listView that should be scrolled |
| * @param offset which should be <= 0 |
| */ |
| public static void requestToMoveToOffset(ListView listView, int offset) { |
| // We try to offset the list if the first item in the list is showing (which is presumed |
| // to have a larger height than the desired offset). If the first item in the list is not |
| // visible, then we simply do not scroll the list at all (since it can get complicated to |
| // compute how many items in the list will equal the given offset). Potentially |
| // some animation elsewhere will make the transition smoother for the user to compensate |
| // for this simplification. |
| if (listView == null || listView.getChildCount() == 0 || |
| listView.getFirstVisiblePosition() != 0 || offset > 0) { |
| return; |
| } |
| |
| // As an optimization, check if the first item is already at the given offset. |
| if (listView.getChildAt(0).getTop() == offset) { |
| return; |
| } |
| |
| listView.setSelectionFromTop(0, offset); |
| } |
| } |