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