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.support.annotation.PluralsRes; 22 import android.telecom.PhoneAccountHandle; 23 import android.telephony.PhoneNumberUtils; 24 import android.text.TextUtils; 25 import com.android.dialer.calllog.model.CoalescedRow; 26 import com.android.dialer.telecom.TelecomUtil; 27 import com.android.dialer.time.Clock; 28 import com.google.common.collect.Collections2; 29 import java.util.List; 30 31 /** Builds descriptions of call log entries for accessibility users. */ 32 public final class CallLogEntryDescriptions { 33 CallLogEntryDescriptions()34 private CallLogEntryDescriptions() {} 35 36 /** 37 * Builds the content description for a call log entry. 38 * 39 * <p>The description is of format<br> 40 * {primary description}, {secondary description}, {phone account description}. 41 * 42 * <ul> 43 * <li>The primary description depends on the number of calls in the entry. For example:<br> 44 * "1 answered call from Jane Smith", or<br> 45 * "2 calls, the latest is an answered call from Jane Smith". 46 * <li>The secondary description is the same as the secondary text for the call log entry, 47 * except that date/time is not abbreviated. For example:<br> 48 * "mobile, 11 minutes ago". 49 * <li>The phone account description is of format "on {phone_account_label}, via {number}". For 50 * example:<br> 51 * "on SIM 1, via 6502531234".<br> 52 * Note that the phone account description will be empty if the device has only one SIM. 53 * </ul> 54 * 55 * <p>An example of the full description can be:<br> 56 * "2 calls, the latest is an answered call from Jane Smith, mobile, 11 minutes ago, on SIM 1, via 57 * 6502531234". 58 */ buildDescriptionForEntry( Context context, Clock clock, CoalescedRow row)59 public static CharSequence buildDescriptionForEntry( 60 Context context, Clock clock, CoalescedRow row) { 61 62 // Build the primary description. 63 // Examples: 64 // (1) For an entry containing only 1 call: 65 // "1 missed call from James Smith". 66 // (2) For entries containing multiple calls: 67 // "2 calls, the latest is a missed call from Jame Smith". 68 CharSequence primaryDescription = 69 TextUtils.expandTemplate( 70 context 71 .getResources() 72 .getQuantityString( 73 getPrimaryDescriptionResIdForCallType(row), 74 row.getCoalescedIds().getCoalescedIdCount()), 75 String.valueOf(row.getCoalescedIds().getCoalescedIdCount()), 76 CallLogEntryText.buildPrimaryText(context, row)); 77 78 // Build the secondary description. 79 // An example: "mobile, 11 minutes ago". 80 CharSequence secondaryDescription = 81 joinSecondaryTextComponents( 82 CallLogEntryText.buildSecondaryTextListForEntries( 83 context, clock, row, /* abbreviateDateTime = */ false)); 84 85 // Build the phone account description. 86 // Note that this description can be an empty string. 87 CharSequence phoneAccountDescription = buildPhoneAccountDescription(context, row); 88 89 return TextUtils.isEmpty(phoneAccountDescription) 90 ? TextUtils.expandTemplate( 91 context 92 .getResources() 93 .getText( 94 R.string.a11y_new_call_log_entry_full_description_without_phone_account_info), 95 primaryDescription, 96 secondaryDescription) 97 : TextUtils.expandTemplate( 98 context 99 .getResources() 100 .getText(R.string.a11y_new_call_log_entry_full_description_with_phone_account_info), 101 primaryDescription, 102 secondaryDescription, 103 phoneAccountDescription); 104 } 105 getPrimaryDescriptionResIdForCallType(CoalescedRow row)106 private static @PluralsRes int getPrimaryDescriptionResIdForCallType(CoalescedRow row) { 107 switch (row.getCallType()) { 108 case Calls.INCOMING_TYPE: 109 case Calls.ANSWERED_EXTERNALLY_TYPE: 110 return R.plurals.a11y_new_call_log_entry_answered_call; 111 case Calls.OUTGOING_TYPE: 112 return R.plurals.a11y_new_call_log_entry_outgoing_call; 113 case Calls.MISSED_TYPE: 114 return R.plurals.a11y_new_call_log_entry_missed_call; 115 case Calls.VOICEMAIL_TYPE: 116 throw new IllegalStateException("Voicemails not expected in call log"); 117 case Calls.BLOCKED_TYPE: 118 return R.plurals.a11y_new_call_log_entry_blocked_call; 119 default: 120 // It is possible for users to end up with calls with unknown call types in their 121 // call history, possibly due to 3rd party call log implementations (e.g. to 122 // distinguish between rejected and missed calls). Instead of crashing, just 123 // assume that all unknown call types are missed calls. 124 return R.plurals.a11y_new_call_log_entry_missed_call; 125 } 126 } 127 buildPhoneAccountDescription(Context context, CoalescedRow row)128 private static CharSequence buildPhoneAccountDescription(Context context, CoalescedRow row) { 129 PhoneAccountHandle phoneAccountHandle = 130 TelecomUtil.composePhoneAccountHandle( 131 row.getPhoneAccountComponentName(), row.getPhoneAccountId()); 132 if (phoneAccountHandle == null) { 133 return ""; 134 } 135 136 String phoneAccountLabel = PhoneAccountUtils.getAccountLabel(context, phoneAccountHandle); 137 if (TextUtils.isEmpty(phoneAccountLabel)) { 138 return ""; 139 } 140 141 if (TextUtils.isEmpty(row.getNumber().getNormalizedNumber())) { 142 return ""; 143 } 144 145 return TextUtils.expandTemplate( 146 context.getResources().getText(R.string.a11y_new_call_log_entry_phone_account), 147 phoneAccountLabel, 148 PhoneNumberUtils.createTtsSpannable(row.getNumber().getNormalizedNumber())); 149 } 150 joinSecondaryTextComponents(List<CharSequence> components)151 private static CharSequence joinSecondaryTextComponents(List<CharSequence> components) { 152 return TextUtils.join( 153 ", ", Collections2.filter(components, (text) -> !TextUtils.isEmpty(text))); 154 } 155 } 156