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&lt;String, Object&gt;} 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