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.support.annotation.Nullable;
26 import android.text.TextUtils;
27 
28 import com.android.settingslib.R;
29 
30 import java.time.Instant;
31 import java.util.Date;
32 import java.util.Locale;
33 import java.util.concurrent.TimeUnit;
34 
35 /** Utility class for keeping power related strings consistent**/
36 public class PowerUtil {
37 
38     private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
39     private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15);
40     private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1);
41     private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2);
42     private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1);
43 
44     /**
45      * This method produces the text used in various places throughout the system to describe the
46      * remaining battery life of the phone in a consistent manner.
47      *
48      * @param context
49      * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
50      * @param percentageString An optional percentage of battery remaining string.
51      * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
52      * @return a properly formatted and localized string describing how much time remains
53      * before the battery runs out.
54      */
getBatteryRemainingStringFormatted(Context context, long drainTimeMs, @Nullable String percentageString, boolean basedOnUsage)55     public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
56             @Nullable String percentageString, boolean basedOnUsage) {
57         if (drainTimeMs > 0) {
58             if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
59                 // show a imminent shutdown warning if less than 7 minutes remain
60                 return getShutdownImminentString(context, percentageString);
61             } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
62                 // show a less than 15 min remaining warning if appropriate
63                 CharSequence timeString = StringUtil.formatElapsedTime(context,
64                         FIFTEEN_MINUTES_MILLIS,
65                         false /* withSeconds */);
66                 return getUnderFifteenString(context, timeString, percentageString);
67             } else if (drainTimeMs >= TWO_DAYS_MILLIS) {
68                 // just say more than two day if over 48 hours
69                 return getMoreThanTwoDaysString(context, percentageString);
70             } else if (drainTimeMs >= ONE_DAY_MILLIS) {
71                 // show remaining days & hours if more than a day
72                 return getMoreThanOneDayString(context, drainTimeMs,
73                         percentageString, basedOnUsage);
74             } else {
75                 // show the time of day we think you'll run out
76                 return getRegularTimeRemainingString(context, drainTimeMs,
77                         percentageString, basedOnUsage);
78             }
79         }
80         return null;
81     }
82 
getShutdownImminentString(Context context, String percentageString)83     private static String getShutdownImminentString(Context context, String percentageString) {
84         return TextUtils.isEmpty(percentageString)
85                 ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
86                 : context.getString(
87                         R.string.power_remaining_duration_shutdown_imminent,
88                         percentageString);
89     }
90 
getUnderFifteenString(Context context, CharSequence timeString, String percentageString)91     private static String getUnderFifteenString(Context context, CharSequence timeString,
92             String percentageString) {
93         return TextUtils.isEmpty(percentageString)
94                 ? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
95                 : context.getString(
96                         R.string.power_remaining_less_than_duration,
97                         timeString,
98                         percentageString);
99 
100     }
101 
getMoreThanOneDayString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)102     private static String getMoreThanOneDayString(Context context, long drainTimeMs,
103             String percentageString, boolean basedOnUsage) {
104         final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
105         CharSequence timeString = StringUtil.formatElapsedTime(context,
106                 roundedTimeMs,
107                 false /* withSeconds */);
108 
109         if (TextUtils.isEmpty(percentageString)) {
110             int id = basedOnUsage
111                     ? R.string.power_remaining_duration_only_enhanced
112                     : R.string.power_remaining_duration_only;
113             return context.getString(id, timeString);
114         } else {
115             int id = basedOnUsage
116                     ? R.string.power_discharging_duration_enhanced
117                     : R.string.power_discharging_duration;
118             return context.getString(id, timeString, percentageString);
119         }
120     }
121 
getMoreThanTwoDaysString(Context context, String percentageString)122     private static String getMoreThanTwoDaysString(Context context, String percentageString) {
123         final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
124         final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
125 
126         final Measure daysMeasure = new Measure(2, MeasureUnit.DAY);
127 
128         return TextUtils.isEmpty(percentageString)
129                 ? context.getString(R.string.power_remaining_only_more_than_subtext,
130                         frmt.formatMeasures(daysMeasure))
131                 : context.getString(
132                         R.string.power_remaining_more_than_subtext,
133                         frmt.formatMeasures(daysMeasure),
134                         percentageString);
135     }
136 
getRegularTimeRemainingString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)137     private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
138             String percentageString, boolean basedOnUsage) {
139         // Get the time of day we think device will die rounded to the nearest 15 min.
140         final long roundedTimeOfDayMs =
141                 roundTimeToNearestThreshold(
142                         System.currentTimeMillis() + drainTimeMs,
143                         FIFTEEN_MINUTES_MILLIS);
144 
145         // convert the time to a properly formatted string.
146         String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
147         DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
148         Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
149         CharSequence timeString = fmt.format(date);
150 
151         if (TextUtils.isEmpty(percentageString)) {
152             int id = basedOnUsage
153                     ? R.string.power_discharge_by_only_enhanced
154                     : R.string.power_discharge_by_only;
155             return context.getString(id, timeString);
156         } else {
157             int id = basedOnUsage
158                     ? R.string.power_discharge_by_enhanced
159                     : R.string.power_discharge_by;
160             return context.getString(id, timeString, percentageString);
161         }
162     }
163 
convertUsToMs(long timeUs)164     public static long convertUsToMs(long timeUs) {
165         return timeUs / 1000;
166     }
167 
convertMsToUs(long timeMs)168     public static long convertMsToUs(long timeMs) {
169         return timeMs * 1000;
170     }
171 
172     /**
173      * Rounds a time to the nearest multiple of the provided threshold. Note: This function takes
174      * the absolute value of the inputs since it is only meant to be used for times, not general
175      * purpose rounding.
176      *
177      * ex: roundTimeToNearestThreshold(41, 24) = 48
178      * @param drainTime The amount to round
179      * @param threshold The value to round to a multiple of
180      * @return The rounded value as a long
181      */
roundTimeToNearestThreshold(long drainTime, long threshold)182     public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
183         long time = Math.abs(drainTime);
184         long multiple = Math.abs(threshold);
185         final long remainder = time % multiple;
186         if (remainder < multiple / 2) {
187             return time - remainder;
188         } else {
189             return time - remainder + multiple;
190         }
191     }
192 }
193