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