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