Home | History | Annotate | Download | only in utils
      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      */
     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
     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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    241     public static long convertUsToMs(long timeUs) {
    242         return timeUs / 1000;
    243     }
    244 
    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      */
    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 }