1 /* 2 * Copyright (C) 2017 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.calllogutils; 18 19 import android.content.Context; 20 import android.provider.CallLog.Calls; 21 import android.telephony.PhoneNumberUtils; 22 import android.text.TextUtils; 23 import com.android.dialer.calllog.model.CoalescedRow; 24 import com.android.dialer.duo.DuoComponent; 25 import com.android.dialer.spam.Spam; 26 import com.android.dialer.time.Clock; 27 import com.google.common.base.Optional; 28 import com.google.common.collect.Collections2; 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.List; 32 33 /** 34 * Computes the primary text and secondary text for call log entries. 35 * 36 * <p>These text values are shown in the main call log list or in the top item of the bottom sheet 37 * menu. 38 */ 39 public final class CallLogEntryText { 40 41 /** 42 * The primary text for bottom sheets is the same as shown in the entry list. 43 * 44 * <p>(In the entry list, the number of calls and additional icons are displayed as images 45 * following the primary text.) 46 */ buildPrimaryText(Context context, CoalescedRow row)47 public static CharSequence buildPrimaryText(Context context, CoalescedRow row) { 48 // Calls to emergency services should be shown as "Emergency number". 49 if (row.getNumberAttributes().getIsEmergencyNumber()) { 50 return context.getText(R.string.emergency_number); 51 } 52 53 // Otherwise, follow the following order of preferences. 54 // 1st preference: the presentation name, like "Restricted". 55 Optional<String> presentationName = 56 PhoneNumberDisplayUtil.getNameForPresentation(context, row.getNumberPresentation()); 57 if (presentationName.isPresent()) { 58 return presentationName.get(); 59 } 60 61 // 2nd preference: the voicemail tag if the call is one made to a voicemail box. 62 if (row.getIsVoicemailCall() && !TextUtils.isEmpty(row.getVoicemailCallTag())) { 63 return row.getVoicemailCallTag(); 64 } 65 66 // 3rd preference: the name associated with the number. 67 if (!TextUtils.isEmpty(row.getNumberAttributes().getName())) { 68 return row.getNumberAttributes().getName(); 69 } 70 71 // 4th preference: the formatted number. 72 if (!TextUtils.isEmpty(row.getFormattedNumber())) { 73 return PhoneNumberUtils.createTtsSpannable(row.getFormattedNumber()); 74 } 75 76 // Last resort: show "Unknown". 77 return context.getText(R.string.new_call_log_unknown); 78 } 79 80 /** 81 * The secondary text to be shown in the main call log entry list. 82 * 83 * <p>This method first obtains a list of strings to be shown in order and then concatenates them 84 * with " • ". 85 * 86 * <p>Examples: 87 * 88 * <ul> 89 * <li>Mobile, Duo video • 10 min ago 90 * <li>Spam • Mobile • Now 91 * <li>Blocked • Spam • Mobile • Now 92 * </ul> 93 * 94 * @see #buildSecondaryTextListForEntries(Context, Clock, CoalescedRow, boolean) for details. 95 */ buildSecondaryTextForEntries( Context context, Clock clock, CoalescedRow row)96 public static CharSequence buildSecondaryTextForEntries( 97 Context context, Clock clock, CoalescedRow row) { 98 return joinSecondaryTextComponents( 99 buildSecondaryTextListForEntries(context, clock, row, /* abbreviateDateTime = */ true)); 100 } 101 102 /** 103 * Returns a list of strings to be shown in order as the main call log entry's secondary text. 104 * 105 * <p>Rules: 106 * 107 * <ul> 108 * <li>An emergency number: [{Date}] 109 * <li>Number - not blocked, call - not spam: 110 * <p>[{$Label(, Duo video|Carrier video)?|$Location}, {Date}] 111 * <li>Number - blocked, call - not spam: 112 * <p>["Blocked", {$Label(, Duo video|Carrier video)?|$Location}, {Date}] 113 * <li>Number - not blocked, call - spam: 114 * <p>["Spam", {$Label(, Duo video|Carrier video)?}, {Date}] 115 * <li>Number - blocked, call - spam: 116 * <p>["Blocked, Spam", {$Label(, Duo video|Carrier video)?}, {Date}] 117 * </ul> 118 * 119 * <p>Examples: 120 * 121 * <ul> 122 * <li>["Mobile, Duo video", "Now"] 123 * <li>["Duo video", "10 min ago"] 124 * <li>["Mobile", "11:45 PM"] 125 * <li>["Mobile", "Sun"] 126 * <li>["Blocked", "Mobile, Duo video", "Now"] 127 * <li>["Blocked", "Brooklyn, NJ", "10 min ago"] 128 * <li>["Spam", "Mobile", "Now"] 129 * <li>["Spam", "Now"] 130 * <li>["Blocked", "Spam", "Mobile", "Now"] 131 * <li>["Brooklyn, NJ", "Jan 15"] 132 * </ul> 133 * 134 * <p>See {@link CallLogDates#newCallLogTimestampLabel(Context, long, long, boolean)} for date 135 * rules. 136 */ buildSecondaryTextListForEntries( Context context, Clock clock, CoalescedRow row, boolean abbreviateDateTime)137 static List<CharSequence> buildSecondaryTextListForEntries( 138 Context context, Clock clock, CoalescedRow row, boolean abbreviateDateTime) { 139 // For emergency numbers, the secondary text should contain only the timestamp. 140 if (row.getNumberAttributes().getIsEmergencyNumber()) { 141 return Collections.singletonList( 142 CallLogDates.newCallLogTimestampLabel( 143 context, clock.currentTimeMillis(), row.getTimestamp(), abbreviateDateTime)); 144 } 145 146 List<CharSequence> components = new ArrayList<>(); 147 148 if (row.getNumberAttributes().getIsBlocked()) { 149 components.add(context.getText(R.string.new_call_log_secondary_blocked)); 150 } 151 if (Spam.shouldShowAsSpam(row.getNumberAttributes().getIsSpam(), row.getCallType())) { 152 components.add(context.getText(R.string.new_call_log_secondary_spam)); 153 } 154 155 components.add(getNumberTypeLabel(context, row)); 156 157 components.add( 158 CallLogDates.newCallLogTimestampLabel( 159 context, clock.currentTimeMillis(), row.getTimestamp(), abbreviateDateTime)); 160 return components; 161 } 162 163 /** 164 * The secondary text to show in the top item of the bottom sheet. 165 * 166 * <p>This is basically the same as {@link #buildSecondaryTextForEntries(Context, Clock, 167 * CoalescedRow)} except that instead of suffixing with the time of the call, we suffix with the 168 * formatted number. 169 */ buildSecondaryTextForBottomSheet(Context context, CoalescedRow row)170 public static CharSequence buildSecondaryTextForBottomSheet(Context context, CoalescedRow row) { 171 /* 172 * Rules: 173 * For an emergency number: 174 * Number 175 * Number - not blocked, call - not spam: 176 * $Label(, Duo video|Carrier video)?|$Location [• NumberIfNoName]? 177 * Number - blocked, call - not spam: 178 * Blocked • $Label(, Duo video|Carrier video)?|$Location [• NumberIfNoName]? 179 * Number - not blocked, call - spam: 180 * Spam • $Label(, Duo video|Carrier video)? [• NumberIfNoName]? 181 * Number - blocked, call - spam: 182 * Blocked • Spam • $Label(, Duo video|Carrier video)? [• NumberIfNoName]? 183 * 184 * The number is shown at the end if there is no name for the entry. (It is shown in primary 185 * text otherwise.) 186 * 187 * Examples: 188 * Mobile, Duo video • 555-1234 189 * Duo video • 555-1234 190 * Mobile • 555-1234 191 * Blocked • Mobile • 555-1234 192 * Blocked • Brooklyn, NJ • 555-1234 193 * Spam • Mobile • 555-1234 194 * Mobile • 555-1234 195 * Brooklyn, NJ 196 */ 197 198 // For emergency numbers, the secondary text should contain only the number. 199 if (row.getNumberAttributes().getIsEmergencyNumber()) { 200 return !row.getFormattedNumber().isEmpty() 201 ? row.getFormattedNumber() 202 : row.getNumber().getNormalizedNumber(); 203 } 204 205 List<CharSequence> components = new ArrayList<>(); 206 207 if (row.getNumberAttributes().getIsBlocked()) { 208 components.add(context.getText(R.string.new_call_log_secondary_blocked)); 209 } 210 if (Spam.shouldShowAsSpam(row.getNumberAttributes().getIsSpam(), row.getCallType())) { 211 components.add(context.getText(R.string.new_call_log_secondary_spam)); 212 } 213 214 components.add(getNumberTypeLabel(context, row)); 215 216 // If there's a presentation name, we showed it in the primary text and shouldn't show any name 217 // or number here. 218 Optional<String> presentationName = 219 PhoneNumberDisplayUtil.getNameForPresentation(context, row.getNumberPresentation()); 220 if (presentationName.isPresent()) { 221 return joinSecondaryTextComponents(components); 222 } 223 224 if (TextUtils.isEmpty(row.getNumberAttributes().getName())) { 225 // If the name is empty the number is shown as the primary text and there's nothing to add. 226 return joinSecondaryTextComponents(components); 227 } 228 if (TextUtils.isEmpty(row.getFormattedNumber())) { 229 // If there's no number, don't append anything. 230 return joinSecondaryTextComponents(components); 231 } 232 components.add(row.getFormattedNumber()); 233 return joinSecondaryTextComponents(components); 234 } 235 236 /** 237 * Returns a value such as "Mobile, Duo video" without the time of the call or formatted number 238 * appended. 239 * 240 * <p>When the secondary text is shown in call log entry list, this prefix is suffixed with the 241 * time of the call, and when it is shown in a bottom sheet, it is suffixed with the formatted 242 * number. 243 */ getNumberTypeLabel(Context context, CoalescedRow row)244 private static CharSequence getNumberTypeLabel(Context context, CoalescedRow row) { 245 StringBuilder secondaryText = new StringBuilder(); 246 247 // The number type label comes first (e.g., "Mobile", "Work", "Home", etc). 248 String numberTypeLabel = row.getNumberAttributes().getNumberTypeLabel(); 249 secondaryText.append(numberTypeLabel); 250 251 // Add video call info if applicable. 252 if ((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { 253 if (secondaryText.length() > 0) { 254 secondaryText.append(", "); 255 } 256 257 boolean isDuoCall = 258 DuoComponent.get(context).getDuo().isDuoAccount(row.getPhoneAccountComponentName()); 259 secondaryText.append( 260 context.getText( 261 isDuoCall ? R.string.new_call_log_duo_video : R.string.new_call_log_carrier_video)); 262 } 263 264 // Show the location if 265 // (1) there is no number type label, and 266 // (2) the call should not be shown as spam. 267 if (TextUtils.isEmpty(numberTypeLabel) 268 && !Spam.shouldShowAsSpam(row.getNumberAttributes().getIsSpam(), row.getCallType())) { 269 // If number attributes contain a location (obtained from a PhoneLookup), use it instead 270 // of the one from the annotated call log. 271 String location = 272 !TextUtils.isEmpty(row.getNumberAttributes().getGeolocation()) 273 ? row.getNumberAttributes().getGeolocation() 274 : row.getGeocodedLocation(); 275 if (!TextUtils.isEmpty(location)) { 276 if (secondaryText.length() > 0) { 277 secondaryText.append(", "); 278 } 279 secondaryText.append(location); 280 } 281 } 282 283 return secondaryText; 284 } 285 joinSecondaryTextComponents(List<CharSequence> components)286 private static CharSequence joinSecondaryTextComponents(List<CharSequence> components) { 287 return TextUtils.join( 288 " • ", Collections2.filter(components, (text) -> !TextUtils.isEmpty(text))); 289 } 290 } 291