Home | History | Annotate | Download | only in detail
      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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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     }
    337 
    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())
    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      */
    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     }
    388 }
    389