• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.DateFormat;
21 import android.icu.text.MeasureFormat;
22 import android.icu.text.MeasureFormat.FormatWidth;
23 import android.icu.util.Measure;
24 import android.icu.util.MeasureUnit;
25 import android.text.TextUtils;
26 
27 import androidx.annotation.Nullable;
28 
29 import com.android.settingslib.R;
30 
31 import java.time.Instant;
32 import java.util.Date;
33 import java.util.Locale;
34 import java.util.concurrent.TimeUnit;
35 
36 /** Utility class for keeping power related strings consistent**/
37 public class PowerUtil {
38 
39     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);
42     private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2);
43     private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1);
44     private static final long ONE_MIN_MILLIS = TimeUnit.MINUTES.toMillis(1);
45 
46     /**
47      * This method produces the text used in various places throughout the system to describe the
48      * remaining battery life of the phone in a consistent manner.
49      *
50      * @param context
51      * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
52      * @param percentageString An optional percentage of battery remaining string.
53      * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
54      * @return a properly formatted and localized string describing how much time remains
55      * before the battery runs out.
56      */
getBatteryRemainingStringFormatted(Context context, long drainTimeMs, @Nullable String percentageString, boolean basedOnUsage)57     public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
58             @Nullable String percentageString, boolean basedOnUsage) {
59         if (drainTimeMs > 0) {
60             if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
61                 // show a imminent shutdown warning if less than 7 minutes remain
62                 return getShutdownImminentString(context, percentageString);
63             } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
64                 // show a less than 15 min remaining warning if appropriate
65                 CharSequence timeString = StringUtil.formatElapsedTime(context,
66                         FIFTEEN_MINUTES_MILLIS,
67                         false /* withSeconds */, false /* collapseTimeUnit */);
68                 return getUnderFifteenString(context, timeString, percentageString);
69             } else if (drainTimeMs >= TWO_DAYS_MILLIS) {
70                 // just say more than two day if over 48 hours
71                 return getMoreThanTwoDaysString(context, percentageString);
72             } else if (drainTimeMs >= ONE_DAY_MILLIS) {
73                 // show remaining days & hours if more than a day
74                 return getMoreThanOneDayString(context, drainTimeMs,
75                         percentageString, basedOnUsage);
76             } else {
77                 // show the time of day we think you'll run out
78                 return getRegularTimeRemainingString(context, drainTimeMs,
79                         percentageString, basedOnUsage);
80             }
81         }
82         return null;
83     }
84 
85     /**
86      * Method to produce a shortened string describing the remaining battery. Suitable for Quick
87      * Settings and other areas where space is constrained.
88      *
89      * @param context context to fetch descriptions from
90      * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
91      *
92      * @return a properly formatted and localized short string describing how much time remains
93      * before the battery runs out.
94      */
95     @Nullable
getBatteryRemainingShortStringFormatted( Context context, long drainTimeMs)96     public static String getBatteryRemainingShortStringFormatted(
97             Context context, long drainTimeMs) {
98         if (drainTimeMs <= 0) {
99             return null;
100         }
101 
102         if (drainTimeMs <= ONE_DAY_MILLIS) {
103             return getRegularTimeRemainingShortString(context, drainTimeMs);
104         } else {
105             return getMoreThanOneDayShortString(context, drainTimeMs,
106                 R.string.power_remaining_duration_only_short);
107         }
108     }
109 
110     /**
111      * This method produces the text used in Settings battery tip to describe the effect after
112      * use the tip.
113      *
114      * @param context
115      * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
116      * @return a properly formatted and localized string
117      */
getBatteryTipStringFormatted(Context context, long drainTimeMs)118     public static String getBatteryTipStringFormatted(Context context, long drainTimeMs) {
119         if (drainTimeMs <= 0) {
120             return null;
121         }
122         if (drainTimeMs <= ONE_DAY_MILLIS) {
123             return context.getString(R.string.power_suggestion_battery_run_out,
124                 getDateTimeStringFromMs(context, drainTimeMs));
125         } else {
126             return getMoreThanOneDayShortString(context, drainTimeMs,
127                 R.string.power_remaining_only_more_than_subtext);
128         }
129     }
130 
getShutdownImminentString(Context context, String percentageString)131     private static String getShutdownImminentString(Context context, String percentageString) {
132         return TextUtils.isEmpty(percentageString)
133                 ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
134                 : context.getString(
135                         R.string.power_remaining_duration_shutdown_imminent,
136                         percentageString);
137     }
138 
getUnderFifteenString(Context context, CharSequence timeString, String percentageString)139     private static String getUnderFifteenString(Context context, CharSequence timeString,
140             String percentageString) {
141         return TextUtils.isEmpty(percentageString)
142                 ? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
143                 : context.getString(
144                         R.string.power_remaining_less_than_duration,
145                         timeString,
146                         percentageString);
147 
148     }
149 
getMoreThanOneDayString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)150     private static String getMoreThanOneDayString(Context context, long drainTimeMs,
151             String percentageString, boolean basedOnUsage) {
152         final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
153         CharSequence timeString = StringUtil.formatElapsedTime(context,
154                 roundedTimeMs,
155                 false /* withSeconds */, true /* collapseTimeUnit */);
156 
157         if (TextUtils.isEmpty(percentageString)) {
158             int id = basedOnUsage
159                     ? R.string.power_remaining_duration_only_enhanced
160                     : R.string.power_remaining_duration_only;
161             return context.getString(id, timeString);
162         } else {
163             int id = basedOnUsage
164                     ? R.string.power_discharging_duration_enhanced
165                     : R.string.power_discharging_duration;
166             return context.getString(id, timeString, percentageString);
167         }
168     }
169 
getMoreThanOneDayShortString(Context context, long drainTimeMs, int resId)170     private static String getMoreThanOneDayShortString(Context context, long drainTimeMs,
171             int resId) {
172         final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
173         CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
174                 false /* withSeconds */, false /* collapseTimeUnit */);
175 
176         return context.getString(resId, timeString);
177     }
178 
getMoreThanTwoDaysString(Context context, String percentageString)179     private static String getMoreThanTwoDaysString(Context context, String percentageString) {
180         final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
181         final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
182 
183         final Measure daysMeasure = new Measure(2, MeasureUnit.DAY);
184 
185         return TextUtils.isEmpty(percentageString)
186                 ? context.getString(R.string.power_remaining_only_more_than_subtext,
187                         frmt.formatMeasures(daysMeasure))
188                 : context.getString(
189                         R.string.power_remaining_more_than_subtext,
190                         frmt.formatMeasures(daysMeasure),
191                         percentageString);
192     }
193 
getRegularTimeRemainingString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)194     private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
195             String percentageString, boolean basedOnUsage) {
196 
197         CharSequence timeString = StringUtil.formatElapsedTime(context,
198                 drainTimeMs, false /* withSeconds */, true /* collapseTimeUnit */);
199 
200         if (TextUtils.isEmpty(percentageString)) {
201             int id = basedOnUsage
202                     ? R.string.power_remaining_duration_only_enhanced
203                     : R.string.power_remaining_duration_only;
204             return context.getString(id, timeString);
205         } else {
206             int id = basedOnUsage
207                     ? R.string.power_discharging_duration_enhanced
208                     : R.string.power_discharging_duration;
209             return context.getString(id, timeString, percentageString);
210         }
211     }
212 
getDateTimeStringFromMs(Context context, long drainTimeMs)213     private static CharSequence getDateTimeStringFromMs(Context context, long drainTimeMs) {
214         // Get the time of day we think device will die rounded to the nearest 15 min.
215         final long roundedTimeOfDayMs =
216                 roundTimeToNearestThreshold(
217                         System.currentTimeMillis() + drainTimeMs,
218                         FIFTEEN_MINUTES_MILLIS);
219 
220         // convert the time to a properly formatted string.
221         String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
222         DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
223         Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
224         return fmt.format(date);
225     }
226 
getRegularTimeRemainingShortString(Context context, long drainTimeMs)227     private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) {
228         // Get the time of day we think device will die rounded to the nearest 15 min.
229         final long roundedTimeOfDayMs =
230                 roundTimeToNearestThreshold(
231                         System.currentTimeMillis() + drainTimeMs,
232                         FIFTEEN_MINUTES_MILLIS);
233 
234         // convert the time to a properly formatted string.
235         String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
236         DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
237         Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
238         CharSequence timeString = fmt.format(date);
239 
240         return context.getString(R.string.power_discharge_by_only_short, timeString);
241     }
242 
convertUsToMs(long timeUs)243     public static long convertUsToMs(long timeUs) {
244         return timeUs / 1000;
245     }
246 
convertMsToUs(long timeMs)247     public static long convertMsToUs(long timeMs) {
248         return timeMs * 1000;
249     }
250 
251     /**
252      * Rounds a time to the nearest multiple of the provided threshold. Note: This function takes
253      * the absolute value of the inputs since it is only meant to be used for times, not general
254      * purpose rounding.
255      *
256      * ex: roundTimeToNearestThreshold(41, 24) = 48
257      * @param drainTime The amount to round
258      * @param threshold The value to round to a multiple of
259      * @return The rounded value as a long
260      */
roundTimeToNearestThreshold(long drainTime, long threshold)261     public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
262         long time = Math.abs(drainTime);
263         long multiple = Math.abs(threshold);
264         final long remainder = time % multiple;
265         if (remainder < multiple / 2) {
266             return time - remainder;
267         } else {
268             return time - remainder + multiple;
269         }
270     }
271 }