summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
blob: 5ee919bfe5387a55ba9854dc387f6cb83a5d49a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.utils;

import android.content.Context;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.text.MessageFormat;
import android.icu.text.RelativeDateTimeFormatter;
import android.icu.text.RelativeDateTimeFormatter.RelativeUnit;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.icu.util.ULocale;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.TtsSpan;

import com.android.settingslib.R;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/** Utility class for generally useful string methods **/
public class StringUtil {

    public static final int SECONDS_PER_MINUTE = 60;
    public static final int SECONDS_PER_HOUR = 60 * 60;
    public static final int SECONDS_PER_DAY = 24 * 60 * 60;

    private static final int LIMITED_TIME_UNIT_COUNT = 2;

    /**
     * Returns elapsed time for the given millis, in the following format:
     * 2 days, 5 hr, 40 min, 29 sec
     *
     * @param context     the application context
     * @param millis      the elapsed time in milli seconds
     * @param withSeconds include seconds?
     * @param collapseTimeUnit limit the output to top 2 time unit
     *                         e.g 2 days, 5 hr, 40 min, 29 sec will convert to 2 days, 5 hr
     * @return the formatted elapsed time
     */
    public static CharSequence formatElapsedTime(Context context, double millis,
            boolean withSeconds, boolean collapseTimeUnit) {
        SpannableStringBuilder sb = new SpannableStringBuilder();
        int seconds = (int) Math.floor(millis / 1000);
        if (!withSeconds) {
            // Round up.
            seconds += 30;
        }

        int days = 0, hours = 0, minutes = 0;
        if (seconds >= SECONDS_PER_DAY) {
            days = seconds / SECONDS_PER_DAY;
            seconds -= days * SECONDS_PER_DAY;
        }
        if (seconds >= SECONDS_PER_HOUR) {
            hours = seconds / SECONDS_PER_HOUR;
            seconds -= hours * SECONDS_PER_HOUR;
        }
        if (seconds >= SECONDS_PER_MINUTE) {
            minutes = seconds / SECONDS_PER_MINUTE;
            seconds -= minutes * SECONDS_PER_MINUTE;
        }

        final ArrayList<Measure> measureList = new ArrayList(4);
        if (days > 0) {
            measureList.add(new Measure(days, MeasureUnit.DAY));
        }
        if (hours > 0) {
            measureList.add(new Measure(hours, MeasureUnit.HOUR));
        }
        if (minutes > 0) {
            measureList.add(new Measure(minutes, MeasureUnit.MINUTE));
        }
        if (withSeconds && seconds > 0) {
            measureList.add(new Measure(seconds, MeasureUnit.SECOND));
        }
        if (measureList.size() == 0) {
            // Everything addable was zero, so nothing was added. We add a zero.
            measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE));
        }

        if (collapseTimeUnit && measureList.size() > LIMITED_TIME_UNIT_COUNT) {
            // Limit the output to top 2 time unit.
            measureList.subList(LIMITED_TIME_UNIT_COUNT, measureList.size()).clear();
        }

        final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]);

        final Locale locale = context.getResources().getConfiguration().locale;
        final MeasureFormat measureFormat = MeasureFormat.getInstance(
                locale, FormatWidth.SHORT);
        sb.append(measureFormat.formatMeasures(measureArray));

        if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) {
            // Add ttsSpan if it only have minute value, because it will be read as "meters"
            final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes)
                    .setUnit("minute").build();
            sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        return sb;
    }

    /**
     * Returns relative time for the given millis in the past with different format style.
     * In a short format such as "2 days ago", "5 hr. ago", "40 min. ago", or "29 sec. ago".
     * In a long format such as "2 days ago", "5 hours ago",  "40 minutes ago" or "29 seconds ago".
     *
     * <p>The unit is chosen to have good information value while only using one unit. So 27 hours
     * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as
     * "2 days ago".
     *
     * @param context     the application context
     * @param millis      the elapsed time in milli seconds
     * @param withSeconds include seconds?
     * @param formatStyle format style
     * @return the formatted elapsed time
     */
    public static CharSequence formatRelativeTime(Context context, double millis,
            boolean withSeconds, RelativeDateTimeFormatter.Style formatStyle) {
        final int seconds = (int) Math.floor(millis / 1000);
        final RelativeUnit unit;
        final int value;
        if (withSeconds && seconds < 2 * SECONDS_PER_MINUTE) {
            return context.getResources().getString(R.string.time_unit_just_now);
        } else if (seconds < 2 * SECONDS_PER_HOUR) {
            unit = RelativeUnit.MINUTES;
            value = (seconds + SECONDS_PER_MINUTE / 2)
                    / SECONDS_PER_MINUTE;
        } else if (seconds < 2 * SECONDS_PER_DAY) {
            unit = RelativeUnit.HOURS;
            value = (seconds + SECONDS_PER_HOUR / 2)
                    / SECONDS_PER_HOUR;
        } else {
            unit = RelativeUnit.DAYS;
            value = (seconds + SECONDS_PER_DAY / 2)
                    / SECONDS_PER_DAY;
        }

        final Locale locale = context.getResources().getConfiguration().locale;
        final RelativeDateTimeFormatter formatter = RelativeDateTimeFormatter.getInstance(
                ULocale.forLocale(locale),
                null /* default NumberFormat */,
                formatStyle,
                android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE);

        return formatter.format(value, RelativeDateTimeFormatter.Direction.LAST, unit);
    }

    /**
     * Returns relative time for the given millis in the past, in a long format such as "2 days
     * ago", "5 hours ago",  "40 minutes ago" or "29 seconds ago".
     *
     * <p>The unit is chosen to have good information value while only using one unit. So 27 hours
     * and 50 minutes would be formatted as "28 hr. ago", while 50 hours would be formatted as
     * "2 days ago".
     *
     * @param context     the application context
     * @param millis      the elapsed time in milli seconds
     * @param withSeconds include seconds?
     * @return the formatted elapsed time
     * @deprecated use {@link #formatRelativeTime(Context, double, boolean,
     * RelativeDateTimeFormatter.Style)} instead.
     */
    @Deprecated
    public static CharSequence formatRelativeTime(Context context, double millis,
            boolean withSeconds) {
        return formatRelativeTime(context, millis, withSeconds,
                RelativeDateTimeFormatter.Style.LONG);
    }

    /**
     * Get ICU plural string without additional arguments
     *
     * @param context Context used to get the string
     * @param count The number used to get the correct string for the current language's plural
     *              rules.
     * @param resId Resource id of the string
     *
     * @return Formatted plural string
     */
    public static String getIcuPluralsString(Context context, int count, int resId) {
        MessageFormat msgFormat = new MessageFormat(context.getResources().getString(resId),
                Locale.getDefault());
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("count", count);
        return msgFormat.format(arguments);
    }

    /**
     * Get ICU plural string with additional arguments
     *
     * @param context Context used to get the string
     * @param args String arguments
     * @param resId Resource id of the string
     *
     * @return Formatted plural string
     */
    public static String getIcuPluralsString(Context context, Map<String, Object> args, int resId) {
        MessageFormat msgFormat = new MessageFormat(context.getResources().getString(resId),
                Locale.getDefault());
        return msgFormat.format(args);
    }
}