blob: 0a70f72518d42e87a105b2c7e323468f151d34f5 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.utils;
import android.content.Context;
import android.icu.text.DateFormat;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.android.settingslib.R;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
/** Utility class for keeping power related strings consistent**/
public class PowerUtil {
private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15);
private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1);
private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2);
private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1);
/**
* This method produces the text used in various places throughout the system to describe the
* remaining battery life of the phone in a consistent manner.
*
* @param context
* @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
* @param percentageString An optional percentage of battery remaining string.
* @param basedOnUsage Whether this estimate is based on usage or simple extrapolation.
* @return a properly formatted and localized string describing how much time remains
* before the battery runs out.
*/
public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs,
@Nullable String percentageString, boolean basedOnUsage) {
if (drainTimeMs > 0) {
if (drainTimeMs <= SEVEN_MINUTES_MILLIS) {
// show a imminent shutdown warning if less than 7 minutes remain
return getShutdownImminentString(context, percentageString);
} else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) {
// show a less than 15 min remaining warning if appropriate
CharSequence timeString = StringUtil.formatElapsedTime(context,
FIFTEEN_MINUTES_MILLIS,
false /* withSeconds */);
return getUnderFifteenString(context, timeString, percentageString);
} else if (drainTimeMs >= TWO_DAYS_MILLIS) {
// just say more than two day if over 48 hours
return getMoreThanTwoDaysString(context, percentageString);
} else if (drainTimeMs >= ONE_DAY_MILLIS) {
// show remaining days & hours if more than a day
return getMoreThanOneDayString(context, drainTimeMs,
percentageString, basedOnUsage);
} else {
// show the time of day we think you'll run out
return getRegularTimeRemainingString(context, drainTimeMs,
percentageString, basedOnUsage);
}
}
return null;
}
/**
* Method to produce a shortened string describing the remaining battery. Suitable for Quick
* Settings and other areas where space is constrained.
*
* @param context context to fetch descriptions from
* @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
*
* @return a properly formatted and localized short string describing how much time remains
* before the battery runs out.
*/
@Nullable
public static String getBatteryRemainingShortStringFormatted(
Context context, long drainTimeMs) {
if (drainTimeMs <= 0) {
return null;
}
if (drainTimeMs <= ONE_DAY_MILLIS) {
return getRegularTimeRemainingShortString(context, drainTimeMs);
} else {
return getMoreThanOneDayShortString(context, drainTimeMs,
R.string.power_remaining_duration_only_short);
}
}
/**
* This method produces the text used in Settings battery tip to describe the effect after
* use the tip.
*
* @param context
* @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
* @return a properly formatted and localized string
*/
public static String getBatteryTipStringFormatted(Context context, long drainTimeMs) {
if (drainTimeMs <= 0) {
return null;
}
if (drainTimeMs <= ONE_DAY_MILLIS) {
return context.getString(R.string.power_suggestion_battery_run_out,
getDateTimeStringFromMs(context, drainTimeMs));
} else {
return getMoreThanOneDayShortString(context, drainTimeMs,
R.string.power_remaining_only_more_than_subtext);
}
}
private static String getShutdownImminentString(Context context, String percentageString) {
return TextUtils.isEmpty(percentageString)
? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
: context.getString(
R.string.power_remaining_duration_shutdown_imminent,
percentageString);
}
private static String getUnderFifteenString(Context context, CharSequence timeString,
String percentageString) {
return TextUtils.isEmpty(percentageString)
? context.getString(R.string.power_remaining_less_than_duration_only, timeString)
: context.getString(
R.string.power_remaining_less_than_duration,
timeString,
percentageString);
}
private static String getMoreThanOneDayString(Context context, long drainTimeMs,
String percentageString, boolean basedOnUsage) {
final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
CharSequence timeString = StringUtil.formatElapsedTime(context,
roundedTimeMs,
false /* withSeconds */);
if (TextUtils.isEmpty(percentageString)) {
int id = basedOnUsage
? R.string.power_remaining_duration_only_enhanced
: R.string.power_remaining_duration_only;
return context.getString(id, timeString);
} else {
int id = basedOnUsage
? R.string.power_discharging_duration_enhanced
: R.string.power_discharging_duration;
return context.getString(id, timeString, percentageString);
}
}
private static String getMoreThanOneDayShortString(Context context, long drainTimeMs,
int resId) {
final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
false /* withSeconds */);
return context.getString(resId, timeString);
}
private static String getMoreThanTwoDaysString(Context context, String percentageString) {
final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
final Measure daysMeasure = new Measure(2, MeasureUnit.DAY);
return TextUtils.isEmpty(percentageString)
? context.getString(R.string.power_remaining_only_more_than_subtext,
frmt.formatMeasures(daysMeasure))
: context.getString(
R.string.power_remaining_more_than_subtext,
frmt.formatMeasures(daysMeasure),
percentageString);
}
private static String getRegularTimeRemainingString(Context context, long drainTimeMs,
String percentageString, boolean basedOnUsage) {
CharSequence timeString = getDateTimeStringFromMs(context, drainTimeMs);
if (TextUtils.isEmpty(percentageString)) {
int id = basedOnUsage
? R.string.power_discharge_by_only_enhanced
: R.string.power_discharge_by_only;
return context.getString(id, timeString);
} else {
int id = basedOnUsage
? R.string.power_discharge_by_enhanced
: R.string.power_discharge_by;
return context.getString(id, timeString, percentageString);
}
}
private static CharSequence getDateTimeStringFromMs(Context context, long drainTimeMs) {
// Get the time of day we think device will die rounded to the nearest 15 min.
final long roundedTimeOfDayMs =
roundTimeToNearestThreshold(
System.currentTimeMillis() + drainTimeMs,
FIFTEEN_MINUTES_MILLIS);
// convert the time to a properly formatted string.
String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
return fmt.format(date);
}
private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) {
// Get the time of day we think device will die rounded to the nearest 15 min.
final long roundedTimeOfDayMs =
roundTimeToNearestThreshold(
System.currentTimeMillis() + drainTimeMs,
FIFTEEN_MINUTES_MILLIS);
// convert the time to a properly formatted string.
String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
CharSequence timeString = fmt.format(date);
return context.getString(R.string.power_discharge_by_only_short, timeString);
}
public static long convertUsToMs(long timeUs) {
return timeUs / 1000;
}
public static long convertMsToUs(long timeMs) {
return timeMs * 1000;
}
/**
* Rounds a time to the nearest multiple of the provided threshold. Note: This function takes
* the absolute value of the inputs since it is only meant to be used for times, not general
* purpose rounding.
*
* ex: roundTimeToNearestThreshold(41, 24) = 48
* @param drainTime The amount to round
* @param threshold The value to round to a multiple of
* @return The rounded value as a long
*/
public static long roundTimeToNearestThreshold(long drainTime, long threshold) {
long time = Math.abs(drainTime);
long multiple = Math.abs(threshold);
final long remainder = time % multiple;
if (remainder < multiple / 2) {
return time - remainder;
} else {
return time - remainder + multiple;
}
}
}