1 /* 2 * Copyright (C) 2018 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.settingslib.utils; 18 19 import android.content.Context; 20 import android.icu.text.MeasureFormat; 21 import android.icu.text.MeasureFormat.FormatWidth; 22 import android.icu.text.RelativeDateTimeFormatter; 23 import android.icu.text.RelativeDateTimeFormatter.RelativeUnit; 24 import android.icu.util.Measure; 25 import android.icu.util.MeasureUnit; 26 import android.icu.util.ULocale; 27 import android.text.SpannableStringBuilder; 28 import android.text.Spanned; 29 import android.text.style.TtsSpan; 30 31 import com.android.settingslib.R; 32 33 import java.util.ArrayList; 34 import java.util.Locale; 35 36 /** Utility class for generally useful string methods **/ 37 public class StringUtil { 38 39 public static final int SECONDS_PER_MINUTE = 60; 40 public static final int SECONDS_PER_HOUR = 60 * 60; 41 public static final int SECONDS_PER_DAY = 24 * 60 * 60; 42 43 /** 44 * Returns elapsed time for the given millis, in the following format: 45 * 2 days, 5 hr, 40 min, 29 sec 46 * 47 * @param context the application context 48 * @param millis the elapsed time in milli seconds 49 * @param withSeconds include seconds? 50 * @return the formatted elapsed time 51 */ formatElapsedTime(Context context, double millis, boolean withSeconds)52 public static CharSequence formatElapsedTime(Context context, double millis, 53 boolean withSeconds) { 54 SpannableStringBuilder sb = new SpannableStringBuilder(); 55 int seconds = (int) Math.floor(millis / 1000); 56 if (!withSeconds) { 57 // Round up. 58 seconds += 30; 59 } 60 61 int days = 0, hours = 0, minutes = 0; 62 if (seconds >= SECONDS_PER_DAY) { 63 days = seconds / SECONDS_PER_DAY; 64 seconds -= days * SECONDS_PER_DAY; 65 } 66 if (seconds >= SECONDS_PER_HOUR) { 67 hours = seconds / SECONDS_PER_HOUR; 68 seconds -= hours * SECONDS_PER_HOUR; 69 } 70 if (seconds >= SECONDS_PER_MINUTE) { 71 minutes = seconds / SECONDS_PER_MINUTE; 72 seconds -= minutes * SECONDS_PER_MINUTE; 73 } 74 75 final ArrayList<Measure> measureList = new ArrayList(4); 76 if (days > 0) { 77 measureList.add(new Measure(days, MeasureUnit.DAY)); 78 } 79 if (hours > 0) { 80 measureList.add(new Measure(hours, MeasureUnit.HOUR)); 81 } 82 if (minutes > 0) { 83 measureList.add(new Measure(minutes, MeasureUnit.MINUTE)); 84 } 85 if (withSeconds && seconds > 0) { 86 measureList.add(new Measure(seconds, MeasureUnit.SECOND)); 87 } 88 if (measureList.size() == 0) { 89 // Everything addable was zero, so nothing was added. We add a zero. 90 measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE)); 91 } 92 final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]); 93 94 final Locale locale = context.getResources().getConfiguration().locale; 95 final MeasureFormat measureFormat = MeasureFormat.getInstance( 96 locale, FormatWidth.SHORT); 97 sb.append(measureFormat.formatMeasures(measureArray)); 98 99 if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { 100 // Add ttsSpan if it only have minute value, because it will be read as "meters" 101 final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) 102 .setUnit("minute").build(); 103 sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 104 } 105 106 return sb; 107 } 108 109 /** 110 * Returns relative time for the given millis in the past with different format style. 111 * In a short format such as "2 days ago", "5 hr. ago", "40 min. ago", or "29 sec. ago". 112 * In a long format such as "2 days ago", "5 hours ago", "40 minutes ago" or "29 seconds ago". 113 * 114 * <p>The unit is chosen to have good information value while only using one unit. So 27 hours 115 * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as 116 * "2 days ago". 117 * 118 * @param context the application context 119 * @param millis the elapsed time in milli seconds 120 * @param withSeconds include seconds? 121 * @param formatStyle format style 122 * @return the formatted elapsed time 123 */ formatRelativeTime(Context context, double millis, boolean withSeconds, RelativeDateTimeFormatter.Style formatStyle)124 public static CharSequence formatRelativeTime(Context context, double millis, 125 boolean withSeconds, RelativeDateTimeFormatter.Style formatStyle) { 126 final int seconds = (int) Math.floor(millis / 1000); 127 final RelativeUnit unit; 128 final int value; 129 if (withSeconds && seconds < 2 * SECONDS_PER_MINUTE) { 130 return context.getResources().getString(R.string.time_unit_just_now); 131 } else if (seconds < 2 * SECONDS_PER_HOUR) { 132 unit = RelativeUnit.MINUTES; 133 value = (seconds + SECONDS_PER_MINUTE / 2) 134 / SECONDS_PER_MINUTE; 135 } else if (seconds < 2 * SECONDS_PER_DAY) { 136 unit = RelativeUnit.HOURS; 137 value = (seconds + SECONDS_PER_HOUR / 2) 138 / SECONDS_PER_HOUR; 139 } else { 140 unit = RelativeUnit.DAYS; 141 value = (seconds + SECONDS_PER_DAY / 2) 142 / SECONDS_PER_DAY; 143 } 144 145 final Locale locale = context.getResources().getConfiguration().locale; 146 final RelativeDateTimeFormatter formatter = RelativeDateTimeFormatter.getInstance( 147 ULocale.forLocale(locale), 148 null /* default NumberFormat */, 149 formatStyle, 150 android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE); 151 152 return formatter.format(value, RelativeDateTimeFormatter.Direction.LAST, unit); 153 } 154 155 /** 156 * Returns relative time for the given millis in the past, in a long format such as "2 days 157 * ago", "5 hours ago", "40 minutes ago" or "29 seconds ago". 158 * 159 * <p>The unit is chosen to have good information value while only using one unit. So 27 hours 160 * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as 161 * "2 days ago". 162 * 163 * @param context the application context 164 * @param millis the elapsed time in milli seconds 165 * @param withSeconds include seconds? 166 * @return the formatted elapsed time 167 * @deprecated use {@link #formatRelativeTime(Context, double, boolean, 168 * RelativeDateTimeFormatter.Style)} instead. 169 */ 170 @Deprecated formatRelativeTime(Context context, double millis, boolean withSeconds)171 public static CharSequence formatRelativeTime(Context context, double millis, 172 boolean withSeconds) { 173 return formatRelativeTime(context, millis, withSeconds, 174 RelativeDateTimeFormatter.Style.LONG); 175 } 176 } 177