Home | History | Annotate | Download | only in calllog
      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.dialer.calllog;
     18 
     19 import com.google.common.base.MoreObjects;
     20 import com.google.common.collect.Lists;
     21 
     22 import android.content.Context;
     23 import android.content.res.Resources;
     24 import android.graphics.Typeface;
     25 import android.provider.CallLog.Calls;
     26 import android.provider.ContactsContract.CommonDataKinds.Phone;
     27 import android.support.v4.content.ContextCompat;
     28 import android.telecom.PhoneAccount;
     29 import android.text.TextUtils;
     30 import android.text.format.DateUtils;
     31 import android.view.View;
     32 import android.widget.TextView;
     33 
     34 import com.android.contacts.common.testing.NeededForTesting;
     35 import com.android.contacts.common.util.PhoneNumberHelper;
     36 import com.android.dialer.PhoneCallDetails;
     37 import com.android.dialer.R;
     38 import com.android.dialer.calllog.calllogcache.CallLogCache;
     39 import com.android.dialer.util.DialerUtils;
     40 
     41 import java.util.ArrayList;
     42 import java.util.Calendar;
     43 import java.util.concurrent.TimeUnit;
     44 
     45 /**
     46  * Helper class to fill in the views in {@link PhoneCallDetailsViews}.
     47  */
     48 public class PhoneCallDetailsHelper {
     49 
     50     /** The maximum number of icons will be shown to represent the call types in a group. */
     51     private static final int MAX_CALL_TYPE_ICONS = 3;
     52 
     53     private final Context mContext;
     54     private final Resources mResources;
     55     /** The injected current time in milliseconds since the epoch. Used only by tests. */
     56     private Long mCurrentTimeMillisForTest;
     57 
     58     private CharSequence mPhoneTypeLabelForTest;
     59 
     60     private final CallLogCache mCallLogCache;
     61 
     62     /** Calendar used to construct dates */
     63     private final Calendar mCalendar;
     64 
     65     /**
     66      * List of items to be concatenated together for accessibility descriptions
     67      */
     68     private ArrayList<CharSequence> mDescriptionItems = Lists.newArrayList();
     69 
     70     /**
     71      * Creates a new instance of the helper.
     72      * <p>
     73      * Generally you should have a single instance of this helper in any context.
     74      *
     75      * @param resources used to look up strings
     76      */
     77     public PhoneCallDetailsHelper(
     78             Context context,
     79             Resources resources,
     80             CallLogCache callLogCache) {
     81         mContext = context;
     82         mResources = resources;
     83         mCallLogCache = callLogCache;
     84         mCalendar = Calendar.getInstance();
     85     }
     86 
     87     /** Fills the call details views with content. */
     88     public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details) {
     89         // Display up to a given number of icons.
     90         views.callTypeIcons.clear();
     91         int count = details.callTypes.length;
     92         boolean isVoicemail = false;
     93         for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) {
     94             views.callTypeIcons.add(details.callTypes[index]);
     95             if (index == 0) {
     96                 isVoicemail = details.callTypes[index] == Calls.VOICEMAIL_TYPE;
     97             }
     98         }
     99 
    100         // Show the video icon if the call had video enabled.
    101         views.callTypeIcons.setShowVideo(
    102                 (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO);
    103         views.callTypeIcons.requestLayout();
    104         views.callTypeIcons.setVisibility(View.VISIBLE);
    105 
    106         // Show the total call count only if there are more than the maximum number of icons.
    107         final Integer callCount;
    108         if (count > MAX_CALL_TYPE_ICONS) {
    109             callCount = count;
    110         } else {
    111             callCount = null;
    112         }
    113 
    114         // Set the call count, location, date and if voicemail, set the duration.
    115         setDetailText(views, callCount, details);
    116 
    117         // Set the account label if it exists.
    118         String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle);
    119         if (!TextUtils.isEmpty(details.viaNumber)) {
    120             if (!TextUtils.isEmpty(accountLabel)) {
    121                 accountLabel = mResources.getString(R.string.call_log_via_number_phone_account,
    122                         accountLabel, details.viaNumber);
    123             } else {
    124                 accountLabel = mResources.getString(R.string.call_log_via_number,
    125                         details.viaNumber);
    126             }
    127         }
    128         if (!TextUtils.isEmpty(accountLabel)) {
    129             views.callAccountLabel.setVisibility(View.VISIBLE);
    130             views.callAccountLabel.setText(accountLabel);
    131             int color = mCallLogCache.getAccountColor(details.accountHandle);
    132             if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) {
    133                 int defaultColor = R.color.dialtacts_secondary_text_color;
    134                 views.callAccountLabel.setTextColor(mContext.getResources().getColor(defaultColor));
    135             } else {
    136                 views.callAccountLabel.setTextColor(color);
    137             }
    138         } else {
    139             views.callAccountLabel.setVisibility(View.GONE);
    140         }
    141 
    142         final CharSequence nameText;
    143         final CharSequence displayNumber = details.displayNumber;
    144         if (TextUtils.isEmpty(details.getPreferredName())) {
    145             nameText = displayNumber;
    146             // We have a real phone number as "nameView" so make it always LTR
    147             views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR);
    148         } else {
    149             nameText = details.getPreferredName();
    150         }
    151 
    152         views.nameView.setText(nameText);
    153 
    154         if (isVoicemail) {
    155             views.voicemailTranscriptionView.setText(TextUtils.isEmpty(details.transcription) ? null
    156                     : details.transcription);
    157         }
    158 
    159         // Bold if not read
    160         Typeface typeface = details.isRead ? Typeface.SANS_SERIF : Typeface.DEFAULT_BOLD;
    161         views.nameView.setTypeface(typeface);
    162         views.voicemailTranscriptionView.setTypeface(typeface);
    163         views.callLocationAndDate.setTypeface(typeface);
    164         views.callLocationAndDate.setTextColor(ContextCompat.getColor(mContext, details.isRead ?
    165                 R.color.call_log_detail_color : R.color.call_log_unread_text_color));
    166     }
    167 
    168     /**
    169      * Builds a string containing the call location and date. For voicemail logs only the call date
    170      * is returned because location information is displayed in the call action button
    171      *
    172      * @param details The call details.
    173      * @return The call location and date string.
    174      */
    175     private CharSequence getCallLocationAndDate(PhoneCallDetails details) {
    176         mDescriptionItems.clear();
    177 
    178         if (details.callTypes[0] != Calls.VOICEMAIL_TYPE) {
    179             // Get type of call (ie mobile, home, etc) if known, or the caller's location.
    180             CharSequence callTypeOrLocation = getCallTypeOrLocation(details);
    181 
    182             // Only add the call type or location if its not empty.  It will be empty for unknown
    183             // callers.
    184             if (!TextUtils.isEmpty(callTypeOrLocation)) {
    185                 mDescriptionItems.add(callTypeOrLocation);
    186             }
    187         }
    188 
    189         // The date of this call
    190         mDescriptionItems.add(getCallDate(details));
    191 
    192         // Create a comma separated list from the call type or location, and call date.
    193         return DialerUtils.join(mResources, mDescriptionItems);
    194     }
    195 
    196     /**
    197      * For a call, if there is an associated contact for the caller, return the known call type
    198      * (e.g. mobile, home, work).  If there is no associated contact, attempt to use the caller's
    199      * location if known.
    200      *
    201      * @param details Call details to use.
    202      * @return Type of call (mobile/home) if known, or the location of the caller (if known).
    203      */
    204     public CharSequence getCallTypeOrLocation(PhoneCallDetails details) {
    205         CharSequence numberFormattedLabel = null;
    206         // Only show a label if the number is shown and it is not a SIP address.
    207         if (!TextUtils.isEmpty(details.number)
    208                 && !PhoneNumberHelper.isUriNumber(details.number.toString())
    209                 && !mCallLogCache.isVoicemailNumber(details.accountHandle, details.number)) {
    210 
    211             if (TextUtils.isEmpty(details.namePrimary) && !TextUtils.isEmpty(details.geocode)) {
    212                 numberFormattedLabel = details.geocode;
    213             } else if (!(details.numberType == Phone.TYPE_CUSTOM
    214                     && TextUtils.isEmpty(details.numberLabel))) {
    215                 // Get type label only if it will not be "Custom" because of an empty number label.
    216                 numberFormattedLabel = MoreObjects.firstNonNull(mPhoneTypeLabelForTest,
    217                         Phone.getTypeLabel(mResources, details.numberType, details.numberLabel));
    218             }
    219         }
    220 
    221         if (!TextUtils.isEmpty(details.namePrimary) && TextUtils.isEmpty(numberFormattedLabel)) {
    222             numberFormattedLabel = details.displayNumber;
    223         }
    224         return numberFormattedLabel;
    225     }
    226 
    227     @NeededForTesting
    228     public void setPhoneTypeLabelForTest(CharSequence phoneTypeLabel) {
    229         this.mPhoneTypeLabelForTest = phoneTypeLabel;
    230     }
    231 
    232     /**
    233      * Get the call date/time of the call. For the call log this is relative to the current time.
    234      * e.g. 3 minutes ago. For voicemail, see {@link #getGranularDateTime(PhoneCallDetails)}
    235      *
    236      * @param details Call details to use.
    237      * @return String representing when the call occurred.
    238      */
    239     public CharSequence getCallDate(PhoneCallDetails details) {
    240         if (details.callTypes[0] == Calls.VOICEMAIL_TYPE) {
    241             return getGranularDateTime(details);
    242         }
    243 
    244         return DateUtils.getRelativeTimeSpanString(details.date, getCurrentTimeMillis(),
    245                 DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
    246     }
    247 
    248     /**
    249      * Get the granular version of the call date/time of the call. The result is always in the form
    250      * 'DATE at TIME'. The date value changes based on when the call was created.
    251      *
    252      * If created today, DATE is 'Today'
    253      * If created this year, DATE is 'MMM dd'
    254      * Otherwise, DATE is 'MMM dd, yyyy'
    255      *
    256      * TIME is the localized time format, e.g. 'hh:mm a' or 'HH:mm'
    257      *
    258      * @param details Call details to use
    259      * @return String representing when the call occurred
    260      */
    261     public CharSequence getGranularDateTime(PhoneCallDetails details) {
    262         return mResources.getString(R.string.voicemailCallLogDateTimeFormat,
    263                 getGranularDate(details.date),
    264                 DateUtils.formatDateTime(mContext, details.date, DateUtils.FORMAT_SHOW_TIME));
    265     }
    266 
    267     /**
    268      * Get the granular version of the call date. See {@link #getGranularDateTime(PhoneCallDetails)}
    269      */
    270     private String getGranularDate(long date) {
    271         if (DateUtils.isToday(date)) {
    272             return mResources.getString(R.string.voicemailCallLogToday);
    273         }
    274         return DateUtils.formatDateTime(mContext, date, DateUtils.FORMAT_SHOW_DATE
    275                 | DateUtils.FORMAT_ABBREV_MONTH
    276                 | (shouldShowYear(date) ? DateUtils.FORMAT_SHOW_YEAR : DateUtils.FORMAT_NO_YEAR));
    277     }
    278 
    279     /**
    280      * Determines whether the year should be shown for the given date
    281      *
    282      * @return {@code true} if date is within the current year, {@code false} otherwise
    283      */
    284     private boolean shouldShowYear(long date) {
    285         mCalendar.setTimeInMillis(getCurrentTimeMillis());
    286         int currentYear = mCalendar.get(Calendar.YEAR);
    287         mCalendar.setTimeInMillis(date);
    288         return currentYear != mCalendar.get(Calendar.YEAR);
    289     }
    290 
    291     /** Sets the text of the header view for the details page of a phone call. */
    292     @NeededForTesting
    293     public void setCallDetailsHeader(TextView nameView, PhoneCallDetails details) {
    294         final CharSequence nameText;
    295         if (!TextUtils.isEmpty(details.namePrimary)) {
    296             nameText = details.namePrimary;
    297         } else if (!TextUtils.isEmpty(details.displayNumber)) {
    298             nameText = details.displayNumber;
    299         } else {
    300             nameText = mResources.getString(R.string.unknown);
    301         }
    302 
    303         nameView.setText(nameText);
    304     }
    305 
    306     @NeededForTesting
    307     public void setCurrentTimeForTest(long currentTimeMillis) {
    308         mCurrentTimeMillisForTest = currentTimeMillis;
    309     }
    310 
    311     /**
    312      * Returns the current time in milliseconds since the epoch.
    313      * <p>
    314      * It can be injected in tests using {@link #setCurrentTimeForTest(long)}.
    315      */
    316     private long getCurrentTimeMillis() {
    317         if (mCurrentTimeMillisForTest == null) {
    318             return System.currentTimeMillis();
    319         } else {
    320             return mCurrentTimeMillisForTest;
    321         }
    322     }
    323 
    324     /** Sets the call count, date, and if it is a voicemail, sets the duration. */
    325     private void setDetailText(PhoneCallDetailsViews views, Integer callCount,
    326                                PhoneCallDetails details) {
    327         // Combine the count (if present) and the date.
    328         CharSequence dateText = getCallLocationAndDate(details);
    329         final CharSequence text;
    330         if (callCount != null) {
    331             text = mResources.getString(
    332                     R.string.call_log_item_count_and_date, callCount.intValue(), dateText);
    333         } else {
    334             text = dateText;
    335         }
    336 
    337         if (details.callTypes[0] == Calls.VOICEMAIL_TYPE && details.duration > 0) {
    338             views.callLocationAndDate.setText(mResources.getString(
    339                     R.string.voicemailCallLogDateTimeFormatWithDuration, text,
    340                     getVoicemailDuration(details)));
    341         } else {
    342             views.callLocationAndDate.setText(text);
    343         }
    344 
    345     }
    346 
    347     private String getVoicemailDuration(PhoneCallDetails details) {
    348         long minutes = TimeUnit.SECONDS.toMinutes(details.duration);
    349         long seconds = details.duration - TimeUnit.MINUTES.toSeconds(minutes);
    350         if (minutes > 99) {
    351             minutes = 99;
    352         }
    353         return mResources.getString(R.string.voicemailDurationFormat, minutes, seconds);
    354     }
    355 }
    356