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
17package android.media;
18
19import android.content.Context;
20import android.content.ContentResolver;
21import android.database.Cursor;
22import android.net.Uri;
23import android.os.Environment;
24import android.os.FileUtils;
25import android.provider.OpenableColumns;
26import android.util.Log;
27import android.util.Pair;
28import android.util.Range;
29import android.util.Rational;
30import android.util.Size;
31
32import java.io.File;
33import java.io.FileNotFoundException;
34import java.util.Arrays;
35import java.util.Comparator;
36import java.util.Vector;
37
38// package private
39class 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