1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.text.format; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.net.NetworkUtils; 24 import android.text.BidiFormatter; 25 import android.text.TextUtils; 26 import android.view.View; 27 28 import java.util.Locale; 29 30 /** 31 * Utility class to aid in formatting common values that are not covered 32 * by the {@link java.util.Formatter} class in {@link java.util} 33 */ 34 public final class Formatter { 35 36 /** {@hide} */ 37 public static final int FLAG_SHORTER = 1 << 0; 38 /** {@hide} */ 39 public static final int FLAG_CALCULATE_ROUNDED = 1 << 1; 40 41 /** {@hide} */ 42 public static class BytesResult { 43 public final String value; 44 public final String units; 45 public final long roundedBytes; 46 BytesResult(String value, String units, long roundedBytes)47 public BytesResult(String value, String units, long roundedBytes) { 48 this.value = value; 49 this.units = units; 50 this.roundedBytes = roundedBytes; 51 } 52 } 53 54 /* Wraps the source string in bidi formatting characters in RTL locales */ bidiWrap(@onNull Context context, String source)55 private static String bidiWrap(@NonNull Context context, String source) { 56 final Locale locale = context.getResources().getConfiguration().locale; 57 if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) { 58 return BidiFormatter.getInstance(true /* RTL*/).unicodeWrap(source); 59 } else { 60 return source; 61 } 62 } 63 64 /** 65 * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc. 66 * 67 * <p>As of O, the prefixes are used in their standard meanings in the SI system, so kB = 1000 68 * bytes, MB = 1,000,000 bytes, etc.</p> 69 * 70 * <p class="note">In {@link android.os.Build.VERSION_CODES#N} and earlier, powers of 1024 are 71 * used instead, with KB = 1024 bytes, MB = 1,048,576 bytes, etc.</p> 72 * 73 * <p>If the context has a right-to-left locale, the returned string is wrapped in bidi 74 * formatting characters to make sure it's displayed correctly if inserted inside a 75 * right-to-left string. (This is useful in cases where the unit strings, like "MB", are 76 * left-to-right, but the locale is right-to-left.)</p> 77 * 78 * @param context Context to use to load the localized units 79 * @param sizeBytes size value to be formatted, in bytes 80 * @return formatted string with the number 81 */ formatFileSize(@ullable Context context, long sizeBytes)82 public static String formatFileSize(@Nullable Context context, long sizeBytes) { 83 if (context == null) { 84 return ""; 85 } 86 final BytesResult res = formatBytes(context.getResources(), sizeBytes, 0); 87 return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, 88 res.value, res.units)); 89 } 90 91 /** 92 * Like {@link #formatFileSize}, but trying to generate shorter numbers 93 * (showing fewer digits of precision). 94 */ formatShortFileSize(@ullable Context context, long sizeBytes)95 public static String formatShortFileSize(@Nullable Context context, long sizeBytes) { 96 if (context == null) { 97 return ""; 98 } 99 final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SHORTER); 100 return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, 101 res.value, res.units)); 102 } 103 104 /** {@hide} */ formatBytes(Resources res, long sizeBytes, int flags)105 public static BytesResult formatBytes(Resources res, long sizeBytes, int flags) { 106 final boolean isNegative = (sizeBytes < 0); 107 float result = isNegative ? -sizeBytes : sizeBytes; 108 int suffix = com.android.internal.R.string.byteShort; 109 long mult = 1; 110 if (result > 900) { 111 suffix = com.android.internal.R.string.kilobyteShort; 112 mult = 1000; 113 result = result / 1000; 114 } 115 if (result > 900) { 116 suffix = com.android.internal.R.string.megabyteShort; 117 mult *= 1000; 118 result = result / 1000; 119 } 120 if (result > 900) { 121 suffix = com.android.internal.R.string.gigabyteShort; 122 mult *= 1000; 123 result = result / 1000; 124 } 125 if (result > 900) { 126 suffix = com.android.internal.R.string.terabyteShort; 127 mult *= 1000; 128 result = result / 1000; 129 } 130 if (result > 900) { 131 suffix = com.android.internal.R.string.petabyteShort; 132 mult *= 1000; 133 result = result / 1000; 134 } 135 // Note we calculate the rounded long by ourselves, but still let String.format() 136 // compute the rounded value. String.format("%f", 0.1) might not return "0.1" due to 137 // floating point errors. 138 final int roundFactor; 139 final String roundFormat; 140 if (mult == 1 || result >= 100) { 141 roundFactor = 1; 142 roundFormat = "%.0f"; 143 } else if (result < 1) { 144 roundFactor = 100; 145 roundFormat = "%.2f"; 146 } else if (result < 10) { 147 if ((flags & FLAG_SHORTER) != 0) { 148 roundFactor = 10; 149 roundFormat = "%.1f"; 150 } else { 151 roundFactor = 100; 152 roundFormat = "%.2f"; 153 } 154 } else { // 10 <= result < 100 155 if ((flags & FLAG_SHORTER) != 0) { 156 roundFactor = 1; 157 roundFormat = "%.0f"; 158 } else { 159 roundFactor = 100; 160 roundFormat = "%.2f"; 161 } 162 } 163 164 if (isNegative) { 165 result = -result; 166 } 167 final String roundedString = String.format(roundFormat, result); 168 169 // Note this might overflow if abs(result) >= Long.MAX_VALUE / 100, but that's like 80PB so 170 // it's okay (for now)... 171 final long roundedBytes = 172 (flags & FLAG_CALCULATE_ROUNDED) == 0 ? 0 173 : (((long) Math.round(result * roundFactor)) * mult / roundFactor); 174 175 final String units = res.getString(suffix); 176 177 return new BytesResult(roundedString, units, roundedBytes); 178 } 179 180 /** 181 * Returns a string in the canonical IPv4 format ###.###.###.### from a packed integer 182 * containing the IP address. The IPv4 address is expected to be in little-endian 183 * format (LSB first). That is, 0x01020304 will return "4.3.2.1". 184 * 185 * @deprecated Use {@link java.net.InetAddress#getHostAddress()}, which supports both IPv4 and 186 * IPv6 addresses. This method does not support IPv6 addresses. 187 */ 188 @Deprecated formatIpAddress(int ipv4Address)189 public static String formatIpAddress(int ipv4Address) { 190 return NetworkUtils.intToInetAddress(ipv4Address).getHostAddress(); 191 } 192 193 private static final int SECONDS_PER_MINUTE = 60; 194 private static final int SECONDS_PER_HOUR = 60 * 60; 195 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 196 private static final int MILLIS_PER_MINUTE = 1000 * 60; 197 198 /** 199 * Returns elapsed time for the given millis, in the following format: 200 * 1 day 5 hrs; will include at most two units, can go down to seconds precision. 201 * @param context the application context 202 * @param millis the elapsed time in milli seconds 203 * @return the formatted elapsed time 204 * @hide 205 */ formatShortElapsedTime(Context context, long millis)206 public static String formatShortElapsedTime(Context context, long millis) { 207 long secondsLong = millis / 1000; 208 209 int days = 0, hours = 0, minutes = 0; 210 if (secondsLong >= SECONDS_PER_DAY) { 211 days = (int)(secondsLong / SECONDS_PER_DAY); 212 secondsLong -= days * SECONDS_PER_DAY; 213 } 214 if (secondsLong >= SECONDS_PER_HOUR) { 215 hours = (int)(secondsLong / SECONDS_PER_HOUR); 216 secondsLong -= hours * SECONDS_PER_HOUR; 217 } 218 if (secondsLong >= SECONDS_PER_MINUTE) { 219 minutes = (int)(secondsLong / SECONDS_PER_MINUTE); 220 secondsLong -= minutes * SECONDS_PER_MINUTE; 221 } 222 int seconds = (int)secondsLong; 223 224 if (days >= 2) { 225 days += (hours+12)/24; 226 return context.getString(com.android.internal.R.string.durationDays, days); 227 } else if (days > 0) { 228 if (hours == 1) { 229 return context.getString(com.android.internal.R.string.durationDayHour, days, hours); 230 } 231 return context.getString(com.android.internal.R.string.durationDayHours, days, hours); 232 } else if (hours >= 2) { 233 hours += (minutes+30)/60; 234 return context.getString(com.android.internal.R.string.durationHours, hours); 235 } else if (hours > 0) { 236 if (minutes == 1) { 237 return context.getString(com.android.internal.R.string.durationHourMinute, hours, 238 minutes); 239 } 240 return context.getString(com.android.internal.R.string.durationHourMinutes, hours, 241 minutes); 242 } else if (minutes >= 2) { 243 minutes += (seconds+30)/60; 244 return context.getString(com.android.internal.R.string.durationMinutes, minutes); 245 } else if (minutes > 0) { 246 if (seconds == 1) { 247 return context.getString(com.android.internal.R.string.durationMinuteSecond, minutes, 248 seconds); 249 } 250 return context.getString(com.android.internal.R.string.durationMinuteSeconds, minutes, 251 seconds); 252 } else if (seconds == 1) { 253 return context.getString(com.android.internal.R.string.durationSecond, seconds); 254 } else { 255 return context.getString(com.android.internal.R.string.durationSeconds, seconds); 256 } 257 } 258 259 /** 260 * Returns elapsed time for the given millis, in the following format: 261 * 1 day 5 hrs; will include at most two units, can go down to minutes precision. 262 * @param context the application context 263 * @param millis the elapsed time in milli seconds 264 * @return the formatted elapsed time 265 * @hide 266 */ formatShortElapsedTimeRoundingUpToMinutes(Context context, long millis)267 public static String formatShortElapsedTimeRoundingUpToMinutes(Context context, long millis) { 268 long minutesRoundedUp = (millis + MILLIS_PER_MINUTE - 1) / MILLIS_PER_MINUTE; 269 270 if (minutesRoundedUp == 0) { 271 return context.getString(com.android.internal.R.string.durationMinutes, 0); 272 } else if (minutesRoundedUp == 1) { 273 return context.getString(com.android.internal.R.string.durationMinute, 1); 274 } 275 276 return formatShortElapsedTime(context, minutesRoundedUp * MILLIS_PER_MINUTE); 277 } 278 } 279