From 63a11c56de4f51eaabeafb54501fe3f6f564a61c Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Wed, 19 Apr 2017 11:37:02 -0700 Subject: [PATCH] Always use GB as the unit in Storage Settings. Bug: 36901322 Test: Settings unit tests Change-Id: I08c711db6a271522942a01d1adf3e8e5223010f7 --- .../deviceinfo/StorageItemPreference.java | 16 ++- .../settings/utils/FileSizeFormatter.java | 116 ++++++++++++++++++ .../deviceinfo/StorageItemPreferenceTest.java | 8 +- .../storage/SecondaryUserControllerTest.java | 14 ++- .../StorageItemPreferenceControllerTest.java | 49 +++++--- .../storage/UserProfileControllerTest.java | 10 +- .../shadow/SettingsShadowResources.java | 42 ++++++- .../settings/utils/FileSizeFormatterTest.java | 109 ++++++++++++++++ 8 files changed, 327 insertions(+), 37 deletions(-) create mode 100644 src/com/android/settings/utils/FileSizeFormatter.java create mode 100644 tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java index 763b6d4bda8..6ae6c1adc4e 100644 --- a/src/com/android/settings/deviceinfo/StorageItemPreference.java +++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java @@ -17,14 +17,15 @@ package com.android.settings.deviceinfo; import android.content.Context; +import android.content.res.Resources; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; -import android.text.format.Formatter; import android.util.AttributeSet; import android.view.View; import android.widget.ProgressBar; import com.android.settings.R; +import com.android.settings.utils.FileSizeFormatter; public class StorageItemPreference extends Preference { public int userHandle; @@ -44,9 +45,12 @@ public class StorageItemPreference extends Preference { } public void setStorageSize(long size, long total) { - setSummary(size == 0 - ? String.valueOf(0) - : Formatter.formatFileSize(getContext(), size)); + setSummary( + FileSizeFormatter.formatFileSize( + getContext(), + size, + getGigabyteSuffix(getContext().getResources()), + FileSizeFormatter.GIGABYTE_IN_BYTES)); if (total == 0) { mProgressPercent = 0; } else { @@ -75,4 +79,8 @@ public class StorageItemPreference extends Preference { 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 new file mode 100644 index 00000000000..e56388af543 --- /dev/null +++ b/src/com/android/settings/utils/FileSizeFormatter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.utils; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.text.BidiFormatter; +import android.text.format.Formatter; + +/** + * Utility class to aid in formatting file sizes always with the same unit. This is modified from + * android.text.format.Formatter to fit this purpose. + */ +public final class FileSizeFormatter { + public static final long KILOBYTE_IN_BYTES = 1000; + public static final long MEGABYTE_IN_BYTES = KILOBYTE_IN_BYTES * 1000; + public static final long GIGABYTE_IN_BYTES = MEGABYTE_IN_BYTES * 1000; + + /** + * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc. + * + *

As of O, the prefixes are used in their standard meanings in the SI system, so kB = 1000 + * bytes, MB = 1,000,000 bytes, etc. + * + *

In {@link android.os.Build.VERSION_CODES#N} and earlier, powers of 1024 are + * used instead, with KB = 1024 bytes, MB = 1,048,576 bytes, etc. + * + *

If the context has a right-to-left locale, the returned string is wrapped in bidi + * formatting characters to make sure it's displayed correctly if inserted inside a + * right-to-left string. (This is useful in cases where the unit strings, like "MB", are + * left-to-right, but the locale is right-to-left.) + * + * @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 + */ + public static String formatFileSize( + @Nullable Context context, long sizeBytes, int suffix, 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"); + } + + /** + * A simplified version of the SettingsLib file size formatter. The primary difference is that + * this version always assumes it is doing a "short file size" and allows for a suffix to be + * provided. + * + * @param res Resources to fetch strings with. + * @param sizeBytes File size in bytes to format. + * @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) { + final boolean isNegative = (sizeBytes < 0); + float result = isNegative ? -sizeBytes : sizeBytes; + result = result / mult; + // 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) { + roundFactor = 1; + roundFormat = "%.0f"; + } else if (result < 1) { + roundFactor = 100; + roundFormat = "%.2f"; + } else if (result < 10) { + roundFactor = 10; + roundFormat = "%.1f"; + } else { // 10 <= result < 100 + roundFactor = 1; + roundFormat = "%.0f"; + } + + 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); + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java index 969719fd16d..6cb8f5824c0 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageItemPreferenceTest.java @@ -15,7 +15,7 @@ */ package com.android.settings.deviceinfo; -import static com.android.settings.TestUtils.KILOBYTE; +import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES; import static com.google.common.truth.Truth.assertThat; @@ -54,8 +54,8 @@ public class StorageItemPreferenceTest { @Test public void testAfterLoad() { - mPreference.setStorageSize(KILOBYTE, KILOBYTE * 10); - assertThat(((String) mPreference.getSummary())).isEqualTo("1.00KB"); + mPreference.setStorageSize(MEGABYTE_IN_BYTES * 10, MEGABYTE_IN_BYTES * 100); + assertThat(((String) mPreference.getSummary())).isEqualTo("0.01GB"); } @Test @@ -66,7 +66,7 @@ public class StorageItemPreferenceTest { (ProgressBar) holder.itemView.findViewById(android.R.id.progress); mPreference.onBindViewHolder(holder); - mPreference.setStorageSize(KILOBYTE, KILOBYTE * 10); + mPreference.setStorageSize(MEGABYTE_IN_BYTES, MEGABYTE_IN_BYTES * 10); assertThat(progressBar.getProgress()).isEqualTo(10); } 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 963e35f1c5c..32def6956b8 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java @@ -17,6 +17,7 @@ package com.android.settings.deviceinfo.storage; import static com.google.common.truth.Truth.assertThat; +import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.verify; @@ -96,13 +97,13 @@ public class SecondaryUserControllerTest { public void controllerUpdatesSummaryOfNewPreference() throws Exception { mPrimaryUser.name = TEST_NAME; mController.displayPreference(mScreen); - mController.setSize(10L); + mController.setSize(MEGABYTE_IN_BYTES * 10); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); verify(mGroup).addPreference(argumentCaptor.capture()); Preference preference = argumentCaptor.getValue(); - assertThat(preference.getSummary()).isEqualTo("10.00B"); + assertThat(preference.getSummary()).isEqualTo("0.01GB"); } @Test @@ -162,7 +163,12 @@ public class SecondaryUserControllerTest { StorageAsyncLoader.AppsStorageResult userResult = new StorageAsyncLoader.AppsStorageResult(); SparseArray result = new SparseArray<>(); - userResult.externalStats = new StorageStatsSource.ExternalStorageStats(99, 33, 33, 33); + userResult.externalStats = + new StorageStatsSource.ExternalStorageStats( + MEGABYTE_IN_BYTES * 30, + MEGABYTE_IN_BYTES * 10, + MEGABYTE_IN_BYTES * 10, + MEGABYTE_IN_BYTES * 10); result.put(10, userResult); mController.handleResult(result); @@ -170,7 +176,7 @@ public class SecondaryUserControllerTest { verify(mGroup).addPreference(argumentCaptor.capture()); Preference preference = argumentCaptor.getValue(); - assertThat(preference.getSummary()).isEqualTo("99.00B"); + assertThat(preference.getSummary()).isEqualTo("0.03GB"); } @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 47d910d3760..588ac4b3054 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -15,9 +15,9 @@ */ package com.android.settings.deviceinfo.storage; -import static com.android.settings.TestUtils.KILOBYTE; import static com.google.common.truth.Truth.assertThat; +import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; @@ -28,6 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; + import android.app.Fragment; import android.content.Context; import android.content.Intent; @@ -50,9 +51,11 @@ import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.deviceinfo.PrivateVolumeSettings; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.deviceinfo.StorageVolumeProvider; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -80,6 +83,8 @@ public class StorageItemPreferenceControllerTest { @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); @@ -96,6 +101,11 @@ public class StorageItemPreferenceControllerTest { mPreference.getLayoutResource(), new LinearLayout(mContext), false); } + @After + public void tearDown() { + SettingsShadowResources.reset(); + } + @Test public void testUpdateStateWithInitialState() { assertThat(mPreference.getSummary().toString()).isEqualTo( @@ -243,28 +253,29 @@ public class StorageItemPreferenceControllerTest { eq(StorageItemPreferenceController.FILES_KEY))).thenReturn(files); mController.displayPreference(screen); - mController.setUsedSize(KILOBYTE * 200); // There should 87kB attributed. + mController.setUsedSize(MEGABYTE_IN_BYTES * 970); // There should 870MB attributed. StorageAsyncLoader.AppsStorageResult result = new StorageAsyncLoader.AppsStorageResult(); - result.gamesSize = KILOBYTE * 8; - result.videoAppsSize = KILOBYTE * 16; - result.musicAppsSize = KILOBYTE * 4; - result.otherAppsSize = KILOBYTE * 9; - result.systemSize = KILOBYTE * 10; // This value is ignored and overriden now. - result.externalStats = new StorageStatsSource.ExternalStorageStats( - KILOBYTE * 50, // total - KILOBYTE * 10, // audio - KILOBYTE * 15, // video - KILOBYTE * 20); // image + result.gamesSize = MEGABYTE_IN_BYTES * 80; + result.videoAppsSize = MEGABYTE_IN_BYTES * 160; + result.musicAppsSize = MEGABYTE_IN_BYTES * 40; + result.otherAppsSize = MEGABYTE_IN_BYTES * 90; + result.systemSize = MEGABYTE_IN_BYTES * 100; // This value is ignored and overridden now. + result.externalStats = + new StorageStatsSource.ExternalStorageStats( + MEGABYTE_IN_BYTES * 500, // total + MEGABYTE_IN_BYTES * 100, // audio + MEGABYTE_IN_BYTES * 150, // video + MEGABYTE_IN_BYTES * 200); // image mController.onLoadFinished(result); - assertThat(audio.getSummary().toString()).isEqualTo("14.00KB"); // 4KB apps + 10KB files - assertThat(image.getSummary().toString()).isEqualTo("35.00KB"); // 15KB video + 20KB images - assertThat(games.getSummary().toString()).isEqualTo("8.00KB"); - assertThat(movies.getSummary().toString()).isEqualTo("16.00KB"); - assertThat(apps.getSummary().toString()).isEqualTo("9.00KB"); - assertThat(system.getSummary().toString()).isEqualTo("113KB"); - assertThat(files.getSummary().toString()).isEqualTo("5.00KB"); + 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(system.getSummary().toString()).isEqualTo("0.10GB"); + assertThat(files.getSummary().toString()).isEqualTo("0.05GB"); } @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 0c3fc47216c..8b7110d8d02 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java @@ -17,6 +17,7 @@ package com.android.settings.deviceinfo.storage; import static com.google.common.truth.Truth.assertThat; +import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -108,7 +109,12 @@ public class UserProfileControllerTest { SparseArray result = new SparseArray<>(); StorageAsyncLoader.AppsStorageResult userResult = new StorageAsyncLoader.AppsStorageResult(); - userResult.externalStats = new StorageStatsSource.ExternalStorageStats(99, 33, 33, 33); + userResult.externalStats = + new StorageStatsSource.ExternalStorageStats( + 99 * MEGABYTE_IN_BYTES, + 33 * MEGABYTE_IN_BYTES, + 33 * MEGABYTE_IN_BYTES, + 33 * MEGABYTE_IN_BYTES); result.put(10, userResult); mController.handleResult(result); @@ -116,7 +122,7 @@ public class UserProfileControllerTest { verify(mScreen).addPreference(argumentCaptor.capture()); Preference preference = argumentCaptor.getValue(); - assertThat(preference.getSummary()).isEqualTo("99.00B"); + assertThat(preference.getSummary()).isEqualTo("0.10GB"); } @Test diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java index 724909df7c0..ba828350588 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/SettingsShadowResources.java @@ -1,5 +1,11 @@ package com.android.settings.testutils.shadow; +import static android.util.TypedValue.TYPE_REFERENCE; + +import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; +import static org.robolectric.internal.Shadow.directlyOn; + import android.annotation.DimenRes; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; @@ -12,6 +18,7 @@ import android.support.annotation.ArrayRes; import android.support.annotation.ColorRes; import android.support.annotation.Nullable; import android.util.AttributeSet; +import android.util.SparseArray; import android.util.TypedValue; import com.android.settings.R; @@ -20,21 +27,19 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; +import org.robolectric.internal.Shadow; import org.robolectric.res.StyleData; import org.robolectric.res.StyleResolver; import org.robolectric.res.builder.XmlResourceParserImpl; import org.robolectric.shadows.ShadowAssetManager; import org.robolectric.shadows.ShadowResources; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.w3c.dom.Node; import java.util.List; import java.util.Map; -import static android.util.TypedValue.TYPE_REFERENCE; -import static org.robolectric.Shadows.shadowOf; -import static org.robolectric.internal.Shadow.directlyOn; - /** * Shadow Resources and Theme classes to handle resource references that Robolectric shadows cannot * handle because they are too new or private. @@ -45,6 +50,25 @@ public class SettingsShadowResources extends ShadowResources { @RealObject public Resources realResources; + private static SparseArray sResourceOverrides = new SparseArray<>(); + + public static void overrideResource(int id, Object value) { + sResourceOverrides.put(id, value); + } + + public static void overrideResource(String name, Object value) { + final Resources res = application.getResources(); + final int resId = res.getIdentifier(name, null, null); + if (resId == 0) { + throw new Resources.NotFoundException("Cannot override \"" + name + "\""); + } + overrideResource(resId, value); + } + + public static void reset() { + sResourceOverrides.clear(); + } + @Implementation public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException { // Handle requests for private dimension resources, @@ -93,6 +117,16 @@ public class SettingsShadowResources extends ShadowResources { return directlyOn(realResources, Resources.class).getIntArray(id); } + @Implementation + public String getString(int id) { + final Object override = sResourceOverrides.get(id); + if (override instanceof String) { + return (String) override; + } + return Shadow.directlyOn( + realResources, Resources.class, "getString", ClassParameter.from(int.class, id)); + } + @Implements(Theme.class) public static class SettingsShadowTheme extends ShadowTheme { diff --git a/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java b/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java new file mode 100644 index 00000000000..c5b050a5a7e --- /dev/null +++ b/tests/unit/src/com/android/settings/utils/FileSizeFormatterTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.utils; + +import static com.android.settings.utils.FileSizeFormatter.GIGABYTE_IN_BYTES; +import static com.android.settings.utils.FileSizeFormatter.MEGABYTE_IN_BYTES; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class FileSizeFormatterTest { + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + } + + @Test + public void formatFileSize_zero() throws Exception { + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + 0 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("0.00 GB"); + } + + @Test + public void formatFileSize_smallSize() throws Exception { + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + MEGABYTE_IN_BYTES * 11 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("0.01 GB"); + } + + @Test + public void formatFileSize_lessThanOneSize() throws Exception { + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + MEGABYTE_IN_BYTES * 155 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("0.16 GB"); + } + + @Test + public void formatFileSize_greaterThanOneSize() throws Exception { + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + MEGABYTE_IN_BYTES * 1551 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("1.6 GB"); + } + + @Test + public void formatFileSize_greaterThanTen() throws Exception { + // Should round down due to truncation + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + GIGABYTE_IN_BYTES * 15 + MEGABYTE_IN_BYTES * 50 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("15 GB"); + } + + @Test + public void formatFileSize_handlesNegativeFileSizes() throws Exception { + assertThat( + FileSizeFormatter.formatFileSize( + mContext, + MEGABYTE_IN_BYTES * -155 /* size */, + com.android.internal.R.string.gigabyteShort, + GIGABYTE_IN_BYTES)) + .isEqualTo("-0.16 GB"); + } +}