Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d990345d authored by Victor Chang's avatar Victor Chang
Browse files

Revert "Switch file size formatters to use ICU's MeasureFormat"

This reverts commit 4e5b71f0.

Test: cts-tradefed run cts-dev -m CtsTextTestCases
Bug: 70005649
Bug: 36994779
Change-Id: Ie9a2fd786d48e2a6c291e313cbb4072c7306af9f
parent 3305bce7
Loading
Loading
Loading
Loading
+72 −179
Original line number Original line Diff line number Diff line
@@ -20,11 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources;
import android.icu.text.DecimalFormat;
import android.icu.text.MeasureFormat;
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.Measure;
import android.icu.util.MeasureUnit;
import android.icu.util.MeasureUnit;
import android.net.NetworkUtils;
import android.net.NetworkUtils;
@@ -32,7 +28,6 @@ import android.text.BidiFormatter;
import android.text.TextUtils;
import android.text.TextUtils;
import android.view.View;
import android.view.View;


import java.math.BigDecimal;
import java.util.Locale;
import java.util.Locale;


/**
/**
@@ -41,8 +36,6 @@ import java.util.Locale;
 */
 */
public final class Formatter {
public final class Formatter {


    /** {@hide} */
    public static final int FLAG_DEFAULT = 0;
    /** {@hide} */
    /** {@hide} */
    public static final int FLAG_SHORTER = 1 << 0;
    public static final int FLAG_SHORTER = 1 << 0;
    /** {@hide} */
    /** {@hide} */
@@ -65,9 +58,7 @@ public final class Formatter {
        return context.getResources().getConfiguration().getLocales().get(0);
        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) {
    private static String bidiWrap(@NonNull Context context, String source) {
        final Locale locale = localeFromContext(context);
        final Locale locale = localeFromContext(context);
        if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
        if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
@@ -96,7 +87,12 @@ public final class Formatter {
     * @return formatted string with the number
     * @return formatted string with the number
     */
     */
    public static String formatFileSize(@Nullable Context context, long sizeBytes) {
    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).
     * (showing fewer digits of precision).
     */
     */
    public static String formatShortFileSize(@Nullable Context context, long sizeBytes) {
    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) {
        if (context == null) {
            return "";
            return "";
        }
        }
        final RoundedBytesResult res = RoundedBytesResult.roundBytes(sizeBytes, flags);
        final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER);
        return bidiWrap(context, formatRoundedBytesResult(context, res));
        return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix,
    }
                res.value, res.units));

    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);
        }
    }
    }


    /** {@hide} */
    /** {@hide} */
    public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) {
    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();
        }
        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);
        final boolean isNegative = (sizeBytes < 0);
        float result = isNegative ? -sizeBytes : sizeBytes;
        float result = isNegative ? -sizeBytes : sizeBytes;
            MeasureUnit units = MeasureUnit.BYTE;
        int suffix = com.android.internal.R.string.byteShort;
        long mult = 1;
        long mult = 1;
        if (result > 900) {
        if (result > 900) {
                units = MeasureUnit.KILOBYTE;
            suffix = com.android.internal.R.string.kilobyteShort;
            mult = 1000;
            mult = 1000;
            result = result / 1000;
            result = result / 1000;
        }
        }
        if (result > 900) {
        if (result > 900) {
                units = MeasureUnit.MEGABYTE;
            suffix = com.android.internal.R.string.megabyteShort;
            mult *= 1000;
            mult *= 1000;
            result = result / 1000;
            result = result / 1000;
        }
        }
        if (result > 900) {
        if (result > 900) {
                units = MeasureUnit.GIGABYTE;
            suffix = com.android.internal.R.string.gigabyteShort;
            mult *= 1000;
            mult *= 1000;
            result = result / 1000;
            result = result / 1000;
        }
        }
        if (result > 900) {
        if (result > 900) {
                units = MeasureUnit.TERABYTE;
            suffix = com.android.internal.R.string.terabyteShort;
            mult *= 1000;
            mult *= 1000;
            result = result / 1000;
            result = result / 1000;
        }
        }
        if (result > 900) {
        if (result > 900) {
                units = PETABYTE;
            suffix = com.android.internal.R.string.petabyteShort;
            mult *= 1000;
            mult *= 1000;
            result = result / 1000;
            result = result / 1000;
        }
        }
            // Note we calculate the rounded long by ourselves, but still let NumberFormat compute
        // Note we calculate the rounded long by ourselves, but still let String.format()
            // the rounded value. NumberFormat.format(0.1) might not return "0.1" due to floating
        // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to
            // point errors.
        // floating point errors.
        final int roundFactor;
        final int roundFactor;
            final int roundDigits;
        final String roundFormat;
        if (mult == 1 || result >= 100) {
        if (mult == 1 || result >= 100) {
            roundFactor = 1;
            roundFactor = 1;
                roundDigits = 0;
            roundFormat = "%.0f";
        } else if (result < 1) {
        } else if (result < 1) {
            roundFactor = 100;
            roundFactor = 100;
                roundDigits = 2;
            roundFormat = "%.2f";
        } else if (result < 10) {
        } else if (result < 10) {
            if ((flags & FLAG_SHORTER) != 0) {
            if ((flags & FLAG_SHORTER) != 0) {
                roundFactor = 10;
                roundFactor = 10;
                    roundDigits = 1;
                roundFormat = "%.1f";
            } else {
            } else {
                roundFactor = 100;
                roundFactor = 100;
                    roundDigits = 2;
                roundFormat = "%.2f";
            }
            }
        } else { // 10 <= result < 100
        } else { // 10 <= result < 100
            if ((flags & FLAG_SHORTER) != 0) {
            if ((flags & FLAG_SHORTER) != 0) {
                roundFactor = 1;
                roundFactor = 1;
                    roundDigits = 0;
                roundFormat = "%.0f";
            } else {
            } else {
                roundFactor = 100;
                roundFactor = 100;
                    roundDigits = 2;
                roundFormat = "%.2f";
            }
            }
        }
        }


        if (isNegative) {
        if (isNegative) {
            result = -result;
            result = -result;
        }
        }
        final String roundedString = String.format(roundFormat, result);


            // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like
        // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so
            // 80PB so it's okay (for now)...
        // it's okay (for now)...
        final long roundedBytes =
        final long roundedBytes =
                (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
                (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0
                : (((long) Math.round(result * roundFactor)) * mult / roundFactor);
                : (((long) Math.round(result * roundFactor)) * mult / roundFactor);


            return new RoundedBytesResult(result, units, roundDigits, roundedBytes);
        final String units = res.getString(suffix);
        }

        return new BytesResult(roundedString, units, roundedBytes);
    }
    }


    /**
    /**
+15 −5
Original line number Original line Diff line number Diff line
@@ -20,13 +20,23 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <!-- Suffix added to a number to signify size in bytes. -->
    <!-- Suffix added to a number to signify size in bytes. -->
    <string name="byteShort">B</string>
    <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. -->
    <!-- Suffix added to a number to signify size in petabytes. -->
    <string name="petabyteShort">PB</string>
    <string name="petabyteShort">PB</string>
    <!-- Format string used to add a suffix like "B" or "PB" to a number
    <!-- Format string used to add a suffix like "kB" or "MB" to a number
         to display a size in bytes or petabytes.
         to display a size in kilobytes, megabytes, or other size units.
         Some languages may want to remove the space between the placeholders
         Some languages (like French) will want to add a space between
         or replace it with a non-breaking space. -->
         the placeholders. -->
    <string name="fileSizeSuffix"><xliff:g id="number" example="123">%1$s</xliff:g> <xliff:g id="unit" example="B">%2$s</xliff:g></string>
    <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
    <!-- Used in Contacts for a field that has no label and in Note Pad
         for a note with no name. -->
         for a note with no name. -->
+4 −0
Original line number Original line Diff line number Diff line
@@ -705,6 +705,7 @@
  <java-symbol type="string" name="fileSizeSuffix" />
  <java-symbol type="string" name="fileSizeSuffix" />
  <java-symbol type="string" name="force_close" />
  <java-symbol type="string" name="force_close" />
  <java-symbol type="string" name="gadget_host_error_inflating" />
  <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="gpsNotifMessage" />
  <java-symbol type="string" name="gpsNotifTicker" />
  <java-symbol type="string" name="gpsNotifTicker" />
  <java-symbol type="string" name="gpsNotifTitle" />
  <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_enter" />
  <java-symbol type="string" name="keyboardview_keycode_mode_change" />
  <java-symbol type="string" name="keyboardview_keycode_mode_change" />
  <java-symbol type="string" name="keyboardview_keycode_shift" />
  <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="last_month" />
  <java-symbol type="string" name="launchBrowserDefault" />
  <java-symbol type="string" name="launchBrowserDefault" />
  <java-symbol type="string" name="lock_to_app_toast" />
  <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_emergency_call" />
  <java-symbol type="string" name="lockscreen_return_to_call" />
  <java-symbol type="string" name="lockscreen_return_to_call" />
  <java-symbol type="string" name="low_memory" />
  <java-symbol type="string" name="low_memory" />
  <java-symbol type="string" name="megabyteShort" />
  <java-symbol type="string" name="midnight" />
  <java-symbol type="string" name="midnight" />
  <java-symbol type="string" name="mismatchPin" />
  <java-symbol type="string" name="mismatchPin" />
  <java-symbol type="string" name="mmiComplete" />
  <java-symbol type="string" name="mmiComplete" />
@@ -981,6 +984,7 @@
  <java-symbol type="string" name="sync_really_delete" />
  <java-symbol type="string" name="sync_really_delete" />
  <java-symbol type="string" name="sync_too_many_deletes_desc" />
  <java-symbol type="string" name="sync_too_many_deletes_desc" />
  <java-symbol type="string" name="sync_undo_deletes" />
  <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="text_copied" />
  <java-symbol type="string" name="time_of_day" />
  <java-symbol type="string" name="time_of_day" />
  <java-symbol type="string" name="time_picker_decrement_hour_button" />
  <java-symbol type="string" name="time_picker_decrement_hour_button" />