Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2014 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.media;
     18 
     19 import android.content.Context;
     20 import android.content.ContentResolver;
     21 import android.database.Cursor;
     22 import android.net.Uri;
     23 import android.os.Environment;
     24 import android.os.FileUtils;
     25 import android.provider.OpenableColumns;
     26 import android.util.Log;
     27 import android.util.Pair;
     28 import android.util.Range;
     29 import android.util.Rational;
     30 import android.util.Size;
     31 
     32 import java.io.File;
     33 import java.io.FileNotFoundException;
     34 import java.util.Arrays;
     35 import java.util.Comparator;
     36 import java.util.Vector;
     37 
     38 // package private
     39 class Utils {
     40     private static final String TAG = "Utils";
     41 
     42     /**
     43      * Sorts distinct (non-intersecting) range array in ascending order.
     44      * @throws java.lang.IllegalArgumentException if ranges are not distinct
     45      */
     46     public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) {
     47         Arrays.sort(ranges, new Comparator<Range<T>>() {
     48             @Override
     49             public int compare(Range<T> lhs, Range<T> rhs) {
     50                 if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
     51                     return -1;
     52                 } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
     53                     return 1;
     54                 }
     55                 throw new IllegalArgumentException(
     56                         "sample rate ranges must be distinct (" + lhs + " and " + rhs + ")");
     57             }
     58         });
     59     }
     60 
     61     /**
     62      * Returns the intersection of two sets of non-intersecting ranges
     63      * @param one a sorted set of non-intersecting ranges in ascending order
     64      * @param another another sorted set of non-intersecting ranges in ascending order
     65      * @return the intersection of the two sets, sorted in ascending order
     66      */
     67     public static <T extends Comparable<? super T>>
     68             Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) {
     69         int ix = 0;
     70         Vector<Range<T>> result = new Vector<Range<T>>();
     71         for (Range<T> range: another) {
     72             while (ix < one.length &&
     73                     one[ix].getUpper().compareTo(range.getLower()) < 0) {
     74                 ++ix;
     75             }
     76             while (ix < one.length &&
     77                     one[ix].getUpper().compareTo(range.getUpper()) < 0) {
     78                 result.add(range.intersect(one[ix]));
     79                 ++ix;
     80             }
     81             if (ix == one.length) {
     82                 break;
     83             }
     84             if (one[ix].getLower().compareTo(range.getUpper()) <= 0) {
     85                 result.add(range.intersect(one[ix]));
     86             }
     87         }
     88         return result.toArray(new Range[result.size()]);
     89     }
     90 
     91     /**
     92      * Returns the index of the range that contains a value in a sorted array of distinct ranges.
     93      * @param ranges a sorted array of non-intersecting ranges in ascending order
     94      * @param value the value to search for
     95      * @return if the value is in one of the ranges, it returns the index of that range.  Otherwise,
     96      * the return value is {@code (-1-index)} for the {@code index} of the range that is
     97      * immediately following {@code value}.
     98      */
     99     public static <T extends Comparable<? super T>>
    100             int binarySearchDistinctRanges(Range<T>[] ranges, T value) {
    101         return Arrays.binarySearch(ranges, Range.create(value, value),
    102                 new Comparator<Range<T>>() {
    103                     @Override
    104                     public int compare(Range<T> lhs, Range<T> rhs) {
    105                         if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
    106                             return -1;
    107                         } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
    108                             return 1;
    109                         }
    110                         return 0;
    111                     }
    112                 });
    113     }
    114 
    115     /**
    116      * Returns greatest common divisor
    117      */
    118     static int gcd(int a, int b) {
    119         if (a == 0 && b == 0) {
    120             return 1;
    121         }
    122         if (b < 0) {
    123             b = -b;
    124         }
    125         if (a < 0) {
    126             a = -a;
    127         }
    128         while (a != 0) {
    129             int c = b % a;
    130             b = a;
    131             a = c;
    132         }
    133         return b;
    134     }
    135 
    136     /** Returns the equivalent factored range {@code newrange}, where for every
    137      * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
    138      * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
    139      */
    140     static Range<Integer>factorRange(Range<Integer> range, int factor) {
    141         if (factor == 1) {
    142             return range;
    143         }
    144         return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
    145     }
    146 
    147     /** Returns the equivalent factored range {@code newrange}, where for every
    148      * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
    149      * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
    150      */
    151     static Range<Long>factorRange(Range<Long> range, long factor) {
    152         if (factor == 1) {
    153             return range;
    154         }
    155         return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
    156     }
    157 
    158     private static Rational scaleRatio(Rational ratio, int num, int den) {
    159         int common = gcd(num, den);
    160         num /= common;
    161         den /= common;
    162         return new Rational(
    163                 (int)(ratio.getNumerator() * (double)num),     // saturate to int
    164                 (int)(ratio.getDenominator() * (double)den));  // saturate to int
    165     }
    166 
    167     static Range<Rational> scaleRange(Range<Rational> range, int num, int den) {
    168         if (num == den) {
    169             return range;
    170         }
    171         return Range.create(
    172                 scaleRatio(range.getLower(), num, den),
    173                 scaleRatio(range.getUpper(), num, den));
    174     }
    175 
    176     static Range<Integer> alignRange(Range<Integer> range, int align) {
    177         return range.intersect(
    178                 divUp(range.getLower(), align) * align,
    179                 (range.getUpper() / align) * align);
    180     }
    181 
    182     static int divUp(int num, int den) {
    183         return (num + den - 1) / den;
    184     }
    185 
    186     static long divUp(long num, long den) {
    187         return (num + den - 1) / den;
    188     }
    189 
    190     /**
    191      * Returns least common multiple
    192      */
    193     private static long lcm(int a, int b) {
    194         if (a == 0 || b == 0) {
    195             throw new IllegalArgumentException("lce is not defined for zero arguments");
    196         }
    197         return (long)a * b / gcd(a, b);
    198     }
    199 
    200     static Range<Integer> intRangeFor(double v) {
    201         return Range.create((int)v, (int)Math.ceil(v));
    202     }
    203 
    204     static Range<Long> longRangeFor(double v) {
    205         return Range.create((long)v, (long)Math.ceil(v));
    206     }
    207 
    208     static Size parseSize(Object o, Size fallback) {
    209         try {
    210             return Size.parseSize((String) o);
    211         } catch (ClassCastException e) {
    212         } catch (NumberFormatException e) {
    213         } catch (NullPointerException e) {
    214             return fallback;
    215         }
    216         Log.w(TAG, "could not parse size '" + o + "'");
    217         return fallback;
    218     }
    219 
    220     static int parseIntSafely(Object o, int fallback) {
    221         if (o == null) {
    222             return fallback;
    223         }
    224         try {
    225             String s = (String)o;
    226             return Integer.parseInt(s);
    227         } catch (ClassCastException e) {
    228         } catch (NumberFormatException e) {
    229         } catch (NullPointerException e) {
    230             return fallback;
    231         }
    232         Log.w(TAG, "could not parse integer '" + o + "'");
    233         return fallback;
    234     }
    235 
    236     static Range<Integer> parseIntRange(Object o, Range<Integer> fallback) {
    237         try {
    238             String s = (String)o;
    239             int ix = s.indexOf('-');
    240             if (ix >= 0) {
    241                 return Range.create(
    242                         Integer.parseInt(s.substring(0, ix), 10),
    243                         Integer.parseInt(s.substring(ix + 1), 10));
    244             }
    245             int value = Integer.parseInt(s);
    246             return Range.create(value, value);
    247         } catch (ClassCastException e) {
    248         } catch (NumberFormatException e) {
    249         } catch (NullPointerException e) {
    250             return fallback;
    251         } catch (IllegalArgumentException e) {
    252         }
    253         Log.w(TAG, "could not parse integer range '" + o + "'");
    254         return fallback;
    255     }
    256 
    257     static Range<Long> parseLongRange(Object o, Range<Long> fallback) {
    258         try {
    259             String s = (String)o;
    260             int ix = s.indexOf('-');
    261             if (ix >= 0) {
    262                 return Range.create(
    263                         Long.parseLong(s.substring(0, ix), 10),
    264                         Long.parseLong(s.substring(ix + 1), 10));
    265             }
    266             long value = Long.parseLong(s);
    267             return Range.create(value, value);
    268         } catch (ClassCastException e) {
    269         } catch (NumberFormatException e) {
    270         } catch (NullPointerException e) {
    271             return fallback;
    272         } catch (IllegalArgumentException e) {
    273         }
    274         Log.w(TAG, "could not parse long range '" + o + "'");
    275         return fallback;
    276     }
    277 
    278     static Range<Rational> parseRationalRange(Object o, Range<Rational> fallback) {
    279         try {
    280             String s = (String)o;
    281             int ix = s.indexOf('-');
    282             if (ix >= 0) {
    283                 return Range.create(
    284                         Rational.parseRational(s.substring(0, ix)),
    285                         Rational.parseRational(s.substring(ix + 1)));
    286             }
    287             Rational value = Rational.parseRational(s);
    288             return Range.create(value, value);
    289         } catch (ClassCastException e) {
    290         } catch (NumberFormatException e) {
    291         } catch (NullPointerException e) {
    292             return fallback;
    293         } catch (IllegalArgumentException e) {
    294         }
    295         Log.w(TAG, "could not parse rational range '" + o + "'");
    296         return fallback;
    297     }
    298 
    299     static Pair<Size, Size> parseSizeRange(Object o) {
    300         try {
    301             String s = (String)o;
    302             int ix = s.indexOf('-');
    303             if (ix >= 0) {
    304                 return Pair.create(
    305                         Size.parseSize(s.substring(0, ix)),
    306                         Size.parseSize(s.substring(ix + 1)));
    307             }
    308             Size value = Size.parseSize(s);
    309             return Pair.create(value, value);
    310         } catch (ClassCastException e) {
    311         } catch (NumberFormatException e) {
    312         } catch (NullPointerException e) {
    313             return null;
    314         } catch (IllegalArgumentException e) {
    315         }
    316         Log.w(TAG, "could not parse size range '" + o + "'");
    317         return null;
    318     }
    319 
    320     /**
    321      * Creates a unique file in the specified external storage with the desired name. If the name is
    322      * taken, the new file's name will have '(%d)' to avoid overwriting files.
    323      *
    324      * @param context {@link Context} to query the file name from.
    325      * @param subdirectory One of the directories specified in {@link android.os.Environment}
    326      * @param fileName desired name for the file.
    327      * @param mimeType MIME type of the file to create.
    328      * @return the File object in the storage, or null if an error occurs.
    329      */
    330     public static File getUniqueExternalFile(Context context, String subdirectory, String fileName,
    331             String mimeType) {
    332         File externalStorage = Environment.getExternalStoragePublicDirectory(subdirectory);
    333         // Make sure the storage subdirectory exists
    334         externalStorage.mkdirs();
    335 
    336         File outFile = null;
    337         try {
    338             // Ensure the file has a unique name, as to not override any existing file
    339             outFile = FileUtils.buildUniqueFile(externalStorage, mimeType, fileName);
    340         } catch (FileNotFoundException e) {
    341             // This might also be reached if the number of repeated files gets too high
    342             Log.e(TAG, "Unable to get a unique file name: " + e);
    343             return null;
    344         }
    345         return outFile;
    346     }
    347 
    348     /**
    349      * Returns a file's display name from its {@link android.content.ContentResolver.SCHEME_FILE}
    350      * or {@link android.content.ContentResolver.SCHEME_CONTENT} Uri. The display name of a file
    351      * includes its extension.
    352      *
    353      * @param context Context trying to resolve the file's display name.
    354      * @param uri Uri of the file.
    355      * @return the file's display name, or the uri's string if something fails or the uri isn't in
    356      *            the schemes specified above.
    357      */
    358     static String getFileDisplayNameFromUri(Context context, Uri uri) {
    359         String scheme = uri.getScheme();
    360 
    361         if (ContentResolver.SCHEME_FILE.equals(scheme)) {
    362             return uri.getLastPathSegment();
    363         } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
    364             // We need to query the ContentResolver to get the actual file name as the Uri masks it.
    365             // This means we want the name used for display purposes only.
    366             String[] proj = {
    367                     OpenableColumns.DISPLAY_NAME
    368             };
    369             try (Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null)) {
    370                 if (cursor != null && cursor.getCount() != 0) {
    371                     cursor.moveToFirst();
    372                     return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
    373                 }
    374             }
    375         }
    376 
    377         // This will only happen if the Uri isn't either SCHEME_CONTENT or SCHEME_FILE, so we assume
    378         // it already represents the file's name.
    379         return uri.toString();
    380     }
    381 }
    382