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 */ sortDistinctRanges(Range<T>[] ranges)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>> intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another)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>> binarySearchDistinctRanges(Range<T>[] ranges, T value)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