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