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