summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/format/Formatter.java251
-rw-r--r--core/res/res/values/strings.xml20
-rw-r--r--core/res/res/values/symbols.xml4
3 files changed, 184 insertions, 91 deletions
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index e5bc32bb4f0a..fc56455236a2 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -20,7 +20,11 @@ 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;
@@ -28,6 +32,7 @@ import android.text.BidiFormatter;
import android.text.TextUtils;
import android.view.View;
+import java.math.BigDecimal;
import java.util.Locale;
/**
@@ -37,6 +42,8 @@ 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} */
public static final int FLAG_CALCULATE_ROUNDED = 1 << 1;
@@ -58,7 +65,9 @@ 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) {
@@ -87,12 +96,7 @@ public final class Formatter {
* @return formatted string with the number
*/
public static String formatFileSize(@Nullable Context context, long sizeBytes) {
- 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));
+ return formatFileSize(context, sizeBytes, FLAG_DEFAULT);
}
/**
@@ -100,88 +104,191 @@ 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 BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER);
- return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
- res.value, res.units));
+ final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags);
+ return bidiWrap(context, formatRoundedBytesResult(context, res));
}
- /** {@hide} */
- public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
- 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;
+ 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);
}
- if (result > 900) {
- suffix = com.android.internal.R.string.megabyteShort;
- mult *= 1000;
- result = result / 1000;
+ }
+
+ 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);
}
- if (result > 900) {
- suffix = com.android.internal.R.string.gigabyteShort;
- mult *= 1000;
- result = result / 1000;
+ 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());
}
- if (result > 900) {
- suffix = com.android.internal.R.string.terabyteShort;
- mult *= 1000;
- result = result / 1000;
+ }
+
+ 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);
}
- if (result > 900) {
- suffix = com.android.internal.R.string.petabyteShort;
- mult *= 1000;
- result = result / 1000;
+ }
+
+ /** {@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();
}
- // 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;
- roundFormat = "%.2f";
+ 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;
+ }
+
+ /**
+ * 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;
}
- } else { // 10 <= result < 100
- if ((flags & FLAG_SHORTER) != 0) {
+ 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;
- roundFormat = "%.0f";
- } else {
+ roundDigits = 0;
+ } else if (result < 1) {
roundFactor = 100;
- roundFormat = "%.2f";
+ 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;
+ }
}
- }
-
- 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);
+ if (isNegative) {
+ result = -result;
+ }
- final String units = res.getString(suffix);
+ // 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 BytesResult(roundedString, units, roundedBytes);
+ return new RoundedBytesResult(result, units, roundDigits, roundedBytes);
+ }
}
/**
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 18e8af7836d2..8c26db43fc41 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -20,23 +20,13 @@
<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 "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>
+ <!-- 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>
<!-- Used in Contacts for a field that has no label and in Note Pad
for a note with no name. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8a7d8e3e8293..8c81f3f2a1f5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -677,7 +677,6 @@
<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" />
@@ -733,7 +732,6 @@
<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" />
@@ -754,7 +752,6 @@
<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" />
@@ -957,7 +954,6 @@
<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" />