Home | History | Annotate | Download | only in format
      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 
     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 */
     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      */
     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      */
     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} */
    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
    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      */
    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      */
    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