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 SimpleDateFormat format = new SimpleDateFormat(formatPattern); 100 return format.format(new Date(TimeUnit.SECONDS.toMillis(elapsedSeconds))); 101 } 102 103 private static CharSequence formatDurationA11y(Context context, long elapsedSeconds) { 104 Resources res = context.getResources(); 105 if (elapsedSeconds >= 60) { 106 int minutes = (int) (elapsedSeconds / 60); 107 int seconds = (int) elapsedSeconds - minutes * 60; 108 String minutesString = res.getQuantityString(R.plurals.a11y_minutes, minutes); 109 String secondsString = res.getQuantityString(R.plurals.a11y_seconds, seconds); 110 // example output: "1 minute 1 second", "2 minutes 2 seconds", ect. 111 return context.getString( 112 R.string.a11y_call_duration_format, minutes, minutesString, seconds, secondsString); 113 } else { 114 String secondsString = res.getQuantityString(R.plurals.a11y_seconds, (int) elapsedSeconds); 115 // example output: "1 second", "2 seconds" 116 return context.getString( 117 R.string.a11y_call_duration_short_format, elapsedSeconds, secondsString); 118 } 119 } 120 121 /** 122 * Formats a string containing the call duration and the data usage (if specified). 123 * 124 * @param elapsedSeconds Total elapsed seconds. 125 * @param dataUsage Data usage in bytes, or null if not specified. 126 * @return String containing call duration and data usage. 127 */ 128 public static CharSequence formatDurationAndDataUsage( 129 Context context, long elapsedSeconds, long dataUsage) { 130 return formatDurationAndDataUsageInternal( 131 context, formatDuration(context, elapsedSeconds), dataUsage); 132 } 133 134 /** 135 * Formats a string containing the call duration and the data usage (if specified) for TalkBack. 136 * 137 * @param elapsedSeconds Total elapsed seconds. 138 * @param dataUsage Data usage in bytes, or null if not specified. 139 * @return String containing call duration and data usage. 140 */ 141 public static CharSequence formatDurationAndDataUsageA11y( 142 Context context, long elapsedSeconds, long dataUsage) { 143 return formatDurationAndDataUsageInternal( 144 context, formatDurationA11y(context, elapsedSeconds), dataUsage); 145 } 146 147 private static CharSequence formatDurationAndDataUsageInternal( 148 Context context, CharSequence duration, long dataUsage) { 149 List<CharSequence> durationItems = new ArrayList<>(); 150 if (dataUsage > 0) { 151 durationItems.add(duration); 152 durationItems.add(Formatter.formatShortFileSize(context, dataUsage)); 153 return DialerUtils.join(durationItems); 154 } else { 155 return duration; 156 } 157 } 158} 159