Home | History | Annotate | Download | only in utils
      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      */
     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      */
    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
    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