• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 com.android.server.audio;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.media.AudioAttributes;
22 import android.media.AudioDeviceAttributes;
23 import android.media.AudioSystem;
24 import android.media.audiopolicy.AudioMix;
25 import android.os.SystemClock;
26 import android.util.Log;
27 import android.util.Pair;
28 
29 import com.android.internal.annotations.GuardedBy;
30 
31 import java.io.PrintWriter;
32 import java.time.Instant;
33 import java.time.ZoneId;
34 import java.time.format.DateTimeFormatter;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.concurrent.ConcurrentHashMap;
40 
41 /**
42  * Provides an adapter to access functionality of the android.media.AudioSystem class for device
43  * related functionality.
44  * Use the "real" AudioSystem through the default adapter.
45  * Use the "always ok" adapter to avoid dealing with the APM behaviors during a test.
46  */
47 public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
48         AudioSystem.VolumeRangeInitRequestCallback {
49 
50     private static final String TAG = "AudioSystemAdapter";
51 
52     // initialized in factory getDefaultAdapter()
53     private static AudioSystemAdapter sSingletonDefaultAdapter;
54 
55     /**
56      * should be false by default unless enabling measurements of method call counts and time spent
57      * in measured methods
58      */
59     private static final boolean ENABLE_GETDEVICES_STATS = false;
60     private static final int NB_MEASUREMENTS = 1;
61     private static final int METHOD_GETDEVICESFORATTRIBUTES = 0;
62     private long[] mMethodTimeNs;
63     private int[] mMethodCallCounter;
64     private String[] mMethodNames = {"getDevicesForAttributes"};
65 
66     private static final boolean USE_CACHE_FOR_GETDEVICES = true;
67     private static final Object sDeviceCacheLock = new Object();
68     @GuardedBy("sDeviceCacheLock")
69     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
70             mDevicesForAttrCache;
71     @GuardedBy("sDeviceCacheLock")
72     private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
73     private int[] mMethodCacheHit;
74     private static final Object sRoutingListenerLock = new Object();
75     @GuardedBy("sRoutingListenerLock")
76     private static @Nullable OnRoutingUpdatedListener sRoutingListener;
77     private static final Object sVolRangeInitReqListenerLock = new Object();
78     @GuardedBy("sVolRangeInitReqListenerLock")
79     private static @Nullable OnVolRangeInitRequestListener sVolRangeInitReqListener;
80 
81     /**
82      * should be false except when trying to debug caching errors. When true, the value retrieved
83      * from the cache will be compared against the real queried value, which defeats the purpose of
84      * the cache in terms of performance.
85      */
86     private static final boolean DEBUG_CACHE = false;
87 
88     /**
89      * Implementation of AudioSystem.RoutingUpdateCallback
90      */
91     @Override
onRoutingUpdated()92     public void onRoutingUpdated() {
93         if (DEBUG_CACHE) {
94             Log.d(TAG, "---- onRoutingUpdated (from native) ----------");
95         }
96         invalidateRoutingCache();
97         final OnRoutingUpdatedListener listener;
98         synchronized (sRoutingListenerLock) {
99             listener = sRoutingListener;
100         }
101         if (listener != null) {
102             listener.onRoutingUpdatedFromNative();
103         }
104     }
105 
106     interface OnRoutingUpdatedListener {
onRoutingUpdatedFromNative()107         void onRoutingUpdatedFromNative();
108     }
109 
setRoutingListener(@ullable OnRoutingUpdatedListener listener)110     static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) {
111         synchronized (sRoutingListenerLock) {
112             sRoutingListener = listener;
113         }
114     }
115 
clearRoutingCache()116     public void clearRoutingCache() {
117         if (DEBUG_CACHE) {
118             Log.d(TAG, "---- routing cache clear (from java) ----------");
119         }
120         invalidateRoutingCache();
121     }
122 
123     /**
124      * Implementation of AudioSystem.VolumeRangeInitRequestCallback
125      */
126     @Override
onVolumeRangeInitializationRequested()127     public void onVolumeRangeInitializationRequested() {
128         final OnVolRangeInitRequestListener listener;
129         synchronized (sVolRangeInitReqListenerLock) {
130             listener = sVolRangeInitReqListener;
131         }
132         if (listener != null) {
133             listener.onVolumeRangeInitRequestFromNative();
134         }
135     }
136 
137     interface OnVolRangeInitRequestListener {
onVolumeRangeInitRequestFromNative()138         void onVolumeRangeInitRequestFromNative();
139     }
140 
setVolRangeInitReqListener(@ullable OnVolRangeInitRequestListener listener)141     static void setVolRangeInitReqListener(@Nullable OnVolRangeInitRequestListener listener) {
142         synchronized (sVolRangeInitReqListenerLock) {
143             sVolRangeInitReqListener = listener;
144         }
145     }
146 
147     /**
148      * Create a wrapper around the {@link AudioSystem} static methods, all functions are directly
149      * forwarded to the AudioSystem class.
150      * @return an adapter around AudioSystem
151      */
getDefaultAdapter()152     static final synchronized @NonNull AudioSystemAdapter getDefaultAdapter() {
153         if (sSingletonDefaultAdapter == null) {
154             sSingletonDefaultAdapter = new AudioSystemAdapter();
155             AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
156             AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter);
157             if (USE_CACHE_FOR_GETDEVICES) {
158                 synchronized (sDeviceCacheLock) {
159                     sSingletonDefaultAdapter.mDevicesForAttrCache =
160                             new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
161                     sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
162                 }
163             }
164             if (ENABLE_GETDEVICES_STATS) {
165                 sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS];
166                 sSingletonDefaultAdapter.mMethodTimeNs = new long[NB_MEASUREMENTS];
167             }
168         }
169         return sSingletonDefaultAdapter;
170     }
171 
invalidateRoutingCache()172     private void invalidateRoutingCache() {
173         if (DEBUG_CACHE) {
174             Log.d(TAG, "---- clearing cache ----------");
175         }
176         synchronized (sDeviceCacheLock) {
177             if (mDevicesForAttrCache != null) {
178                 mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
179                 mDevicesForAttrCache.clear();
180             }
181         }
182     }
183 
184     /**
185      * Same as {@link AudioSystem#getDevicesForAttributes(AudioAttributes)}
186      * @param attributes the attributes for which the routing is queried
187      * @return the devices that the stream with the given attributes would be routed to
188      */
getDevicesForAttributes( @onNull AudioAttributes attributes, boolean forVolume)189     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
190             @NonNull AudioAttributes attributes, boolean forVolume) {
191         if (!ENABLE_GETDEVICES_STATS) {
192             return getDevicesForAttributesImpl(attributes, forVolume);
193         }
194         mMethodCallCounter[METHOD_GETDEVICESFORATTRIBUTES]++;
195         final long startTime = SystemClock.uptimeNanos();
196         final ArrayList<AudioDeviceAttributes> res = getDevicesForAttributesImpl(
197                 attributes, forVolume);
198         mMethodTimeNs[METHOD_GETDEVICESFORATTRIBUTES] += SystemClock.uptimeNanos() - startTime;
199         return res;
200     }
201 
getDevicesForAttributesImpl( @onNull AudioAttributes attributes, boolean forVolume)202     private @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesImpl(
203             @NonNull AudioAttributes attributes, boolean forVolume) {
204         if (USE_CACHE_FOR_GETDEVICES) {
205             ArrayList<AudioDeviceAttributes> res;
206             final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
207             synchronized (sDeviceCacheLock) {
208                 res = mDevicesForAttrCache.get(key);
209                 if (res == null) {
210                     // result from AudioSystem guaranteed non-null, but could be invalid
211                     // if there is a failure to talk to APM
212                     res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
213                     if (res.size() > 1 && res.get(0) != null
214                             && res.get(0).getInternalType() == AudioSystem.DEVICE_NONE) {
215                         Log.e(TAG, "unable to get devices for " + attributes);
216                         // return now, do not put invalid value in cache
217                         return res;
218                     }
219                     mDevicesForAttrCache.put(key, res);
220                     if (DEBUG_CACHE) {
221                         Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
222                                 + attrDeviceToDebugString(attributes, res));
223                     }
224                     return res;
225                 }
226                 // cache hit
227                 mMethodCacheHit[METHOD_GETDEVICESFORATTRIBUTES]++;
228                 if (DEBUG_CACHE) {
229                     final ArrayList<AudioDeviceAttributes> real =
230                             AudioSystem.getDevicesForAttributes(attributes, forVolume);
231                     if (res.equals(real)) {
232                         Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
233                                 + attrDeviceToDebugString(attributes, res) + " CACHE");
234                     } else {
235                         Log.e(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
236                                 + attrDeviceToDebugString(attributes, res)
237                                 + " CACHE ERROR real:" + attrDeviceToDebugString(attributes, real));
238                     }
239                 }
240             }
241             return res;
242         }
243         // not using cache
244         return AudioSystem.getDevicesForAttributes(attributes, forVolume);
245     }
246 
attrDeviceToDebugString(@onNull AudioAttributes attr, @NonNull List<AudioDeviceAttributes> devices)247     private static String attrDeviceToDebugString(@NonNull AudioAttributes attr,
248             @NonNull List<AudioDeviceAttributes> devices) {
249         return " attrUsage=" + attr.getSystemUsage() + " "
250                 + AudioSystem.deviceSetToString(AudioSystem.generateAudioDeviceTypesSet(devices));
251     }
252 
253     /**
254      * Same as {@link AudioSystem#setDeviceConnectionState(AudioDeviceAttributes, int, int)}
255      * @param attributes
256      * @param state
257      * @param codecFormat
258      * @return
259      */
setDeviceConnectionState(AudioDeviceAttributes attributes, int state, int codecFormat)260     public int setDeviceConnectionState(AudioDeviceAttributes attributes, int state,
261             int codecFormat) {
262         invalidateRoutingCache();
263         return AudioSystem.setDeviceConnectionState(attributes, state, codecFormat);
264     }
265 
266     /**
267      * Same as {@link AudioSystem#getDeviceConnectionState(int, String)}
268      * @param device
269      * @param deviceAddress
270      * @return
271      */
getDeviceConnectionState(int device, String deviceAddress)272     public int getDeviceConnectionState(int device, String deviceAddress) {
273         return AudioSystem.getDeviceConnectionState(device, deviceAddress);
274     }
275 
276     /**
277      * Same as {@link AudioSystem#handleDeviceConfigChange(int, String, String, int)}
278      * @param device
279      * @param deviceAddress
280      * @param deviceName
281      * @param codecFormat
282      * @return
283      */
handleDeviceConfigChange(int device, String deviceAddress, String deviceName, int codecFormat)284     public int handleDeviceConfigChange(int device, String deviceAddress,
285                                                String deviceName, int codecFormat) {
286         invalidateRoutingCache();
287         return AudioSystem.handleDeviceConfigChange(device, deviceAddress, deviceName,
288                 codecFormat);
289     }
290 
291     /**
292      * Same as {@link AudioSystem#setDevicesRoleForStrategy(int, int, List)}
293      * @param strategy
294      * @param role
295      * @param devices
296      * @return
297      */
setDevicesRoleForStrategy(int strategy, int role, @NonNull List<AudioDeviceAttributes> devices)298     public int setDevicesRoleForStrategy(int strategy, int role,
299                                          @NonNull List<AudioDeviceAttributes> devices) {
300         invalidateRoutingCache();
301         return AudioSystem.setDevicesRoleForStrategy(strategy, role, devices);
302     }
303 
304     /**
305      * Same as {@link AudioSystem#removeDevicesRoleForStrategy(int, int)}
306      * @param strategy
307      * @param role
308      * @return
309      */
removeDevicesRoleForStrategy(int strategy, int role)310     public int removeDevicesRoleForStrategy(int strategy, int role) {
311         invalidateRoutingCache();
312         return AudioSystem.removeDevicesRoleForStrategy(strategy, role);
313     }
314 
315     /**
316      * Same as (@link AudioSystem#setDevicesRoleForCapturePreset(int, List))
317      * @param capturePreset
318      * @param role
319      * @param devices
320      * @return
321      */
setDevicesRoleForCapturePreset(int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)322     public int setDevicesRoleForCapturePreset(int capturePreset, int role,
323                                               @NonNull List<AudioDeviceAttributes> devices) {
324         invalidateRoutingCache();
325         return AudioSystem.setDevicesRoleForCapturePreset(capturePreset, role, devices);
326     }
327 
328     /**
329      * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, int[], String[])}
330      * @param capturePreset
331      * @param role
332      * @param devicesToRemove
333      * @return
334      */
removeDevicesRoleForCapturePreset( int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove)335     public int removeDevicesRoleForCapturePreset(
336             int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove) {
337         invalidateRoutingCache();
338         return AudioSystem.removeDevicesRoleForCapturePreset(capturePreset, role, devicesToRemove);
339     }
340 
341     /**
342      * Same as {@link AudioSystem#}
343      * @param capturePreset
344      * @param role
345      * @return
346      */
clearDevicesRoleForCapturePreset(int capturePreset, int role)347     public int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
348         invalidateRoutingCache();
349         return AudioSystem.clearDevicesRoleForCapturePreset(capturePreset, role);
350     }
351 
352     /**
353      * Same as {@link AudioSystem#setParameters(String)}
354      * @param keyValuePairs
355      * @return
356      */
setParameters(String keyValuePairs)357     public int setParameters(String keyValuePairs) {
358         invalidateRoutingCache();
359         return AudioSystem.setParameters(keyValuePairs);
360     }
361 
362     /**
363      * Same as {@link AudioSystem#isMicrophoneMuted()}}
364      * Checks whether the microphone mute is on or off.
365      * @return true if microphone is muted, false if it's not
366      */
isMicrophoneMuted()367     public boolean isMicrophoneMuted() {
368         return AudioSystem.isMicrophoneMuted();
369     }
370 
371     /**
372      * Same as {@link AudioSystem#muteMicrophone(boolean)}
373      * Sets the microphone mute on or off.
374      *
375      * @param on set <var>true</var> to mute the microphone;
376      *           <var>false</var> to turn mute off
377      * @return command completion status see AUDIO_STATUS_OK, see AUDIO_STATUS_ERROR
378      */
muteMicrophone(boolean on)379     public int muteMicrophone(boolean on) {
380         return AudioSystem.muteMicrophone(on);
381     }
382 
383     /**
384      * Same as {@link AudioSystem#setCurrentImeUid(int)}
385      * Communicate UID of current InputMethodService to audio policy service.
386      */
setCurrentImeUid(int uid)387     public int setCurrentImeUid(int uid) {
388         return AudioSystem.setCurrentImeUid(uid);
389     }
390 
391     /**
392      * Same as {@link AudioSystem#isStreamActive(int, int)}
393      */
isStreamActive(int stream, int inPastMs)394     public boolean isStreamActive(int stream, int inPastMs) {
395         return AudioSystem.isStreamActive(stream, inPastMs);
396     }
397 
398     /**
399      * Same as {@link AudioSystem#isStreamActiveRemotely(int, int)}
400      * @param stream
401      * @param inPastMs
402      * @return
403      */
isStreamActiveRemotely(int stream, int inPastMs)404     public boolean isStreamActiveRemotely(int stream, int inPastMs) {
405         return AudioSystem.isStreamActiveRemotely(stream, inPastMs);
406     }
407 
408     /**
409      * Same as {@link AudioSystem#setStreamVolumeIndexAS(int, int, int)}
410      * @param stream
411      * @param index
412      * @param device
413      * @return
414      */
setStreamVolumeIndexAS(int stream, int index, int device)415     public int setStreamVolumeIndexAS(int stream, int index, int device) {
416         return AudioSystem.setStreamVolumeIndexAS(stream, index, device);
417     }
418 
419     /**
420      * Same as {@link AudioSystem#setPhoneState(int, int)}
421      * @param state
422      * @param uid
423      * @return
424      */
setPhoneState(int state, int uid)425     public int setPhoneState(int state, int uid) {
426         invalidateRoutingCache();
427         return AudioSystem.setPhoneState(state, uid);
428     }
429 
430     /**
431      * Same as {@link AudioSystem#setAllowedCapturePolicy(int, int)}
432      * @param uid
433      * @param flags
434      * @return
435      */
setAllowedCapturePolicy(int uid, int flags)436     public int setAllowedCapturePolicy(int uid, int flags) {
437         return AudioSystem.setAllowedCapturePolicy(uid, flags);
438     }
439 
440     /**
441      * Same as {@link AudioSystem#setForceUse(int, int)}
442      * @param usage
443      * @param config
444      * @return
445      */
setForceUse(int usage, int config)446     public int setForceUse(int usage, int config) {
447         invalidateRoutingCache();
448         return AudioSystem.setForceUse(usage, config);
449     }
450 
451     /**
452      * Same as {@link AudioSystem#getForceUse(int)}
453      * @param usage
454      * @return
455      */
getForceUse(int usage)456     public int getForceUse(int usage) {
457         return AudioSystem.getForceUse(usage);
458     }
459 
460     /**
461      * Same as {@link AudioSystem#registerPolicyMixes(ArrayList, boolean)}
462      * @param mixes
463      * @param register
464      * @return
465      */
registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register)466     public int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register) {
467         invalidateRoutingCache();
468         return AudioSystem.registerPolicyMixes(mixes, register);
469     }
470 
471     /**
472      * Same as {@link AudioSystem#setUidDeviceAffinities(int, int[], String[])}
473      * @param uid
474      * @param types
475      * @param addresses
476      * @return
477      */
setUidDeviceAffinities(int uid, @NonNull int[] types, @NonNull String[] addresses)478     public int setUidDeviceAffinities(int uid, @NonNull int[] types,  @NonNull String[] addresses) {
479         invalidateRoutingCache();
480         return AudioSystem.setUidDeviceAffinities(uid, types, addresses);
481     }
482 
483     /**
484      * Same as {@link AudioSystem#removeUidDeviceAffinities(int)}
485      * @param uid
486      * @return
487      */
removeUidDeviceAffinities(int uid)488     public int removeUidDeviceAffinities(int uid) {
489         invalidateRoutingCache();
490         return AudioSystem.removeUidDeviceAffinities(uid);
491     }
492 
493     /**
494      * Same as {@link AudioSystem#setUserIdDeviceAffinities(int, int[], String[])}
495      * @param userId
496      * @param types
497      * @param addresses
498      * @return
499      */
setUserIdDeviceAffinities(int userId, @NonNull int[] types, @NonNull String[] addresses)500     public int setUserIdDeviceAffinities(int userId, @NonNull int[] types,
501             @NonNull String[] addresses) {
502         invalidateRoutingCache();
503         return AudioSystem.setUserIdDeviceAffinities(userId, types, addresses);
504     }
505 
506     /**
507      * Same as {@link AudioSystem#removeUserIdDeviceAffinities(int)}
508      * @param userId
509      * @return
510      */
removeUserIdDeviceAffinities(int userId)511     public int removeUserIdDeviceAffinities(int userId) {
512         invalidateRoutingCache();
513         return AudioSystem.removeUserIdDeviceAffinities(userId);
514     }
515 
516     /**
517      * Part of AudioService dump
518      * @param pw
519      */
dump(PrintWriter pw)520     public void dump(PrintWriter pw) {
521         pw.println("\nAudioSystemAdapter:");
522         final DateTimeFormatter formatter = DateTimeFormatter
523                 .ofPattern("MM-dd HH:mm:ss:SSS")
524                 .withLocale(Locale.US)
525                 .withZone(ZoneId.systemDefault());
526         synchronized (sDeviceCacheLock) {
527             pw.println(" last cache clear time: " + formatter.format(
528                     Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs)));
529             pw.println(" mDevicesForAttrCache:");
530             if (mDevicesForAttrCache != null) {
531                 for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
532                         entry : mDevicesForAttrCache.entrySet()) {
533                     final AudioAttributes attributes = entry.getKey().first;
534                     try {
535                         final int stream = attributes.getVolumeControlStream();
536                         pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
537                                 + " stream: "
538                                 + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
539                         for (AudioDeviceAttributes devAttr : entry.getValue()) {
540                             pw.println("\t\t" + devAttr);
541                         }
542                     } catch (IllegalArgumentException e) {
543                         // dump could fail if attributes do not map to a stream.
544                         pw.println("\t dump failed for attributes: " + attributes);
545                         Log.e(TAG, "dump failed", e);
546                     }
547                 }
548             }
549         }
550 
551         if (!ENABLE_GETDEVICES_STATS) {
552             // only stats in the rest of this dump
553             return;
554         }
555         for (int i = 0; i < NB_MEASUREMENTS; i++) {
556             pw.println(mMethodNames[i]
557                     + ": counter=" + mMethodCallCounter[i]
558                     + " time(ms)=" + (mMethodTimeNs[i] / 1E6)
559                     + (USE_CACHE_FOR_GETDEVICES
560                         ? (" FScacheHit=" + mMethodCacheHit[METHOD_GETDEVICESFORATTRIBUTES])
561                         : ""));
562         }
563         pw.println("\n");
564     }
565 }
566