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