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