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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.Binder; 26 import android.os.Environment; 27 import android.os.FileUtils; 28 import android.os.Handler; 29 import android.provider.OpenableColumns; 30 import android.util.Log; 31 import android.util.Pair; 32 import android.util.Range; 33 import android.util.Rational; 34 import android.util.Size; 35 36 import com.android.internal.annotations.GuardedBy; 37 38 import java.io.File; 39 import java.io.FileNotFoundException; 40 import java.util.Arrays; 41 import java.util.Comparator; 42 import java.util.HashMap; 43 import java.util.Objects; 44 import java.util.Vector; 45 import java.util.concurrent.Executor; 46 47 /** 48 * Media Utilities 49 * 50 * This class is hidden but public to allow CTS testing and verification 51 * of the static methods and classes. 52 * 53 * @hide 54 */ 55 public class Utils { 56 private static final String TAG = "Utils"; 57 58 /** 59 * Sorts distinct (non-intersecting) range array in ascending order. 60 * @throws java.lang.IllegalArgumentException if ranges are not distinct 61 */ sortDistinctRanges(Range<T>[] ranges)62 public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) { 63 Arrays.sort(ranges, new Comparator<Range<T>>() { 64 @Override 65 public int compare(Range<T> lhs, Range<T> rhs) { 66 if (lhs.getUpper().compareTo(rhs.getLower()) < 0) { 67 return -1; 68 } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) { 69 return 1; 70 } 71 throw new IllegalArgumentException( 72 "sample rate ranges must be distinct (" + lhs + " and " + rhs + ")"); 73 } 74 }); 75 } 76 77 /** 78 * Returns the intersection of two sets of non-intersecting ranges 79 * @param one a sorted set of non-intersecting ranges in ascending order 80 * @param another another sorted set of non-intersecting ranges in ascending order 81 * @return the intersection of the two sets, sorted in ascending order 82 */ 83 public static <T extends Comparable<? super T>> intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another)84 Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) { 85 int ix = 0; 86 Vector<Range<T>> result = new Vector<Range<T>>(); 87 for (Range<T> range: another) { 88 while (ix < one.length && 89 one[ix].getUpper().compareTo(range.getLower()) < 0) { 90 ++ix; 91 } 92 while (ix < one.length && 93 one[ix].getUpper().compareTo(range.getUpper()) < 0) { 94 result.add(range.intersect(one[ix])); 95 ++ix; 96 } 97 if (ix == one.length) { 98 break; 99 } 100 if (one[ix].getLower().compareTo(range.getUpper()) <= 0) { 101 result.add(range.intersect(one[ix])); 102 } 103 } 104 return result.toArray(new Range[result.size()]); 105 } 106 107 /** 108 * Returns the index of the range that contains a value in a sorted array of distinct ranges. 109 * @param ranges a sorted array of non-intersecting ranges in ascending order 110 * @param value the value to search for 111 * @return if the value is in one of the ranges, it returns the index of that range. Otherwise, 112 * the return value is {@code (-1-index)} for the {@code index} of the range that is 113 * immediately following {@code value}. 114 */ 115 public static <T extends Comparable<? super T>> binarySearchDistinctRanges(Range<T>[] ranges, T value)116 int binarySearchDistinctRanges(Range<T>[] ranges, T value) { 117 return Arrays.binarySearch(ranges, Range.create(value, value), 118 new Comparator<Range<T>>() { 119 @Override 120 public int compare(Range<T> lhs, Range<T> rhs) { 121 if (lhs.getUpper().compareTo(rhs.getLower()) < 0) { 122 return -1; 123 } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) { 124 return 1; 125 } 126 return 0; 127 } 128 }); 129 } 130 131 /** 132 * Returns greatest common divisor 133 */ 134 static int gcd(int a, int b) { 135 if (a == 0 && b == 0) { 136 return 1; 137 } 138 if (b < 0) { 139 b = -b; 140 } 141 if (a < 0) { 142 a = -a; 143 } 144 while (a != 0) { 145 int c = b % a; 146 b = a; 147 a = c; 148 } 149 return b; 150 } 151 152 /** Returns the equivalent factored range {@code newrange}, where for every 153 * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)}, 154 * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}. 155 */ 156 static Range<Integer>factorRange(Range<Integer> range, int factor) { 157 if (factor == 1) { 158 return range; 159 } 160 return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor); 161 } 162 163 /** Returns the equivalent factored range {@code newrange}, where for every 164 * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)}, 165 * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}. 166 */ 167 static Range<Long>factorRange(Range<Long> range, long factor) { 168 if (factor == 1) { 169 return range; 170 } 171 return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor); 172 } 173 174 private static Rational scaleRatio(Rational ratio, int num, int den) { 175 int common = gcd(num, den); 176 num /= common; 177 den /= common; 178 return new Rational( 179 (int)(ratio.getNumerator() * (double)num), // saturate to int 180 (int)(ratio.getDenominator() * (double)den)); // saturate to int 181 } 182 183 static Range<Rational> scaleRange(Range<Rational> range, int num, int den) { 184 if (num == den) { 185 return range; 186 } 187 return Range.create( 188 scaleRatio(range.getLower(), num, den), 189 scaleRatio(range.getUpper(), num, den)); 190 } 191 192 static Range<Integer> alignRange(Range<Integer> range, int align) { 193 return range.intersect( 194 divUp(range.getLower(), align) * align, 195 (range.getUpper() / align) * align); 196 } 197 198 static int divUp(int num, int den) { 199 return (num + den - 1) / den; 200 } 201 202 static long divUp(long num, long den) { 203 return (num + den - 1) / den; 204 } 205 206 /** 207 * Returns least common multiple 208 */ 209 private static long lcm(int a, int b) { 210 if (a == 0 || b == 0) { 211 throw new IllegalArgumentException("lce is not defined for zero arguments"); 212 } 213 return (long)a * b / gcd(a, b); 214 } 215 216 static Range<Integer> intRangeFor(double v) { 217 return Range.create((int)v, (int)Math.ceil(v)); 218 } 219 220 static Range<Long> longRangeFor(double v) { 221 return Range.create((long)v, (long)Math.ceil(v)); 222 } 223 224 static Size parseSize(Object o, Size fallback) { 225 if (o == null) { 226 return fallback; 227 } 228 try { 229 return Size.parseSize((String) o); 230 } catch (ClassCastException e) { 231 } catch (NumberFormatException e) { 232 } 233 Log.w(TAG, "could not parse size '" + o + "'"); 234 return fallback; 235 } 236 237 static int parseIntSafely(Object o, int fallback) { 238 if (o == null) { 239 return fallback; 240 } 241 try { 242 String s = (String)o; 243 return Integer.parseInt(s); 244 } catch (ClassCastException e) { 245 } catch (NumberFormatException e) { 246 } 247 Log.w(TAG, "could not parse integer '" + o + "'"); 248 return fallback; 249 } 250 251 static Range<Integer> parseIntRange(Object o, Range<Integer> fallback) { 252 if (o == null) { 253 return fallback; 254 } 255 try { 256 String s = (String)o; 257 int ix = s.indexOf('-'); 258 if (ix >= 0) { 259 return Range.create( 260 Integer.parseInt(s.substring(0, ix), 10), 261 Integer.parseInt(s.substring(ix + 1), 10)); 262 } 263 int value = Integer.parseInt(s); 264 return Range.create(value, value); 265 } catch (ClassCastException e) { 266 } catch (NumberFormatException e) { 267 } catch (IllegalArgumentException e) { 268 } 269 Log.w(TAG, "could not parse integer range '" + o + "'"); 270 return fallback; 271 } 272 273 static Range<Long> parseLongRange(Object o, Range<Long> fallback) { 274 if (o == null) { 275 return fallback; 276 } 277 try { 278 String s = (String)o; 279 int ix = s.indexOf('-'); 280 if (ix >= 0) { 281 return Range.create( 282 Long.parseLong(s.substring(0, ix), 10), 283 Long.parseLong(s.substring(ix + 1), 10)); 284 } 285 long value = Long.parseLong(s); 286 return Range.create(value, value); 287 } catch (ClassCastException e) { 288 } catch (NumberFormatException e) { 289 } catch (IllegalArgumentException e) { 290 } 291 Log.w(TAG, "could not parse long range '" + o + "'"); 292 return fallback; 293 } 294 295 static Range<Rational> parseRationalRange(Object o, Range<Rational> fallback) { 296 if (o == null) { 297 return fallback; 298 } 299 try { 300 String s = (String)o; 301 int ix = s.indexOf('-'); 302 if (ix >= 0) { 303 return Range.create( 304 Rational.parseRational(s.substring(0, ix)), 305 Rational.parseRational(s.substring(ix + 1))); 306 } 307 Rational value = Rational.parseRational(s); 308 return Range.create(value, value); 309 } catch (ClassCastException e) { 310 } catch (NumberFormatException e) { 311 } catch (IllegalArgumentException e) { 312 } 313 Log.w(TAG, "could not parse rational range '" + o + "'"); 314 return fallback; 315 } 316 317 static Pair<Size, Size> parseSizeRange(Object o) { 318 if (o == null) { 319 return null; 320 } 321 try { 322 String s = (String)o; 323 int ix = s.indexOf('-'); 324 if (ix >= 0) { 325 return Pair.create( 326 Size.parseSize(s.substring(0, ix)), 327 Size.parseSize(s.substring(ix + 1))); 328 } 329 Size value = Size.parseSize(s); 330 return Pair.create(value, value); 331 } catch (ClassCastException e) { 332 } catch (NumberFormatException e) { 333 } catch (IllegalArgumentException e) { 334 } 335 Log.w(TAG, "could not parse size range '" + o + "'"); 336 return null; 337 } 338 339 /** 340 * Creates a unique file in the specified external storage with the desired name. If the name is 341 * taken, the new file's name will have '(%d)' to avoid overwriting files. 342 * 343 * @param context {@link Context} to query the file name from. 344 * @param subdirectory One of the directories specified in {@link android.os.Environment} 345 * @param fileName desired name for the file. 346 * @param mimeType MIME type of the file to create. 347 * @return the File object in the storage, or null if an error occurs. 348 */ 349 public static File getUniqueExternalFile(Context context, String subdirectory, String fileName, 350 String mimeType) { 351 File externalStorage = Environment.getExternalStoragePublicDirectory(subdirectory); 352 // Make sure the storage subdirectory exists 353 externalStorage.mkdirs(); 354 355 File outFile = null; 356 try { 357 // Ensure the file has a unique name, as to not override any existing file 358 outFile = FileUtils.buildUniqueFile(externalStorage, mimeType, fileName); 359 } catch (FileNotFoundException e) { 360 // This might also be reached if the number of repeated files gets too high 361 Log.e(TAG, "Unable to get a unique file name: " + e); 362 return null; 363 } 364 return outFile; 365 } 366 367 /** 368 * Returns a file's display name from its {@link android.content.ContentResolver.SCHEME_FILE} 369 * or {@link android.content.ContentResolver.SCHEME_CONTENT} Uri. The display name of a file 370 * includes its extension. 371 * 372 * @param context Context trying to resolve the file's display name. 373 * @param uri Uri of the file. 374 * @return the file's display name, or the uri's string if something fails or the uri isn't in 375 * the schemes specified above. 376 */ 377 static String getFileDisplayNameFromUri(Context context, Uri uri) { 378 String scheme = uri.getScheme(); 379 380 if (ContentResolver.SCHEME_FILE.equals(scheme)) { 381 return uri.getLastPathSegment(); 382 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { 383 // We need to query the ContentResolver to get the actual file name as the Uri masks it. 384 // This means we want the name used for display purposes only. 385 String[] proj = { 386 OpenableColumns.DISPLAY_NAME 387 }; 388 try (Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null)) { 389 if (cursor != null && cursor.getCount() != 0) { 390 cursor.moveToFirst(); 391 return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 392 } 393 } 394 } 395 396 // This will only happen if the Uri isn't either SCHEME_CONTENT or SCHEME_FILE, so we assume 397 // it already represents the file's name. 398 return uri.toString(); 399 } 400 401 /** 402 * {@code ListenerList} is a helper class that delivers events to listeners. 403 * 404 * It is written to isolate the <strong>mechanics</strong> of event delivery from the 405 * <strong>details</strong> of those events. 406 * 407 * The {@code ListenerList} is parameterized on the generic type {@code V} 408 * of the object delivered by {@code notify()}. 409 * This gives compile time type safety over run-time casting of a general {@code Object}, 410 * much like {@code HashMap<String, Object>} does not give type safety of the 411 * stored {@code Object} value and may allow 412 * permissive storage of {@code Object}s that are not expected by users of the 413 * {@code HashMap}, later resulting in run-time cast exceptions that 414 * could have been caught by replacing 415 * {@code Object} with a more precise type to enforce a compile time contract. 416 * 417 * The {@code ListenerList} is implemented as a single method callback 418 * - or a "listener" according to Android style guidelines. 419 * 420 * The {@code ListenerList} can be trivially extended by a suitable lambda to implement 421 * a <strong> multiple method abstract class</strong> "callback", 422 * in which the generic type {@code V} could be an {@code Object} 423 * to encapsulate the details of the parameters of each callback method, and 424 * {@code instanceof} could be used to disambiguate which callback method to use. 425 * A {@link Bundle} could alternatively encapsulate those generic parameters, 426 * perhaps more conveniently. 427 * Again, this is a detail of the event, not the mechanics of the event delivery, 428 * which this class is concerned with. 429 * 430 * For details on how to use this class to implement a <strong>single listener</strong> 431 * {@code ListenerList}, see notes on {@link #add}. 432 * 433 * For details on how to optimize this class to implement 434 * a listener based on {@link Handler}s 435 * instead of {@link Executor}s, see{@link #ListenerList(boolean, boolean, boolean)}. 436 * 437 * This is a TestApi for CTS Unit Testing, not exposed for general Application use. 438 * @hide 439 * 440 * @param <V> The class of the object returned to the listener. 441 */ 442 public static class ListenerList<V> { 443 /** 444 * The Listener interface for callback. 445 * 446 * @param <V> The class of the object returned to the listener 447 */ 448 public interface Listener<V> { 449 /** 450 * General event listener interface which is managed by the {@code ListenerList}. 451 * 452 * @param eventCode is an integer representing the event type. This is an 453 * implementation defined parameter. 454 * @param info is the object returned to the listener. It is expected 455 * that the listener makes a private copy of the {@code info} object before 456 * modification, as it is the same instance passed to all listeners. 457 * This is an implementation defined parameter that may be null. 458 */ 459 void onEvent(int eventCode, @Nullable V info); 460 } 461 462 private interface ListenerWithCancellation<V> extends Listener<V> { 463 void cancel(); 464 } 465 466 /** 467 * Default {@code ListenerList} constructor for {@link Executor} based implementation. 468 * 469 * TODO: consider adding a "name" for debugging if this is used for 470 * multiple listener implementations. 471 */ 472 public ListenerList() { 473 this(true /* restrictSingleCallerOnEvent */, 474 true /* clearCallingIdentity */, 475 false /* forceRemoveConsistency*/); 476 } 477 478 /** 479 * Specific {@code ListenerList} constructor for customization. 480 * 481 * See the internal notes for the corresponding private variables on the behavior of 482 * the boolean configuration parameters. 483 * 484 * {@code ListenerList(true, true, false)} is the default and used for 485 * {@link Executor} based notification implementation. 486 * 487 * {@code ListenerList(false, false, false)} may be used for as an optimization 488 * where the {@link Executor} is actually a {@link Handler} post. 489 * 490 * @param restrictSingleCallerOnEvent whether the listener will only be called by 491 * a single thread at a time. 492 * @param clearCallingIdentity whether the binder calling identity on 493 * {@link #notify} is cleared. 494 * @param forceRemoveConsistency whether remove() guarantees no more callbacks to 495 * the listener immediately after the call. 496 */ 497 public ListenerList(boolean restrictSingleCallerOnEvent, 498 boolean clearCallingIdentity, 499 boolean forceRemoveConsistency) { 500 mRestrictSingleCallerOnEvent = restrictSingleCallerOnEvent; 501 mClearCallingIdentity = clearCallingIdentity; 502 mForceRemoveConsistency = forceRemoveConsistency; 503 } 504 505 /** 506 * Adds a listener to the {@code ListenerList}. 507 * 508 * The {@code ListenerList} is most often used to hold {@code multiple} listeners. 509 * 510 * Per Android style, for a single method Listener interface, the add and remove 511 * would be wrapped in "addSomeListener" or "removeSomeListener"; 512 * or a lambda implemented abstract class callback, wrapped in 513 * "registerSomeCallback" or "unregisterSomeCallback". 514 * 515 * We allow a general {@code key} to be attached to add and remove that specific 516 * listener. It could be the {@code listener} object itself. 517 * 518 * For some implementations, there may be only a {@code single} listener permitted. 519 * 520 * Per Android style, for a single listener {@code ListenerList}, 521 * the naming of the wrapping call to {@link #add} would be 522 * "setSomeListener" with a nullable listener, which would be null 523 * to call {@link #remove}. 524 * 525 * In that case, the caller may use this {@link #add} with a single constant object for 526 * the {@code key} to enforce only one Listener in the {@code ListenerList}. 527 * Likewise on remove it would use that 528 * same single constant object to remove the listener. 529 * That {@code key} object could be the {@code ListenerList} itself for convenience. 530 * 531 * @param key is a unique object that is used to identify the listener 532 * when {@code remove()} is called. It can be the listener itself. 533 * @param executor is used to execute the callback. 534 * @param listener is the {@link AudioTrack.ListenerList.Listener} 535 * interface to be called upon {@link notify}. 536 */ 537 public void add( 538 @NonNull Object key, @NonNull Executor executor, @NonNull Listener<V> listener) { 539 Objects.requireNonNull(key); 540 Objects.requireNonNull(executor); 541 Objects.requireNonNull(listener); 542 543 // construct wrapper outside of lock. 544 ListenerWithCancellation<V> listenerWithCancellation = 545 new ListenerWithCancellation<V>() { 546 private final Object mLock = new Object(); // our lock is per Listener. 547 private volatile boolean mCancelled = false; // atomic rmw not needed. 548 549 @Override 550 public void onEvent(int eventCode, V info) { 551 executor.execute(() -> { 552 // Note deep execution of locking and cancellation 553 // so this works after posting on different threads. 554 if (mRestrictSingleCallerOnEvent || mForceRemoveConsistency) { 555 synchronized (mLock) { 556 if (mCancelled) return; 557 listener.onEvent(eventCode, info); 558 } 559 } else { 560 if (mCancelled) return; 561 listener.onEvent(eventCode, info); 562 } 563 }); 564 } 565 566 @Override 567 public void cancel() { 568 if (mForceRemoveConsistency) { 569 synchronized (mLock) { 570 mCancelled = true; 571 } 572 } else { 573 mCancelled = true; 574 } 575 } 576 }; 577 578 synchronized (mListeners) { 579 // TODO: consider an option to check the existence of the key 580 // and throw an ISE if it exists. 581 mListeners.put(key, listenerWithCancellation); // replaces old value 582 } 583 } 584 585 /** 586 * Removes a listener from the {@code ListenerList}. 587 * 588 * @param key the unique object associated with the listener during {@link #add}. 589 */ 590 public void remove(@NonNull Object key) { 591 Objects.requireNonNull(key); 592 593 ListenerWithCancellation<V> listener; 594 synchronized (mListeners) { 595 listener = mListeners.get(key); 596 if (listener == null) { // TODO: consider an option to throw ISE Here. 597 return; 598 } 599 mListeners.remove(key); // removes if exist 600 } 601 602 // cancel outside of lock 603 listener.cancel(); 604 } 605 606 /** 607 * Notifies all listeners on the List. 608 * 609 * @param eventCode to pass to all listeners. 610 * @param info to pass to all listeners. This is an implemention defined parameter 611 * which may be {@code null}. 612 */ 613 public void notify(int eventCode, @Nullable V info) { 614 Object[] listeners; // note we can't cast an object array to a listener array 615 synchronized (mListeners) { 616 if (mListeners.size() == 0) { 617 return; 618 } 619 listeners = mListeners.values().toArray(); // guarantees a copy. 620 } 621 622 // notify outside of lock. 623 final Long identity = mClearCallingIdentity ? Binder.clearCallingIdentity() : null; 624 try { 625 for (Object object : listeners) { 626 final ListenerWithCancellation<V> listener = 627 (ListenerWithCancellation<V>) object; 628 listener.onEvent(eventCode, info); 629 } 630 } finally { 631 if (identity != null) { 632 Binder.restoreCallingIdentity(identity); 633 } 634 } 635 } 636 637 @GuardedBy("mListeners") 638 private HashMap<Object, ListenerWithCancellation<V>> mListeners = new HashMap<>(); 639 640 // An Executor may run in multiple threads, whereas a Handler runs on a single Looper. 641 // Should be true for an Executor to avoid concurrent calling into the same listener, 642 // can be false for a Handler as a Handler forces single thread caller for each listener. 643 private final boolean mRestrictSingleCallerOnEvent; // default true 644 645 // An Executor may run in the calling thread, whereas a handler will post to the Looper. 646 // Should be true for an Executor to prevent privilege escalation, 647 // can be false for a Handler as its thread is not the calling binder thread. 648 private final boolean mClearCallingIdentity; // default true 649 650 // Guaranteeing no listener callbacks after removal requires taking the same lock for the 651 // remove as the callback; this is a reversal in calling layers, 652 // hence the risk of lock order inversion is great. 653 // 654 // Set to true only if you can control the caller's listen and remove methods and/or 655 // the threading of the Executor used for each listener. 656 // When set to false, we do not lock, but still do a best effort to cancel messages 657 // on the fly. 658 private final boolean mForceRemoveConsistency; // default false 659 } 660 661 /** 662 * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app 663 * Must match the implementation of BluetoothUtils.toAnonymizedAddress() 664 * @param address MAC address to be anonymized 665 * @return anonymized MAC address 666 */ 667 public static @Nullable String anonymizeBluetoothAddress(@Nullable String address) { 668 if (address == null) { 669 return null; 670 } 671 if (address.length() != "AA:BB:CC:DD:EE:FF".length()) { 672 return address; 673 } 674 return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length()); 675 } 676 677 /** 678 * Convert a Bluetooth MAC address to an anonymized one if the internal device type corresponds 679 * to a Bluetooth. 680 * @param deviceType the internal type of the audio device 681 * @param address MAC address to be anonymized 682 * @return anonymized MAC address 683 */ 684 public static @Nullable String anonymizeBluetoothAddress( 685 int deviceType, @Nullable String address) { 686 if (!AudioSystem.isBluetoothDevice(deviceType)) { 687 return address; 688 } 689 return anonymizeBluetoothAddress(address); 690 } 691 } 692