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