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