blob: 5600dd279b95c0ff1bb76429e766160d5395ae16 [file] [log] [blame]
Salvador Martinezeb9ab292018-01-19 17:50:24 -08001/*
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
17package com.android.settingslib.utils;
18
19import android.content.Context;
Salvador Martinezeec87362018-02-27 12:57:49 -080020import android.icu.text.DateFormat;
Salvador Martinezeb9ab292018-01-19 17:50:24 -080021import android.icu.text.MeasureFormat;
22import android.icu.text.MeasureFormat.FormatWidth;
23import android.icu.util.Measure;
24import android.icu.util.MeasureUnit;
Salvador Martinezeb9ab292018-01-19 17:50:24 -080025import android.text.TextUtils;
Salvador Martinez2ee2b0a2018-04-13 13:34:46 -070026
Fan Zhangf7802ea2018-08-28 15:15:19 -070027import androidx.annotation.Nullable;
28
Salvador Martinezeb9ab292018-01-19 17:50:24 -080029import com.android.settingslib.R;
Salvador Martinez2ee2b0a2018-04-13 13:34:46 -070030
Salvador Martinezeec87362018-02-27 12:57:49 -080031import java.time.Instant;
Salvador Martinezeec87362018-02-27 12:57:49 -080032import java.util.Date;
Salvador Martinezeb9ab292018-01-19 17:50:24 -080033import java.util.Locale;
34import java.util.concurrent.TimeUnit;
35
36/** Utility class for keeping power related strings consistent**/
37public class PowerUtil {
Salvador Martinezeec87362018-02-27 12:57:49 -080038
Salvador Martinezeb9ab292018-01-19 17:50:24 -080039 private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
40 private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15);
41 private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1);
Salvador Martinezeec87362018-02-27 12:57:49 -080042 private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2);
43 private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1);
Salvador Martinezeb9ab292018-01-19 17:50:24 -080044
45 /**
46 * This method produces the text used in various places throughout the system to describe the
47 * remaining battery life of the phone in a consistent manner.
48 *
49 * @param context
50 * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
51 * @param percentageString An optional percentage of battery remaining string.
52 * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
53 * @return a properly formatted and localized string describing how much time remains
54 * before the battery runs out.
55 */
56 public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
57 @Nullable String percentageString, boolean basedOnUsage) {
58 if (drainTimeMs > 0) {
59 if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
60 // show a imminent shutdown warning if less than 7 minutes remain
61 return getShutdownImminentString(context, percentageString);
62 } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
63 // show a less than 15 min remaining warning if appropriate
64 CharSequence timeString = StringUtil.formatElapsedTime(context,
65 FIFTEEN_MINUTES_MILLIS,
66 false /* withSeconds */);
67 return getUnderFifteenString(context, timeString, percentageString);
Salvador Martinezeec87362018-02-27 12:57:49 -080068 } else if (drainTimeMs >= TWO_DAYS_MILLIS) {
69 // just say more than two day if over 48 hours
70 return getMoreThanTwoDaysString(context, percentageString);
Salvador Martinezeb9ab292018-01-19 17:50:24 -080071 } else if (drainTimeMs >= ONE_DAY_MILLIS) {
Salvador Martinezeec87362018-02-27 12:57:49 -080072 // show remaining days & hours if more than a day
73 return getMoreThanOneDayString(context, drainTimeMs,
74 percentageString, basedOnUsage);
Salvador Martinezeb9ab292018-01-19 17:50:24 -080075 } else {
Salvador Martinezeec87362018-02-27 12:57:49 -080076 // show the time of day we think you'll run out
Salvador Martinezeb9ab292018-01-19 17:50:24 -080077 return getRegularTimeRemainingString(context, drainTimeMs,
78 percentageString, basedOnUsage);
79 }
80 }
81 return null;
82 }
83
Evan Laird4bf21df2018-10-22 14:24:32 -040084 /**
85 * Method to produce a shortened string describing the remaining battery. Suitable for Quick
86 * Settings and other areas where space is constrained.
87 *
88 * @param context context to fetch descriptions from
89 * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
90 *
91 * @return a properly formatted and localized short string describing how much time remains
92 * before the battery runs out.
93 */
94 @Nullable
95 public static String getBatteryRemainingShortStringFormatted(
96 Context context, long drainTimeMs) {
97 if (drainTimeMs <= 0) {
98 return null;
99 }
100
101 if (drainTimeMs <= ONE_DAY_MILLIS) {
102 return getRegularTimeRemainingShortString(context, drainTimeMs);
103 } else {
Raff Tsai9753fe62019-03-06 10:46:46 +0800104 return getMoreThanOneDayShortString(context, drainTimeMs,
105 R.string.power_remaining_duration_only_short);
106 }
107 }
108
109 /**
110 * This method produces the text used in Settings battery tip to describe the effect after
111 * use the tip.
112 *
113 * @param context
114 * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
115 * @return a properly formatted and localized string
116 */
117 public static String getBatteryTipStringFormatted(Context context, long drainTimeMs) {
118 if (drainTimeMs <= 0) {
119 return null;
120 }
121 if (drainTimeMs <= ONE_DAY_MILLIS) {
122 return context.getString(R.string.power_suggestion_extend_battery,
123 getDateTimeStringFromMs(context, drainTimeMs));
124 } else {
125 return getMoreThanOneDayShortString(context, drainTimeMs,
126 R.string.power_remaining_only_more_than_subtext);
Evan Laird4bf21df2018-10-22 14:24:32 -0400127 }
128 }
129
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800130 private static String getShutdownImminentString(Context context, String percentageString) {
131 return TextUtils.isEmpty(percentageString)
132 ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
133 : context.getString(
134 R.string.power_remaining_duration_shutdown_imminent,
135 percentageString);
136 }
137
138 private static String getUnderFifteenString(Context context, CharSequence timeString,
139 String percentageString) {
140 return TextUtils.isEmpty(percentageString)
141 ? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
142 : context.getString(
143 R.string.power_remaining_less_than_duration,
Salvador Martinezeec87362018-02-27 12:57:49 -0800144 timeString,
145 percentageString);
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800146
147 }
148
Salvador Martinezeec87362018-02-27 12:57:49 -0800149 private static String getMoreThanOneDayString(Context context, long drainTimeMs,
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800150 String percentageString, boolean basedOnUsage) {
Salvador Martinez2ee2b0a2018-04-13 13:34:46 -0700151 final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800152 CharSequence timeString = StringUtil.formatElapsedTime(context,
153 roundedTimeMs,
154 false /* withSeconds */);
Salvador Martinezeec87362018-02-27 12:57:49 -0800155
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800156 if (TextUtils.isEmpty(percentageString)) {
157 int id = basedOnUsage
158 ? R.string.power_remaining_duration_only_enhanced
159 : R.string.power_remaining_duration_only;
160 return context.getString(id, timeString);
161 } else {
162 int id = basedOnUsage
163 ? R.string.power_discharging_duration_enhanced
164 : R.string.power_discharging_duration;
Salvador Martinezeec87362018-02-27 12:57:49 -0800165 return context.getString(id, timeString, percentageString);
166 }
167 }
168
Raff Tsai9753fe62019-03-06 10:46:46 +0800169 private static String getMoreThanOneDayShortString(Context context, long drainTimeMs,
170 int resId) {
Evan Laird4bf21df2018-10-22 14:24:32 -0400171 final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
172 CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
173 false /* withSeconds */);
174
Raff Tsai9753fe62019-03-06 10:46:46 +0800175 return context.getString(resId, timeString);
Evan Laird4bf21df2018-10-22 14:24:32 -0400176 }
177
Salvador Martinezeec87362018-02-27 12:57:49 -0800178 private static String getMoreThanTwoDaysString(Context context, String percentageString) {
179 final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
180 final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
181
182 final Measure daysMeasure = new Measure(2, MeasureUnit.DAY);
183
184 return TextUtils.isEmpty(percentageString)
185 ? context.getString(R.string.power_remaining_only_more_than_subtext,
186 frmt.formatMeasures(daysMeasure))
187 : context.getString(
188 R.string.power_remaining_more_than_subtext,
189 frmt.formatMeasures(daysMeasure),
190 percentageString);
191 }
192
193 private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
194 String percentageString, boolean basedOnUsage) {
Salvador Martinezeec87362018-02-27 12:57:49 -0800195
Raff Tsai9753fe62019-03-06 10:46:46 +0800196 CharSequence timeString = getDateTimeStringFromMs(context, drainTimeMs);
Salvador Martinezeec87362018-02-27 12:57:49 -0800197
198 if (TextUtils.isEmpty(percentageString)) {
199 int id = basedOnUsage
200 ? R.string.power_discharge_by_only_enhanced
201 : R.string.power_discharge_by_only;
202 return context.getString(id, timeString);
203 } else {
204 int id = basedOnUsage
205 ? R.string.power_discharge_by_enhanced
206 : R.string.power_discharge_by;
207 return context.getString(id, timeString, percentageString);
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800208 }
209 }
210
Raff Tsai9753fe62019-03-06 10:46:46 +0800211 private static CharSequence getDateTimeStringFromMs(Context context, long drainTimeMs) {
212 // Get the time of day we think device will die rounded to the nearest 15 min.
213 final long roundedTimeOfDayMs =
214 roundTimeToNearestThreshold(
215 System.currentTimeMillis() + drainTimeMs,
216 FIFTEEN_MINUTES_MILLIS);
217
218 // convert the time to a properly formatted string.
219 String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
220 DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
221 Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
222 return fmt.format(date);
223 }
224
Evan Laird4bf21df2018-10-22 14:24:32 -0400225 private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) {
Evan Laird85ee4a32019-03-06 18:09:20 -0500226 // Get the time remaining rounded to the nearest 15 min
227 final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, FIFTEEN_MINUTES_MILLIS);
228 CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
229 false /* withSeconds */);
Evan Laird4bf21df2018-10-22 14:24:32 -0400230
Evan Laird85ee4a32019-03-06 18:09:20 -0500231 return context.getString(R.string.power_remaining_duration_only_short, timeString);
Evan Laird4bf21df2018-10-22 14:24:32 -0400232 }
233
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800234 public static long convertUsToMs(long timeUs) {
235 return timeUs / 1000;
236 }
237
238 public static long convertMsToUs(long timeMs) {
239 return timeMs * 1000;
240 }
241
Salvador Martinez2ee2b0a2018-04-13 13:34:46 -0700242 /**
243 * Rounds a time to the nearest multiple of the provided threshold. Note: This function takes
244 * the absolute value of the inputs since it is only meant to be used for times, not general
245 * purpose rounding.
246 *
247 * ex: roundTimeToNearestThreshold(41, 24) = 48
248 * @param drainTime The amount to round
249 * @param threshold The value to round to a multiple of
250 * @return The rounded value as a long
251 */
252 public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
253 long time = Math.abs(drainTime);
254 long multiple = Math.abs(threshold);
255 final long remainder = time % multiple;
256 if (remainder < multiple / 2) {
257 return time - remainder;
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800258 } else {
Salvador Martinez2ee2b0a2018-04-13 13:34:46 -0700259 return time - remainder + multiple;
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800260 }
261 }
Raff Tsai9753fe62019-03-06 10:46:46 +0800262}