Don't use framework strings for formatting file sizes
Modify various formatters to avoid using framework strings for
formatting file sizes.
Also update README instructions for running unit tests.
Bug: 36994779
Test: adb shell am instrument -w -e class com.android.settings.utils.FileSizeFormatterTest com.android.settings.tests.unit/android.support.test.runner.AndroidJUnitRunner
Test: make -j RunSettingsRoboTests
Change-Id: I4035f26d29408b64389892a4a2379b4823f8ac96
diff --git a/res/layout/data_usage_bytes_editor.xml b/res/layout/data_usage_bytes_editor.xml
index 2878c3e..af2d59b 100644
--- a/res/layout/data_usage_bytes_editor.xml
+++ b/res/layout/data_usage_bytes_editor.xml
@@ -37,7 +37,6 @@
android:id="@+id/size_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:entries="@array/bytes_picker_sizes" />
+ android:layout_gravity="center_vertical" />
</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8fc9432..ee26d75 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8328,11 +8328,6 @@
<!-- Text for the setting on whether you can type text into notifications without unlocking the device. -->
<string name="lockscreen_remote_input">If device is locked, prevent typing replies or other text in notifications</string>
- <string-array name="bytes_picker_sizes" translatable="false">
- <item>@*android:string/megabyteShort</item>
- <item>@*android:string/gigabyteShort</item>
- </string-array>
-
<!-- [CHAR LIMIT=30] Label for setting to control the default spell checker -->
<string name="default_spell_checker">Default spell checker</string>
diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java
index 1025ad7..ea0bcf0 100644
--- a/src/com/android/settings/datausage/BillingCycleSettings.java
+++ b/src/com/android/settings/datausage/BillingCycleSettings.java
@@ -21,6 +21,8 @@
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;
@@ -31,6 +33,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.NumberPicker;
import android.widget.Spinner;
@@ -248,6 +251,17 @@
: 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);
+
if (bytes > 1.5f * GB_IN_BYTES) {
final String bytesText = formatText(bytes / (float) GB_IN_BYTES);
bytesPicker.setText(bytesText);
diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java
index 3dcf935..d0114e3 100644
--- a/src/com/android/settings/deviceinfo/StorageItemPreference.java
+++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.icu.util.MeasureUnit;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
@@ -51,7 +52,7 @@
FileSizeFormatter.formatFileSize(
getContext(),
size,
- getGigabyteSuffix(getContext().getResources()),
+ MeasureUnit.GIGABYTE,
FileSizeFormatter.GIGABYTE_IN_BYTES));
if (total == 0) {
mProgressPercent = 0;
@@ -75,8 +76,4 @@
updateProgressBar();
super.onBindViewHolder(view);
}
-
- private static int getGigabyteSuffix(Resources res) {
- return res.getIdentifier("gigabyteShort", "string", "android");
- }
}
diff --git a/src/com/android/settings/utils/FileSizeFormatter.java b/src/com/android/settings/utils/FileSizeFormatter.java
index e56388a..c0d360f 100644
--- a/src/com/android/settings/utils/FileSizeFormatter.java
+++ b/src/com/android/settings/utils/FileSizeFormatter.java
@@ -16,11 +16,22 @@
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.TextUtils;
import android.text.format.Formatter;
+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 +42,61 @@
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;
+
+ public 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 +113,17 @@
*
* @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 +136,8 @@
* @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 +145,29 @@
// 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);
}
}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
index a154c03..5b34c7d 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java
@@ -55,7 +55,7 @@
@Test
public void testAfterLoad() {
mPreference.setStorageSize(MEGABYTE_IN_BYTES * 10, MEGABYTE_IN_BYTES * 100);
- assertThat(((String) mPreference.getSummary())).isEqualTo("0.01GB");
+ assertThat(((String) mPreference.getSummary())).isEqualTo("0.01 GB");
}
@Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
index fcda085..a871c19 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
@@ -104,7 +104,7 @@
verify(mGroup).addPreference(argumentCaptor.capture());
Preference preference = argumentCaptor.getValue();
- assertThat(preference.getSummary()).isEqualTo("0.01GB");
+ assertThat(preference.getSummary()).isEqualTo("0.01 GB");
}
@Test
@@ -177,7 +177,7 @@
verify(mGroup).addPreference(argumentCaptor.capture());
Preference preference = argumentCaptor.getValue();
- assertThat(preference.getSummary()).isEqualTo("0.03GB");
+ assertThat(preference.getSummary()).isEqualTo("0.03 GB");
}
@Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
index 0d6a4d7..1599440 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -84,8 +84,6 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- SettingsShadowResources.overrideResource("android:string/fileSizeSuffix", "%1$s %2$s");
- SettingsShadowResources.overrideResource("android:string/gigabyteShort", "GB");
mContext = spy(RuntimeEnvironment.application.getApplicationContext());
FakeFeatureFactory.setupForTest(mContext);
mFakeFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
@@ -287,12 +285,12 @@
results.put(0, result);
mController.onLoadFinished(results, 0);
- assertThat(audio.getSummary().toString()).isEqualTo("0.14GB");
- assertThat(image.getSummary().toString()).isEqualTo("0.35GB");
- assertThat(games.getSummary().toString()).isEqualTo("0.08GB");
- assertThat(movies.getSummary().toString()).isEqualTo("0.16GB");
- assertThat(apps.getSummary().toString()).isEqualTo("0.09GB");
- assertThat(files.getSummary().toString()).isEqualTo("0.05GB");
+ assertThat(audio.getSummary().toString()).isEqualTo("0.14 GB");
+ assertThat(image.getSummary().toString()).isEqualTo("0.35 GB");
+ assertThat(games.getSummary().toString()).isEqualTo("0.08 GB");
+ assertThat(movies.getSummary().toString()).isEqualTo("0.16 GB");
+ assertThat(apps.getSummary().toString()).isEqualTo("0.09 GB");
+ assertThat(files.getSummary().toString()).isEqualTo("0.05 GB");
}
@Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java
index db7c9f7..a2e57b9 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java
@@ -122,7 +122,7 @@
verify(mScreen).addPreference(argumentCaptor.capture());
Preference preference = argumentCaptor.getValue();
- assertThat(preference.getSummary()).isEqualTo("0.10GB");
+ assertThat(preference.getSummary()).isEqualTo("0.10 GB");
}
@Test
diff --git a/tests/unit/README b/tests/unit/README
index 5184b07..2544ea5 100644
--- a/tests/unit/README
+++ b/tests/unit/README
@@ -1,8 +1,8 @@
To build the tests you can use the following command at the root of your android source tree
-$ make SettingsUnitTests
+$ make -j SettingsUnitTests
The test apk then needs to be installed onto your test device via for example
-$ adb install -r out/target/product/shamu/data/app/SettingsUnitTests/SettingsUnitTests.apk
+$ adb install -r ${ANDROID_PRODUCT_OUT}/data/app/SettingsUnitTests/SettingsUnitTests.apk
To run all tests:
$ adb shell am instrument -w com.android.settings.tests.unit/android.support.test.runner.AndroidJUnitRunner
diff --git a/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java b/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java
index c5b050a..41b236c 100644
--- a/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java
+++ b/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.icu.util.MeasureUnit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -46,7 +47,7 @@
FileSizeFormatter.formatFileSize(
mContext,
0 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("0.00 GB");
}
@@ -57,7 +58,7 @@
FileSizeFormatter.formatFileSize(
mContext,
MEGABYTE_IN_BYTES * 11 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("0.01 GB");
}
@@ -68,7 +69,7 @@
FileSizeFormatter.formatFileSize(
mContext,
MEGABYTE_IN_BYTES * 155 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("0.16 GB");
}
@@ -79,7 +80,7 @@
FileSizeFormatter.formatFileSize(
mContext,
MEGABYTE_IN_BYTES * 1551 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("1.6 GB");
}
@@ -91,7 +92,7 @@
FileSizeFormatter.formatFileSize(
mContext,
GIGABYTE_IN_BYTES * 15 + MEGABYTE_IN_BYTES * 50 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("15 GB");
}
@@ -102,7 +103,7 @@
FileSizeFormatter.formatFileSize(
mContext,
MEGABYTE_IN_BYTES * -155 /* size */,
- com.android.internal.R.string.gigabyteShort,
+ MeasureUnit.GIGABYTE,
GIGABYTE_IN_BYTES))
.isEqualTo("-0.16 GB");
}