content: /*
   * Copyright (C) 2014 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 android.media;
  
  import android.content.Context;
  import android.content.ContentResolver;
  import android.database.Cursor;
  import android.net.Uri;
  import android.os.Environment;
  import android.os.FileUtils;
  import android.provider.OpenableColumns;
  import android.util.Log;
  import android.util.Pair;
  import android.util.Range;
  import android.util.Rational;
  import android.util.Size;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.Vector;
  
  // package private
  class Utils {
      private static final String TAG = "Utils";
  
      /**
       * Sorts distinct (non-intersecting) range array in ascending order.
       * @throws java.lang.IllegalArgumentException if ranges are not distinct
       */
      public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) {
          Arrays.sort(ranges, new Comparator<Range<T>>() {
              @Override
              public int compare(Range<T> lhs, Range<T> rhs) {
                  if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
                      return -1;
                  } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
                      return 1;
                  }
                  throw new IllegalArgumentException(
                          "sample rate ranges must be distinct (" + lhs + " and " + rhs + ")");
              }
          });
      }
  
      /**
       * Returns the intersection of two sets of non-intersecting ranges
       * @param one a sorted set of non-intersecting ranges in ascending order
       * @param another another sorted set of non-intersecting ranges in ascending order
       * @return the intersection of the two sets, sorted in ascending order
       */
      public static <T extends Comparable<? super T>>
              Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) {
          int ix = 0;
          Vector<Range<T>> result = new Vector<Range<T>>();
          for (Range<T> range: another) {
              while (ix < one.length &&
                      one[ix].getUpper().compareTo(range.getLower()) < 0) {
                  ++ix;
              }
              while (ix < one.length &&
                      one[ix].getUpper().compareTo(range.getUpper()) < 0) {
                  result.add(range.intersect(one[ix]));
                  ++ix;
              }
              if (ix == one.length) {
                  break;
              }
              if (one[ix].getLower().compareTo(range.getUpper()) <= 0) {
                  result.add(range.intersect(one[ix]));
              }
          }
          return result.toArray(new Range[result.size()]);
      }
  
      /**
       * Returns the index of the range that contains a value in a sorted array of distinct ranges.
       * @param ranges a sorted array of non-intersecting ranges in ascending order
       * @param value the value to search for
       * @return if the value is in one of the ranges, it returns the index of that range.  Otherwise,
       * the return value is {@code (-1-index)} for the {@code index} of the range that is
       * immediately following {@code value}.
       */
      public static <T extends Comparable<? super T>>
              int binarySearchDistinctRanges(Range<T>[] ranges, T value) {
          return Arrays.binarySearch(ranges, Range.create(value, value),
                  new Comparator<Range<T>>() {
                      @Override
                      public int compare(Range<T> lhs, Range<T> rhs) {
                          if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
                              return -1;
                          } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
                              return 1;
                          }
                          return 0;
                      }
                  });
      }
  
      /**
       * Returns greatest common divisor
       */
      static int gcd(int a, int b) {
          if (a == 0 && b == 0) {
              return 1;
          }
          if (b < 0) {
              b = -b;
          }
          if (a < 0) {
              a = -a;
          }
          while (a != 0) {
              int c = b % a;
              b = a;
              a = c;
          }
          return b;
      }
  
      /** Returns the equivalent factored range {@code newrange}, where for every
       * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
       * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
       */
      static Range<Integer>factorRange(Range<Integer> range, int factor) {
          if (factor == 1) {
              return range;
          }
          return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
      }
  
      /** Returns the equivalent factored range {@code newrange}, where for every
       * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
       * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
       */
      static Range<Long>factorRange(Range<Long> range, long factor) {
          if (factor == 1) {
              return range;
          }
          return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
      }
  
      private static Rational scaleRatio(Rational ratio, int num, int den) {
          int common = gcd(num, den);
          num /= common;
          den /= common;
          return new Rational(
                  (int)(ratio.getNumerator() * (double)num),     // saturate to int
                  (int)(ratio.getDenominator() * (double)den));  // saturate to int
      }
  
      static Range<Rational> scaleRange(Range<Rational> range, int num, int den) {
          if (num == den) {
              return range;
          }
          return Range.create(
                  scaleRatio(range.getLower(), num, den),
                  scaleRatio(range.getUpper(), num, den));
      }
  
      static Range<Integer> alignRange(Range<Integer> range, int align) {
          return range.intersect(
                  divUp(range.getLower(), align) * align,
                  (range.getUpper() / align) * align);
      }
  
      static int divUp(int num, int den) {
          return (num + den - 1) / den;
      }
  
      static long divUp(long num, long den) {
          return (num + den - 1) / den;
      }
  
      /**
       * Returns least common multiple
       */
      private static long lcm(int a, int b) {
          if (a == 0 || b == 0) {
              throw new IllegalArgumentException("lce is not defined for zero arguments");
          }
          return (long)a * b / gcd(a, b);
      }
  
      static Range<Integer> intRangeFor(double v) {
          return Range.create((int)v, (int)Math.ceil(v));
      }
  
      static Range<Long> longRangeFor(double v) {
          return Range.create((long)v, (long)Math.ceil(v));
      }
  
      static Size parseSize(Object o, Size fallback) {
          try {
              return Size.parseSize((String) o);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return fallback;
          }
          Log.w(TAG, "could not parse size '" + o + "'");
          return fallback;
      }
  
      static int parseIntSafely(Object o, int fallback) {
          if (o == null) {
              return fallback;
          }
          try {
              String s = (String)o;
              return Integer.parseInt(s);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return fallback;
          }
          Log.w(TAG, "could not parse integer '" + o + "'");
          return fallback;
      }
  
      static Range<Integer> parseIntRange(Object o, Range<Integer> fallback) {
          try {
              String s = (String)o;
              int ix = s.indexOf('-');
              if (ix >= 0) {
                  return Range.create(
                          Integer.parseInt(s.substring(0, ix), 10),
                          Integer.parseInt(s.substring(ix + 1), 10));
              }
              int value = Integer.parseInt(s);
              return Range.create(value, value);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return fallback;
          } catch (IllegalArgumentException e) {
          }
          Log.w(TAG, "could not parse integer range '" + o + "'");
          return fallback;
      }
  
      static Range<Long> parseLongRange(Object o, Range<Long> fallback) {
          try {
              String s = (String)o;
              int ix = s.indexOf('-');
              if (ix >= 0) {
                  return Range.create(
                          Long.parseLong(s.substring(0, ix), 10),
                          Long.parseLong(s.substring(ix + 1), 10));
              }
              long value = Long.parseLong(s);
              return Range.create(value, value);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return fallback;
          } catch (IllegalArgumentException e) {
          }
          Log.w(TAG, "could not parse long range '" + o + "'");
          return fallback;
      }
  
      static Range<Rational> parseRationalRange(Object o, Range<Rational> fallback) {
          try {
              String s = (String)o;
              int ix = s.indexOf('-');
              if (ix >= 0) {
                  return Range.create(
                          Rational.parseRational(s.substring(0, ix)),
                          Rational.parseRational(s.substring(ix + 1)));
              }
              Rational value = Rational.parseRational(s);
              return Range.create(value, value);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return fallback;
          } catch (IllegalArgumentException e) {
          }
          Log.w(TAG, "could not parse rational range '" + o + "'");
          return fallback;
      }
  
      static Pair<Size, Size> parseSizeRange(Object o) {
          try {
              String s = (String)o;
              int ix = s.indexOf('-');
              if (ix >= 0) {
                  return Pair.create(
                          Size.parseSize(s.substring(0, ix)),
                          Size.parseSize(s.substring(ix + 1)));
              }
              Size value = Size.parseSize(s);
              return Pair.create(value, value);
          } catch (ClassCastException e) {
          } catch (NumberFormatException e) {
          } catch (NullPointerException e) {
              return null;
          } catch (IllegalArgumentException e) {
          }
          Log.w(TAG, "could not parse size range '" + o + "'");
          return null;
      }
  
      /**
       * Creates a unique file in the specified external storage with the desired name. If the name is
       * taken, the new file's name will have '(%d)' to avoid overwriting files.
       *
       * @param context {@link Context} to query the file name from.
       * @param subdirectory One of the directories specified in {@link android.os.Environment}
       * @param fileName desired name for the file.
       * @param mimeType MIME type of the file to create.
       * @return the File object in the storage, or null if an error occurs.
       */
      public static File getUniqueExternalFile(Context context, String subdirectory, String fileName,
              String mimeType) {
          File externalStorage = Environment.getExternalStoragePublicDirectory(subdirectory);
          // Make sure the storage subdirectory exists
          externalStorage.mkdirs();
  
          File outFile = null;
          try {
              // Ensure the file has a unique name, as to not override any existing file
              outFile = FileUtils.buildUniqueFile(externalStorage, mimeType, fileName);
          } catch (FileNotFoundException e) {
              // This might also be reached if the number of repeated files gets too high
              Log.e(TAG, "Unable to get a unique file name: " + e);
              return null;
          }
          return outFile;
      }
  
      /**
       * Returns a file's display name from its {@link android.content.ContentResolver.SCHEME_FILE}
       * or {@link android.content.ContentResolver.SCHEME_CONTENT} Uri. The display name of a file
       * includes its extension.
       *
       * @param context Context trying to resolve the file's display name.
       * @param uri Uri of the file.
       * @return the file's display name, or the uri's string if something fails or the uri isn't in
       *            the schemes specified above.
       */
      static String getFileDisplayNameFromUri(Context context, Uri uri) {
          String scheme = uri.getScheme();
  
          if (ContentResolver.SCHEME_FILE.equals(scheme)) {
              return uri.getLastPathSegment();
          } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
              // We need to query the ContentResolver to get the actual file name as the Uri masks it.
              // This means we want the name used for display purposes only.
              String[] proj = {
                      OpenableColumns.DISPLAY_NAME
              };
              try (Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null)) {
                  if (cursor != null && cursor.getCount() != 0) {
                      cursor.moveToFirst();
                      return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                  }
              }
          }
  
          // This will only happen if the Uri isn't either SCHEME_CONTENT or SCHEME_FILE, so we assume
          // it already represents the file's name.
          return uri.toString();
      }
  }
  