Revert "Switch file size formatters to use ICU's MeasureFormat"
This reverts commit 4e5b71f084.
Test: cts-tradefed run cts-dev -m CtsTextTestCases
Bug: 70005649
Bug: 36994779
Change-Id: Ie9a2fd786d48e2a6c291e313cbb4072c7306af9f
This commit is contained in:
@@ -20,11 +20,7 @@ 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.text.UnicodeSet;
|
||||
import android.icu.text.UnicodeSetSpanner;
|
||||
import android.icu.util.Measure;
|
||||
import android.icu.util.MeasureUnit;
|
||||
import android.net.NetworkUtils;
|
||||
@@ -32,7 +28,6 @@ import android.text.BidiFormatter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@@ -41,8 +36,6 @@ import java.util.Locale;
|
||||
*/
|
||||
public final class Formatter {
|
||||
|
||||
/** {@hide} */
|
||||
public static final int FLAG_DEFAULT = 0;
|
||||
/** {@hide} */
|
||||
public static final int FLAG_SHORTER = 1 << 0;
|
||||
/** {@hide} */
|
||||
@@ -65,9 +58,7 @@ public final class Formatter {
|
||||
return context.getResources().getConfiguration().getLocales().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the source string in bidi formatting characters in RTL locales.
|
||||
*/
|
||||
/* Wraps the source string in bidi formatting characters in RTL locales */
|
||||
private static String bidiWrap(@NonNull Context context, String source) {
|
||||
final Locale locale = localeFromContext(context);
|
||||
if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
|
||||
@@ -96,7 +87,12 @@ public final class Formatter {
|
||||
* @return formatted string with the number
|
||||
*/
|
||||
public static String formatFileSize(@Nullable Context context, long sizeBytes) {
|
||||
return formatFileSize(context, sizeBytes, FLAG_DEFAULT);
|
||||
if (context == null) {
|
||||
return "";
|
||||
}
|
||||
final BytesResult res = formatBytes(context.getResources(), sizeBytes, 0);
|
||||
return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
|
||||
res.value, res.units));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,191 +100,88 @@ public final class Formatter {
|
||||
* (showing fewer digits of precision).
|
||||
*/
|
||||
public static String formatShortFileSize(@Nullable Context context, long sizeBytes) {
|
||||
return formatFileSize(context, sizeBytes, FLAG_SHORTER);
|
||||
}
|
||||
|
||||
private static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) {
|
||||
if (context == null) {
|
||||
return "";
|
||||
}
|
||||
final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags);
|
||||
return bidiWrap(context, formatRoundedBytesResult(context, res));
|
||||
}
|
||||
|
||||
private static String getSuffixOverride(@NonNull Resources res, MeasureUnit unit) {
|
||||
if (unit == MeasureUnit.BYTE) {
|
||||
return res.getString(com.android.internal.R.string.byteShort);
|
||||
} else { // unit == PETABYTE
|
||||
return res.getString(com.android.internal.R.string.petabyteShort);
|
||||
}
|
||||
}
|
||||
|
||||
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 deleteFirstFromString(String source, String toDelete) {
|
||||
final int location = source.indexOf(toDelete);
|
||||
if (location == -1) {
|
||||
return source;
|
||||
} else {
|
||||
return source.substring(0, location)
|
||||
+ source.substring(location + toDelete.length(), source.length());
|
||||
}
|
||||
}
|
||||
|
||||
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 final UnicodeSetSpanner SPACES_AND_CONTROLS =
|
||||
new UnicodeSetSpanner(new UnicodeSet("[[:Zs:][:Cf:]]").freeze());
|
||||
|
||||
private static String formatRoundedBytesResult(
|
||||
@NonNull Context context, @NonNull RoundedBytesResult input) {
|
||||
final Locale locale = localeFromContext(context);
|
||||
final NumberFormat numberFormatter = getNumberFormatter(locale, input.fractionDigits);
|
||||
if (input.units == MeasureUnit.BYTE || input.units == PETABYTE) {
|
||||
// ICU spells out "byte" instead of "B", and can't format petabytes yet.
|
||||
final String formattedNumber = numberFormatter.format(input.value);
|
||||
return context.getString(com.android.internal.R.string.fileSizeSuffix,
|
||||
formattedNumber, getSuffixOverride(context.getResources(), input.units));
|
||||
} else {
|
||||
return formatMeasureShort(locale, numberFormatter, input.value, input.units);
|
||||
}
|
||||
final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER);
|
||||
return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
|
||||
res.value, res.units));
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
|
||||
final RoundedBytesResult rounded = RoundedBytesResult.roundBytes(sizeBytes, flags);
|
||||
final Locale locale = res.getConfiguration().getLocales().get(0);
|
||||
final NumberFormat numberFormatter = getNumberFormatter(locale, rounded.fractionDigits);
|
||||
final String formattedNumber = numberFormatter.format(rounded.value);
|
||||
final String units;
|
||||
if (rounded.units == MeasureUnit.BYTE || rounded.units == PETABYTE) {
|
||||
// ICU spells out "byte" instead of "B", and can't format petabytes yet.
|
||||
units = getSuffixOverride(res, rounded.units);
|
||||
} else {
|
||||
// Since ICU does not give us access to the pattern, we need to extract the unit string
|
||||
// from ICU, which we do by taking out the formatted number out of the formatted string
|
||||
// and trimming the result of spaces and controls.
|
||||
final String formattedMeasure = formatMeasureShort(
|
||||
locale, numberFormatter, rounded.value, rounded.units);
|
||||
final String numberRemoved = deleteFirstFromString(formattedMeasure, formattedNumber);
|
||||
units = SPACES_AND_CONTROLS.trim(numberRemoved).toString();
|
||||
final boolean isNegative = (sizeBytes < 0);
|
||||
float result = isNegative ? -sizeBytes : sizeBytes;
|
||||
int suffix = com.android.internal.R.string.byteShort;
|
||||
long mult = 1;
|
||||
if (result > 900) {
|
||||
suffix = com.android.internal.R.string.kilobyteShort;
|
||||
mult = 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
return new BytesResult(formattedNumber, units, rounded.roundedBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way.
|
||||
* {@hide}
|
||||
*/
|
||||
public static final MeasureUnit PETABYTE = MeasureUnit.internalGetInstance(
|
||||
"digital", "petabyte");
|
||||
|
||||
/** {@hide} */
|
||||
public 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;
|
||||
if (result > 900) {
|
||||
suffix = com.android.internal.R.string.megabyteShort;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RoundedBytesResult object based on the input size in bytes and the rounding
|
||||
* flags. The result can be used for formatting.
|
||||
*/
|
||||
public static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
|
||||
final boolean isNegative = (sizeBytes < 0);
|
||||
float result = isNegative ? -sizeBytes : sizeBytes;
|
||||
MeasureUnit units = MeasureUnit.BYTE;
|
||||
long mult = 1;
|
||||
if (result > 900) {
|
||||
units = MeasureUnit.KILOBYTE;
|
||||
mult = 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
units = MeasureUnit.MEGABYTE;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
units = MeasureUnit.GIGABYTE;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
units = MeasureUnit.TERABYTE;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
units = PETABYTE;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
// Note we calculate the rounded long by ourselves, but still let NumberFormat compute
|
||||
// the rounded value. NumberFormat.format(0.1) might not return "0.1" due to floating
|
||||
// point errors.
|
||||
final int roundFactor;
|
||||
final int roundDigits;
|
||||
if (mult == 1 || result >= 100) {
|
||||
roundFactor = 1;
|
||||
roundDigits = 0;
|
||||
} else if (result < 1) {
|
||||
if (result > 900) {
|
||||
suffix = com.android.internal.R.string.gigabyteShort;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
suffix = com.android.internal.R.string.terabyteShort;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
if (result > 900) {
|
||||
suffix = com.android.internal.R.string.petabyteShort;
|
||||
mult *= 1000;
|
||||
result = result / 1000;
|
||||
}
|
||||
// Note we calculate the rounded long by ourselves, but still let String.format()
|
||||
// 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;
|
||||
if (mult == 1 || result >= 100) {
|
||||
roundFactor = 1;
|
||||
roundFormat = "%.0f";
|
||||
} else if (result < 1) {
|
||||
roundFactor = 100;
|
||||
roundFormat = "%.2f";
|
||||
} else if (result < 10) {
|
||||
if ((flags & FLAG_SHORTER) != 0) {
|
||||
roundFactor = 10;
|
||||
roundFormat = "%.1f";
|
||||
} else {
|
||||
roundFactor = 100;
|
||||
roundDigits = 2;
|
||||
} else if (result < 10) {
|
||||
if ((flags & FLAG_SHORTER) != 0) {
|
||||
roundFactor = 10;
|
||||
roundDigits = 1;
|
||||
} else {
|
||||
roundFactor = 100;
|
||||
roundDigits = 2;
|
||||
}
|
||||
} else { // 10 <= result < 100
|
||||
if ((flags & FLAG_SHORTER) != 0) {
|
||||
roundFactor = 1;
|
||||
roundDigits = 0;
|
||||
} else {
|
||||
roundFactor = 100;
|
||||
roundDigits = 2;
|
||||
}
|
||||
roundFormat = "%.2f";
|
||||
}
|
||||
|
||||
if (isNegative) {
|
||||
result = -result;
|
||||
} else { // 10 <= result < 100
|
||||
if ((flags & FLAG_SHORTER) != 0) {
|
||||
roundFactor = 1;
|
||||
roundFormat = "%.0f";
|
||||
} else {
|
||||
roundFactor = 100;
|
||||
roundFormat = "%.2f";
|
||||
}
|
||||
|
||||
// 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 =
|
||||
(flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
|
||||
: (((long) Math.round(result * roundFactor)) * mult / roundFactor);
|
||||
|
||||
return new RoundedBytesResult(result, units, roundDigits, roundedBytes);
|
||||
}
|
||||
|
||||
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 =
|
||||
(flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
|
||||
: (((long) Math.round(result * roundFactor)) * mult / roundFactor);
|
||||
|
||||
final String units = res.getString(suffix);
|
||||
|
||||
return new BytesResult(roundedString, units, roundedBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,13 +20,23 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Suffix added to a number to signify size in bytes. -->
|
||||
<string name="byteShort">B</string>
|
||||
<!-- Suffix added to a number to signify size in kilobytes (1000 bytes).
|
||||
If you retain the Latin script for the localization, please use the lowercase
|
||||
'k', as it signifies 1000 bytes as opposed to 1024 bytes. -->
|
||||
<string name="kilobyteShort">kB</string>
|
||||
<!-- Suffix added to a number to signify size in megabytes. -->
|
||||
<string name="megabyteShort">MB</string>
|
||||
<!-- Suffix added to a number to signify size in gigabytes. -->
|
||||
<string name="gigabyteShort">GB</string>
|
||||
<!-- Suffix added to a number to signify size in terabytes. -->
|
||||
<string name="terabyteShort">TB</string>
|
||||
<!-- Suffix added to a number to signify size in petabytes. -->
|
||||
<string name="petabyteShort">PB</string>
|
||||
<!-- Format string used to add a suffix like "B" or "PB" to a number
|
||||
to display a size in bytes or petabytes.
|
||||
Some languages may want to remove the space between the placeholders
|
||||
or replace it with a non-breaking space. -->
|
||||
<string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="B">%2$s</xliff:g></string>
|
||||
<!-- Format string used to add a suffix like "kB" or "MB" to a number
|
||||
to display a size in kilobytes, megabytes, or other size units.
|
||||
Some languages (like French) will want to add a space between
|
||||
the placeholders. -->
|
||||
<string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="MB">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Used in Contacts for a field that has no label and in Note Pad
|
||||
for a note with no name. -->
|
||||
|
||||
@@ -705,6 +705,7 @@
|
||||
<java-symbol type="string" name="fileSizeSuffix" />
|
||||
<java-symbol type="string" name="force_close" />
|
||||
<java-symbol type="string" name="gadget_host_error_inflating" />
|
||||
<java-symbol type="string" name="gigabyteShort" />
|
||||
<java-symbol type="string" name="gpsNotifMessage" />
|
||||
<java-symbol type="string" name="gpsNotifTicker" />
|
||||
<java-symbol type="string" name="gpsNotifTitle" />
|
||||
@@ -760,6 +761,7 @@
|
||||
<java-symbol type="string" name="keyboardview_keycode_enter" />
|
||||
<java-symbol type="string" name="keyboardview_keycode_mode_change" />
|
||||
<java-symbol type="string" name="keyboardview_keycode_shift" />
|
||||
<java-symbol type="string" name="kilobyteShort" />
|
||||
<java-symbol type="string" name="last_month" />
|
||||
<java-symbol type="string" name="launchBrowserDefault" />
|
||||
<java-symbol type="string" name="lock_to_app_toast" />
|
||||
@@ -779,6 +781,7 @@
|
||||
<java-symbol type="string" name="lockscreen_emergency_call" />
|
||||
<java-symbol type="string" name="lockscreen_return_to_call" />
|
||||
<java-symbol type="string" name="low_memory" />
|
||||
<java-symbol type="string" name="megabyteShort" />
|
||||
<java-symbol type="string" name="midnight" />
|
||||
<java-symbol type="string" name="mismatchPin" />
|
||||
<java-symbol type="string" name="mmiComplete" />
|
||||
@@ -981,6 +984,7 @@
|
||||
<java-symbol type="string" name="sync_really_delete" />
|
||||
<java-symbol type="string" name="sync_too_many_deletes_desc" />
|
||||
<java-symbol type="string" name="sync_undo_deletes" />
|
||||
<java-symbol type="string" name="terabyteShort" />
|
||||
<java-symbol type="string" name="text_copied" />
|
||||
<java-symbol type="string" name="time_of_day" />
|
||||
<java-symbol type="string" name="time_picker_decrement_hour_button" />
|
||||
|
||||
Reference in New Issue
Block a user