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 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 */ getBatteryRemainingStringFormatted(Context context, long drainTimeMs, @Nullable String percentageString, boolean basedOnUsage)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); 68 } else if (drainTimeMs >= TWO_DAYS_MILLIS) { 69 // just say more than two day if over 48 hours 70 return getMoreThanTwoDaysString(context, percentageString); 71 } else if (drainTimeMs >= ONE_DAY_MILLIS) { 72 // show remaining days & hours if more than a day 73 return getMoreThanOneDayString(context, drainTimeMs, 74 percentageString, basedOnUsage); 75 } else { 76 // show the time of day we think you'll run out 77 return getRegularTimeRemainingString(context, drainTimeMs, 78 percentageString, basedOnUsage); 79 } 80 } 81 return null; 82 } 83 84 /** 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 getBatteryRemainingShortStringFormatted( Context context, long drainTimeMs)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 { 104 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 */ getBatteryTipStringFormatted(Context context, long drainTimeMs)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); 127 } 128 } 129 getShutdownImminentString(Context context, String percentageString)130 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 getUnderFifteenString(Context context, CharSequence timeString, String percentageString)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, 144 timeString, 145 percentageString); 146 147 } 148 getMoreThanOneDayString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)149 private static String getMoreThanOneDayString(Context context, long drainTimeMs, 150 String percentageString, boolean basedOnUsage) { 151 final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS); 152 CharSequence timeString = StringUtil.formatElapsedTime(context, 153 roundedTimeMs, 154 false /* withSeconds */); 155 156 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; 165 return context.getString(id, timeString, percentageString); 166 } 167 } 168 getMoreThanOneDayShortString(Context context, long drainTimeMs, int resId)169 private static String getMoreThanOneDayShortString(Context context, long drainTimeMs, 170 int resId) { 171 final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS); 172 CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs, 173 false /* withSeconds */); 174 175 return context.getString(resId, timeString); 176 } 177 getMoreThanTwoDaysString(Context context, String percentageString)178 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 getRegularTimeRemainingString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage)193 private static String getRegularTimeRemainingString(Context context, long drainTimeMs, 194 String percentageString, boolean basedOnUsage) { 195 196 CharSequence timeString = getDateTimeStringFromMs(context, drainTimeMs); 197 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); 208 } 209 } 210 getDateTimeStringFromMs(Context context, long drainTimeMs)211 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 getRegularTimeRemainingShortString(Context context, long drainTimeMs)225 private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) { 226 // Get the time of day we think device will die rounded to the nearest 15 min. 227 final long roundedTimeOfDayMs = 228 roundTimeToNearestThreshold( 229 System.currentTimeMillis() + drainTimeMs, 230 FIFTEEN_MINUTES_MILLIS); 231 232 // convert the time to a properly formatted string. 233 String skeleton = android.text.format.DateFormat.getTimeFormatString(context); 234 DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton); 235 Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs)); 236 CharSequence timeString = fmt.format(date); 237 238 return context.getString(R.string.power_discharge_by_only_short, timeString); 239 } 240 convertUsToMs(long timeUs)241 public static long convertUsToMs(long timeUs) { 242 return timeUs / 1000; 243 } 244 convertMsToUs(long timeMs)245 public static long convertMsToUs(long timeMs) { 246 return timeMs * 1000; 247 } 248 249 /** 250 * Rounds a time to the nearest multiple of the provided threshold. Note: This function takes 251 * the absolute value of the inputs since it is only meant to be used for times, not general 252 * purpose rounding. 253 * 254 * ex: roundTimeToNearestThreshold(41, 24) = 48 255 * @param drainTime The amount to round 256 * @param threshold The value to round to a multiple of 257 * @return The rounded value as a long 258 */ roundTimeToNearestThreshold(long drainTime, long threshold)259 public static long roundTimeToNearestThreshold(long drainTime, long threshold) { 260 long time = Math.abs(drainTime); 261 long multiple = Math.abs(threshold); 262 final long remainder = time % multiple; 263 if (remainder < multiple / 2) { 264 return time - remainder; 265 } else { 266 return time - remainder + multiple; 267 } 268 } 269 }