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