Merge "Reland "Don't use framework strings for formatting file sizes""

This commit is contained in:
Victor Chang
2022-05-24 10:04:46 +00:00
committed by Gerrit Code Review
6 changed files with 106 additions and 41 deletions

View File

@@ -22,6 +22,8 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.icu.text.MeasureFormat;
import android.icu.util.MeasureUnit;
import android.net.NetworkPolicy;
import android.net.NetworkTemplate;
import android.os.Bundle;
@@ -30,6 +32,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.NumberPicker;
import android.widget.Spinner;
@@ -301,6 +304,17 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements
: editor.getPolicyWarningBytes(template);
final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED;
final MeasureFormat formatter = MeasureFormat.getInstance(
getContext().getResources().getConfiguration().locale,
MeasureFormat.FormatWidth.SHORT);
final String[] unitNames = new String[] {
formatter.getUnitDisplayName(MeasureUnit.MEGABYTE),
formatter.getUnitDisplayName(MeasureUnit.GIGABYTE)
};
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getContext(), android.R.layout.simple_spinner_item, unitNames);
type.setAdapter(adapter);
final boolean unitInGigaBytes = (bytes > 1.5f * GIB_IN_BYTES);
final String bytesText = formatText(bytes,
unitInGigaBytes ? GIB_IN_BYTES : MIB_IN_BYTES);

View File

@@ -16,11 +16,20 @@
package com.android.settings.utils;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.icu.text.DecimalFormat;
import android.icu.text.MeasureFormat;
import android.icu.text.NumberFormat;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.text.BidiFormatter;
import android.text.format.Formatter;
import android.text.TextUtils;
import android.view.View;
import java.math.BigDecimal;
import java.util.Locale;
/**
* Utility class to aid in formatting file sizes always with the same unit. This is modified from
@@ -31,6 +40,61 @@ public final class FileSizeFormatter {
public static final long MEGABYTE_IN_BYTES = KILOBYTE_IN_BYTES * 1000;
public static final long GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000;
private static class RoundedBytesResult {
public final float value;
public final MeasureUnit units;
public final int fractionDigits;
public final long roundedBytes;
private RoundedBytesResult(
float value, MeasureUnit units, int fractionDigits, long roundedBytes) {
this.value = value;
this.units = units;
this.fractionDigits = fractionDigits;
this.roundedBytes = roundedBytes;
}
}
private static Locale localeFromContext(@NonNull Context context) {
return context.getResources().getConfiguration().locale;
}
private static String bidiWrap(@NonNull Context context, String source) {
final Locale locale = localeFromContext(context);
if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
return BidiFormatter.getInstance(true /* RTL*/).unicodeWrap(source);
} else {
return source;
}
}
private static NumberFormat getNumberFormatter(Locale locale, int fractionDigits) {
final NumberFormat numberFormatter = NumberFormat.getInstance(locale);
numberFormatter.setMinimumFractionDigits(fractionDigits);
numberFormatter.setMaximumFractionDigits(fractionDigits);
numberFormatter.setGroupingUsed(false);
if (numberFormatter instanceof DecimalFormat) {
// We do this only for DecimalFormat, since in the general NumberFormat case, calling
// setRoundingMode may throw an exception.
numberFormatter.setRoundingMode(BigDecimal.ROUND_HALF_UP);
}
return numberFormatter;
}
private static String formatMeasureShort(Locale locale, NumberFormat numberFormatter,
float value, MeasureUnit units) {
final MeasureFormat measureFormatter = MeasureFormat.getInstance(
locale, MeasureFormat.FormatWidth.SHORT, numberFormatter);
return measureFormatter.format(new Measure(value, units));
}
private static String formatRoundedBytesResult(
@NonNull Context context, @NonNull RoundedBytesResult input) {
final Locale locale = localeFromContext(context);
final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits);
return formatMeasureShort(locale, numberFormatter, input.value, input.units);
}
/**
* Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
*
@@ -47,23 +111,17 @@ public final class FileSizeFormatter {
*
* @param context Context to use to load the localized units
* @param sizeBytes size value to be formatted, in bytes
* @param suffix String id for the unit suffix.
* @param mult Amount of bytes in the unit. * @return formatted string with the number
* @param unit The unit used for formatting.
* @param mult Amount of bytes in the unit.
* @return formatted string with the number
*/
public static String formatFileSize(
@Nullable Context context, long sizeBytes, int suffix, long mult) {
@Nullable Context context, long sizeBytes, MeasureUnit unit, long mult) {
if (context == null) {
return "";
}
final Formatter.BytesResult res =
formatBytes(context.getResources(), sizeBytes, suffix, mult);
return BidiFormatter.getInstance()
.unicodeWrap(context.getString(getFileSizeSuffix(context), res.value, res.units));
}
private static int getFileSizeSuffix(Context context) {
final Resources res = context.getResources();
return res.getIdentifier("fileSizeSuffix", "string", "android");
final RoundedBytesResult res = formatBytes(sizeBytes, unit, mult);
return bidiWrap(context, formatRoundedBytesResult(context, res));
}
/**
@@ -76,8 +134,8 @@ public final class FileSizeFormatter {
* @param suffix String id for the unit suffix.
* @param mult Amount of bytes in the unit.
*/
private static Formatter.BytesResult formatBytes(
Resources res, long sizeBytes, int suffix, long mult) {
private static RoundedBytesResult formatBytes(
long sizeBytes, MeasureUnit unit, long mult) {
final boolean isNegative = (sizeBytes < 0);
float result = isNegative ? -sizeBytes : sizeBytes;
result = result / mult;
@@ -85,32 +143,29 @@ public final class FileSizeFormatter {
// compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
// floating point errors.
final int roundFactor;
final String roundFormat;
final int roundDigits;
if (mult == 1) {
roundFactor = 1;
roundFormat = "%.0f";
roundDigits = 0;
} else if (result < 1) {
roundFactor = 100;
roundFormat = "%.2f";
roundDigits = 2;
} else if (result < 10) {
roundFactor = 10;
roundFormat = "%.1f";
roundDigits = 1;
} else { // 10 <= result < 100
roundFactor = 1;
roundFormat = "%.0f";
roundDigits = 0;
}
if (isNegative) {
result = -result;
}
final String roundedString = String.format(roundFormat, result);
// Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
// it's okay (for now)...
final long roundedBytes = (((long) Math.round(result * roundFactor)) * mult / roundFactor);
final String units = res.getString(suffix);
return new Formatter.BytesResult(roundedString, units, roundedBytes);
return new RoundedBytesResult(result, unit, roundDigits, roundedBytes);
}
}