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 }