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.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      */
     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 
     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 
     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 
    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 
    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 
    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 
    164     public static long convertUsToMs(long timeUs) {
    165         return timeUs / 1000;
    166     }
    167 
    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      */
    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