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 17package com.android.dialer.calllogutils; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.icu.lang.UCharacter; 22import android.icu.text.BreakIterator; 23import android.os.Build.VERSION; 24import android.os.Build.VERSION_CODES; 25import android.text.format.DateUtils; 26import android.text.format.Formatter; 27import com.android.dialer.util.DialerUtils; 28import java.text.SimpleDateFormat; 29import java.util.ArrayList; 30import java.util.Date; 31import java.util.List; 32import java.util.Locale; 33import java.util.concurrent.TimeUnit; 34 35/** Utility class for formatting data and data usage in call log entries. */ 36public class CallEntryFormatter { 37 38 /** 39 * Formats the provided date into a value suitable for display in the current locale. 40 * 41 * <p>For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 42 * may 25,20:02". 43 * 44 * <p>For pre-N devices, the returned value may not start with a capital if the local convention 45 * is to not capitalize day names. On N+ devices, the returned value is always capitalized. 46 */ 47 public static CharSequence formatDate(Context context, long callDateMillis) { 48 CharSequence dateValue = 49 DateUtils.formatDateRange( 50 context, 51 callDateMillis /* startDate */, 52 callDateMillis /* endDate */, 53 DateUtils.FORMAT_SHOW_TIME 54 | DateUtils.FORMAT_SHOW_DATE 55 | DateUtils.FORMAT_SHOW_WEEKDAY 56 | DateUtils.FORMAT_SHOW_YEAR); 57 58 // We want the beginning of the date string to be capitalized, even if the word at the beginning 59 // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” 60 // (not capitalized). To handle this issue we apply title casing to the start of the sentence so 61 // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". 62 // 63 // The ICU library was not available in Android until N, so we can only do this in N+ devices. 64 // Pre-N devices will still see incorrect capitalization in some languages. 65 if (VERSION.SDK_INT < VERSION_CODES.N) { 66 return dateValue; 67 } 68 69 // Using the ICU library is safer than just applying toUpperCase() on the first letter of the 70 // word because in some languages, there can be multiple starting characters which should be 71 // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be 72 // capitalized together. 73 74 // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized like the 75 // month ("May") are not lower-cased as part of the conversion. 76 return UCharacter.toTitleCase( 77 Locale.getDefault(), 78 dateValue.toString(), 79 BreakIterator.getSentenceInstance(), 80 UCharacter.TITLECASE_NO_LOWERCASE); 81 } 82 83 private static CharSequence formatDuration(Context context, long elapsedSeconds) { 84 Resources res = context.getResources(); 85 String formatPattern; 86 if (elapsedSeconds >= 60) { 87 String minutesString = res.getString(R.string.call_details_minutes_abbreviation); 88 String secondsString = res.getString(R.string.call_details_seconds_abbreviation); 89 // example output: "1m 1s" 90 formatPattern = 91 context.getString( 92 R.string.call_duration_format_pattern, "m", minutesString, "s", secondsString); 93 } else { 94 String secondsString = res.getString(R.string.call_details_seconds_abbreviation); 95 // example output: "1s" 96 formatPattern = 97 context.getString(R.string.call_duration_short_format_pattern, "s", secondsString); 98 99 // Temporary work around for a broken Hebrew(iw) translation. 100 if (formatPattern.endsWith("\'\'")) { 101 formatPattern = formatPattern.substring(0, formatPattern.length() - 1); 102 } 103 } 104 105 // If new translation issues arise, we should catch them here to prevent crashes. 106 try { 107 Date date = new Date(TimeUnit.SECONDS.toMillis(elapsedSeconds)); 108 SimpleDateFormat format = new SimpleDateFormat(formatPattern); 109 String duration = format.format(date); 110 111 // SimpleDateFormat cannot display more than 59 minutes, instead it displays MINUTES % 60. 112 // Here we check for that value and replace it with the correct value. 113 if (elapsedSeconds >= TimeUnit.MINUTES.toSeconds(60)) { 114 int minutes = (int) (elapsedSeconds / 60); 115 duration = duration.replaceFirst(Integer.toString(minutes % 60), Integer.toString(minutes)); 116 } 117 return duration; 118 } catch (Exception e) { 119 return ""; 120 } 121 } 122 123 private static CharSequence formatDurationA11y(Context context, long elapsedSeconds) { 124 Resources res = context.getResources(); 125 if (elapsedSeconds >= 60) { 126 int minutes = (int) (elapsedSeconds / 60); 127 int seconds = (int) elapsedSeconds - minutes * 60; 128 String minutesString = res.getQuantityString(R.plurals.a11y_minutes, minutes); 129 String secondsString = res.getQuantityString(R.plurals.a11y_seconds, seconds); 130 // example output: "1 minute 1 second", "2 minutes 2 seconds", ect. 131 return context.getString( 132 R.string.a11y_call_duration_format, minutes, minutesString, seconds, secondsString); 133 } else { 134 String secondsString = res.getQuantityString(R.plurals.a11y_seconds, (int) elapsedSeconds); 135 // example output: "1 second", "2 seconds" 136 return context.getString( 137 R.string.a11y_call_duration_short_format, elapsedSeconds, secondsString); 138 } 139 } 140 141 /** 142 * Formats a string containing the call duration and the data usage (if specified). 143 * 144 * @param elapsedSeconds Total elapsed seconds. 145 * @param dataUsage Data usage in bytes, or null if not specified. 146 * @return String containing call duration and data usage. 147 */ 148 public static CharSequence formatDurationAndDataUsage( 149 Context context, long elapsedSeconds, long dataUsage) { 150 return formatDurationAndDataUsageInternal( 151 context, formatDuration(context, elapsedSeconds), dataUsage); 152 } 153 154 /** 155 * Formats a string containing the call duration and the data usage (if specified) for TalkBack. 156 * 157 * @param elapsedSeconds Total elapsed seconds. 158 * @param dataUsage Data usage in bytes, or null if not specified. 159 * @return String containing call duration and data usage. 160 */ 161 public static CharSequence formatDurationAndDataUsageA11y( 162 Context context, long elapsedSeconds, long dataUsage) { 163 return formatDurationAndDataUsageInternal( 164 context, formatDurationA11y(context, elapsedSeconds), dataUsage); 165 } 166 167 private static CharSequence formatDurationAndDataUsageInternal( 168 Context context, CharSequence duration, long dataUsage) { 169 List<CharSequence> durationItems = new ArrayList<>(); 170 if (dataUsage > 0) { 171 durationItems.add(duration); 172 durationItems.add(Formatter.formatShortFileSize(context, dataUsage)); 173 return DialerUtils.join(durationItems); 174 } else { 175 return duration; 176 } 177 } 178} 179