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.res.Resources;
20import android.provider.CallLog.Calls;
21import android.support.annotation.WorkerThread;
22import android.text.SpannableStringBuilder;
23import android.text.TextUtils;
24import com.android.dialer.app.R;
25import com.android.dialer.app.calllog.calllogcache.CallLogCache;
26import com.android.dialer.calllogutils.PhoneCallDetails;
27import com.android.dialer.common.Assert;
28import com.android.dialer.common.LogUtil;
29import com.android.dialer.compat.AppCompatConstants;
30
31/** Helper class to fill in the views of a call log entry. */
32/* package */ class CallLogListItemHelper {
33
34  /** Helper for populating the details of a phone call. */
35  private final PhoneCallDetailsHelper mPhoneCallDetailsHelper;
36  /** Resources to look up strings. */
37  private final Resources mResources;
38
39  private final CallLogCache mCallLogCache;
40
41  /**
42   * Creates a new helper instance.
43   *
44   * @param phoneCallDetailsHelper used to set the details of a phone call
45   * @param resources The object from which resources can be retrieved
46   * @param callLogCache A cache for values retrieved from telecom/telephony
47   */
48  public CallLogListItemHelper(
49      PhoneCallDetailsHelper phoneCallDetailsHelper,
50      Resources resources,
51      CallLogCache callLogCache) {
52    mPhoneCallDetailsHelper = phoneCallDetailsHelper;
53    mResources = resources;
54    mCallLogCache = callLogCache;
55  }
56
57  /**
58   * Update phone call details. This is called before any drawing to avoid expensive operation on UI
59   * thread.
60   *
61   * @param details
62   */
63  @WorkerThread
64  public void updatePhoneCallDetails(PhoneCallDetails details) {
65    Assert.isWorkerThread();
66    details.callLocationAndDate = mPhoneCallDetailsHelper.getCallLocationAndDate(details);
67    details.callDescription = getCallDescription(details);
68  }
69
70  /**
71   * Sets the name, label, and number for a contact.
72   *
73   * @param views the views to populate
74   * @param details the details of a phone call needed to fill in the data
75   */
76  public void setPhoneCallDetails(CallLogListItemViewHolder views, PhoneCallDetails details) {
77    mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details);
78
79    // Set the accessibility text for the contact badge
80    views.quickContactView.setContentDescription(getContactBadgeDescription(details));
81
82    // Set the primary action accessibility description
83    views.primaryActionView.setContentDescription(details.callDescription);
84
85    // Cache name or number of caller.  Used when setting the content descriptions of buttons
86    // when the actions ViewStub is inflated.
87    views.nameOrNumber = getNameOrNumber(details);
88
89    // The call type or Location associated with the call. Use when setting text for a
90    // voicemail log's call button
91    views.callTypeOrLocation = mPhoneCallDetailsHelper.getCallTypeOrLocation(details);
92
93    // Cache country iso. Used for number filtering.
94    views.countryIso = details.countryIso;
95
96    views.updatePhoto();
97  }
98
99  /**
100   * Sets the accessibility descriptions for the action buttons in the action button ViewStub.
101   *
102   * @param views The views associated with the current call log entry.
103   */
104  public void setActionContentDescriptions(CallLogListItemViewHolder views) {
105    if (views.nameOrNumber == null) {
106      LogUtil.e(
107          "CallLogListItemHelper.setActionContentDescriptions",
108          "setActionContentDescriptions; name or number is null.");
109    }
110
111    // Calling expandTemplate with a null parameter will cause a NullPointerException.
112    // Although we don't expect a null name or number, it is best to protect against it.
113    CharSequence nameOrNumber = views.nameOrNumber == null ? "" : views.nameOrNumber;
114
115    views.videoCallButtonView.setContentDescription(
116        TextUtils.expandTemplate(
117            mResources.getString(R.string.description_video_call_action), nameOrNumber));
118
119    views.createNewContactButtonView.setContentDescription(
120        TextUtils.expandTemplate(
121            mResources.getString(R.string.description_create_new_contact_action), nameOrNumber));
122
123    views.addToExistingContactButtonView.setContentDescription(
124        TextUtils.expandTemplate(
125            mResources.getString(R.string.description_add_to_existing_contact_action),
126            nameOrNumber));
127
128    views.detailsButtonView.setContentDescription(
129        TextUtils.expandTemplate(
130            mResources.getString(R.string.description_details_action), nameOrNumber));
131  }
132
133  /**
134   * Returns the accessibility description for the contact badge for a call log entry.
135   *
136   * @param details Details of call.
137   * @return Accessibility description.
138   */
139  private CharSequence getContactBadgeDescription(PhoneCallDetails details) {
140    if (details.isSpam) {
141      return mResources.getString(
142          R.string.description_spam_contact_details, getNameOrNumber(details));
143    }
144    return mResources.getString(R.string.description_contact_details, getNameOrNumber(details));
145  }
146
147  /**
148   * Returns the accessibility description of the "return call/call" action for a call log entry.
149   * Accessibility text is a combination of: {Voicemail Prefix}. {Number of Calls}. {Caller
150   * information} {Phone Account}. If most recent call is a voicemail, {Voicemail Prefix} is "New
151   * Voicemail.", otherwise "".
152   *
153   * <p>If more than one call for the caller, {Number of Calls} is: "{number of calls} calls.",
154   * otherwise "".
155   *
156   * <p>The {Caller Information} references the most recent call associated with the caller. For
157   * incoming calls: If missed call: Missed call from {Name/Number} {Call Type} {Call Time}. If
158   * answered call: Answered call from {Name/Number} {Call Type} {Call Time}.
159   *
160   * <p>For outgoing calls: If outgoing: Call to {Name/Number] {Call Type} {Call Time}.
161   *
162   * <p>Where: {Name/Number} is the name or number of the caller (as shown in call log). {Call type}
163   * is the contact phone number type (eg mobile) or location. {Call Time} is the time since the
164   * last call for the contact occurred.
165   *
166   * <p>The {Phone Account} refers to the account/SIM through which the call was placed or received
167   * in multi-SIM devices.
168   *
169   * <p>Examples: 3 calls. New Voicemail. Missed call from Joe Smith mobile 2 hours ago on SIM 1.
170   *
171   * <p>2 calls. Answered call from John Doe mobile 1 hour ago.
172   *
173   * @param details Details of call.
174   * @return Return call action description.
175   */
176  public CharSequence getCallDescription(PhoneCallDetails details) {
177    // Get the name or number of the caller.
178    final CharSequence nameOrNumber = getNameOrNumber(details);
179
180    // Get the call type or location of the caller; null if not applicable
181    final CharSequence typeOrLocation = mPhoneCallDetailsHelper.getCallTypeOrLocation(details);
182
183    // Get the time/date of the call
184    final CharSequence timeOfCall = mPhoneCallDetailsHelper.getCallDate(details);
185
186    SpannableStringBuilder callDescription = new SpannableStringBuilder();
187
188    // Add number of calls if more than one.
189    if (details.callTypes.length > 1) {
190      callDescription.append(
191          mResources.getString(R.string.description_num_calls, details.callTypes.length));
192    }
193
194    // If call had video capabilities, add the "Video Call" string.
195    if ((details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
196      callDescription.append(mResources.getString(R.string.description_video_call));
197    }
198
199    String accountLabel = mCallLogCache.getAccountLabel(details.accountHandle);
200    CharSequence onAccountLabel =
201        PhoneCallDetails.createAccountLabelDescription(mResources, details.viaNumber, accountLabel);
202
203    int stringID = getCallDescriptionStringID(details.callTypes, details.isRead);
204    callDescription.append(
205        TextUtils.expandTemplate(
206            mResources.getString(stringID),
207            nameOrNumber,
208            typeOrLocation == null ? "" : typeOrLocation,
209            timeOfCall,
210            onAccountLabel));
211
212    return callDescription;
213  }
214
215  /**
216   * Determine the appropriate string ID to describe a call for accessibility purposes.
217   *
218   * @param callTypes The type of call corresponding to this entry or multiple if this entry
219   *     represents multiple calls grouped together.
220   * @param isRead If the entry is a voicemail, {@code true} if the voicemail is read.
221   * @return String resource ID to use.
222   */
223  public int getCallDescriptionStringID(int[] callTypes, boolean isRead) {
224    int lastCallType = getLastCallType(callTypes);
225    int stringID;
226
227    if (lastCallType == AppCompatConstants.CALLS_MISSED_TYPE) {
228      //Message: Missed call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>,
229      //<PhoneAccount>.
230      stringID = R.string.description_incoming_missed_call;
231    } else if (lastCallType == AppCompatConstants.CALLS_INCOMING_TYPE) {
232      //Message: Answered call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>,
233      //<PhoneAccount>.
234      stringID = R.string.description_incoming_answered_call;
235    } else if (lastCallType == AppCompatConstants.CALLS_VOICEMAIL_TYPE) {
236      //Message: (Unread) [V/v]oicemail from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>,
237      //<PhoneAccount>.
238      stringID =
239          isRead ? R.string.description_read_voicemail : R.string.description_unread_voicemail;
240    } else {
241      //Message: Call to <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>, <PhoneAccount>.
242      stringID = R.string.description_outgoing_call;
243    }
244    return stringID;
245  }
246
247  /**
248   * Determine the call type for the most recent call.
249   *
250   * @param callTypes Call types to check.
251   * @return Call type.
252   */
253  private int getLastCallType(int[] callTypes) {
254    if (callTypes.length > 0) {
255      return callTypes[0];
256    } else {
257      return Calls.MISSED_TYPE;
258    }
259  }
260
261  /**
262   * Return the name or number of the caller specified by the details.
263   *
264   * @param details Call details
265   * @return the name (if known) of the caller, otherwise the formatted number.
266   */
267  private CharSequence getNameOrNumber(PhoneCallDetails details) {
268    final CharSequence recipient;
269    if (!TextUtils.isEmpty(details.getPreferredName())) {
270      recipient = details.getPreferredName();
271    } else {
272      recipient = details.displayNumber;
273    }
274    return recipient;
275  }
276}
277