1/* 2 * Copyright (C) 2016 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 */ 16package com.android.car.test; 17 18import static com.android.car.test.AudioTestUtils.doRequestFocus; 19 20import android.car.Car; 21import android.car.CarNotConnectedException; 22import android.car.media.CarAudioManager; 23import android.content.Context; 24import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag; 25import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag; 26import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState; 27import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeIndex; 28import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction; 29import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; 30import android.hardware.automotive.vehicle.V2_0.VehicleProperty; 31import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; 32import android.media.AudioAttributes; 33import android.media.AudioManager; 34import android.media.IVolumeController; 35import android.os.RemoteException; 36import android.os.SystemClock; 37import android.test.suitebuilder.annotation.MediumTest; 38import android.util.Log; 39import android.util.Pair; 40import android.util.SparseIntArray; 41import android.view.KeyEvent; 42 43import com.android.car.VolumeUtils; 44import com.android.car.vehiclehal.VehiclePropValueBuilder; 45import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; 46import com.android.internal.annotations.GuardedBy; 47 48import com.google.android.collect.Lists; 49 50import java.util.ArrayList; 51import java.util.List; 52 53@MediumTest 54public class CarVolumeServiceTest extends MockedCarTestBase { 55 private static final String TAG = CarVolumeServiceTest.class.getSimpleName(); 56 57 private static final int MIN_VOL = 1; 58 private static final int MAX_VOL = 20; 59 private static final long TIMEOUT_MS = 3000; 60 private static final long POLL_INTERVAL_MS = 50; 61 62 private static final int[] LOGICAL_STREAMS = { 63 AudioManager.STREAM_VOICE_CALL, 64 AudioManager.STREAM_SYSTEM, 65 AudioManager.STREAM_RING, 66 AudioManager.STREAM_MUSIC, 67 AudioManager.STREAM_ALARM, 68 AudioManager.STREAM_NOTIFICATION, 69 AudioManager.STREAM_DTMF, 70 }; 71 72 private CarAudioManager mCarAudioManager; 73 private AudioManager mAudioManager; 74 75 @Override 76 protected synchronized void setUp() throws Exception { 77 super.setUp(); 78 // AudioManager should be created in main thread to get focus event. :( 79 runOnMainSync(new Runnable() { 80 @Override 81 public void run() { 82 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 83 } 84 }); 85 } 86 87 private SingleChannelVolumeHandler setupExternalVolumeEmulation(boolean supportAudioContext) 88 throws Exception { 89 List<Integer> maxs = new ArrayList<>(); 90 int supportedAudioContext = 0; 91 if (!supportAudioContext) { 92 // set up 2 physical streams 93 maxs.add(MAX_VOL); 94 maxs.add(MAX_VOL); 95 } else { 96 // add supported contexts 97 int[] contexts = VolumeUtils.CAR_AUDIO_CONTEXT; 98 for (int context : contexts) { 99 supportedAudioContext |= context; 100 maxs.add(MAX_VOL); 101 } 102 } 103 SingleChannelVolumeHandler handler = 104 startVolumeEmulation(supportedAudioContext, maxs); 105 mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE); 106 return handler; 107 } 108 109 public void testUnknownVolumeChange() throws Exception { 110 SingleChannelVolumeHandler volumeHandler = setupExternalVolumeEmulation(true); 111 VolumeController volumeController = new VolumeController(); 112 mCarAudioManager.setVolumeController(volumeController); 113 mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 2, 0); 114 // give focus to music, now current context becomes VehicleAudioContextFlag.MUSIC_FLAG 115 CarAudioFocusTest.AudioFocusListener listenerMusic = 116 new CarAudioFocusTest.AudioFocusListener(); 117 int res = doRequestFocus(mAudioManager, listenerMusic, 118 AudioManager.STREAM_MUSIC, 119 AudioManager.AUDIOFOCUS_GAIN); 120 assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); 121 int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); 122 mAudioFocusPropertyHandler.sendAudioFocusState( 123 VehicleAudioFocusState.STATE_GAIN, 124 request[1], 125 VehicleAudioExtFocusFlag.NONE_FLAG); 126 127 // let vehicle hal report volume change from unknown context, we should map it to the 128 // current context (music). 129 volumeHandler.injectVolumeEvent(VehicleAudioContextFlag.UNKNOWN_FLAG, 3); 130 // now music volume should be recorded as 3. 131 volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, 3)); 132 } 133 134 public void testVolumeLimits() throws Exception { 135 setupExternalVolumeEmulation(false); 136 for (int stream : LOGICAL_STREAMS) { 137 assertEquals(MAX_VOL, mCarAudioManager.getStreamMaxVolume(stream)); 138 } 139 } 140 141 public void testVolumeSet() throws Exception { 142 try { 143 setupExternalVolumeEmulation(false); 144 int callVol = 10; 145 int musicVol = 15; 146 mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); 147 mCarAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, callVol, 0); 148 149 volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol), 150 createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); 151 152 musicVol = MAX_VOL + 1; 153 mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); 154 155 volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, MAX_VOL), 156 createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); 157 } catch (CarNotConnectedException e) { 158 fail("Car not connected"); 159 } 160 } 161 162 public void testSuppressVolumeUI() { 163 try { 164 setupExternalVolumeEmulation(false); 165 VolumeController volumeController = new VolumeController(); 166 mCarAudioManager.setVolumeController(volumeController); 167 168 // first give focus to system sound 169 CarAudioFocusTest.AudioFocusListener listenerMusic = 170 new CarAudioFocusTest.AudioFocusListener(); 171 int res = doRequestFocus(mAudioManager, listenerMusic, 172 AudioManager.STREAM_SYSTEM, 173 AudioManager.AUDIOFOCUS_GAIN); 174 assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); 175 int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); 176 mAudioFocusPropertyHandler.sendAudioFocusState( 177 VehicleAudioFocusState.STATE_GAIN, 178 request[1], 179 VehicleAudioExtFocusFlag.NONE_FLAG); 180 181 // focus gives to Alarm, there should be a audio context change. 182 CarAudioFocusTest.AudioFocusListener listenerAlarm = new 183 CarAudioFocusTest.AudioFocusListener(); 184 AudioAttributes callAttrib = (new AudioAttributes.Builder()). 185 setUsage(AudioAttributes.USAGE_ALARM). 186 build(); 187 res = doRequestFocus(mAudioManager, listenerAlarm, callAttrib, 188 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 189 assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); 190 request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); 191 mAudioFocusPropertyHandler.sendAudioFocusState( 192 VehicleAudioFocusState.STATE_GAIN, request[1], 193 VehicleAudioExtFocusFlag.NONE_FLAG); 194 // should not show UI 195 volumeChangeVerificationPoll(AudioManager.STREAM_ALARM, false, volumeController); 196 197 int alarmVol = mCarAudioManager.getStreamVolume(AudioManager.STREAM_ALARM); 198 // set alarm volume with show_ui flag and a different volume 199 mCarAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 200 (alarmVol + 1) % mCarAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 201 AudioManager.FLAG_SHOW_UI); 202 // should show ui 203 volumeChangeVerificationPoll(AudioManager.STREAM_ALARM, true, volumeController); 204 mAudioManager.abandonAudioFocus(listenerAlarm); 205 } catch (Exception e) { 206 fail(e.getMessage()); 207 } 208 } 209 210 public void testVolumeKeys() throws Exception { 211 try { 212 setupExternalVolumeEmulation(false); 213 int musicVol = 10; 214 mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); 215 int callVol = 12; 216 mCarAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, callVol, 0); 217 218 CarAudioFocusTest.AudioFocusListener listenerMusic = 219 new CarAudioFocusTest.AudioFocusListener(); 220 int res = doRequestFocus(mAudioManager, listenerMusic, 221 AudioManager.STREAM_MUSIC, 222 AudioManager.AUDIOFOCUS_GAIN); 223 assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); 224 int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); 225 mAudioFocusPropertyHandler.sendAudioFocusState( 226 VehicleAudioFocusState.STATE_GAIN, 227 request[1], 228 VehicleAudioExtFocusFlag.NONE_FLAG); 229 230 231 assertEquals(musicVol, mCarAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)); 232 sendVolumeKey(true /*vol up*/); 233 musicVol++; 234 volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol)); 235 236 // call start 237 CarAudioFocusTest.AudioFocusListener listenerCall = new 238 CarAudioFocusTest.AudioFocusListener(); 239 AudioAttributes callAttrib = (new AudioAttributes.Builder()). 240 setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION). 241 build(); 242 doRequestFocus(mAudioManager, listenerCall, callAttrib, 243 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); 244 request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); 245 mAudioFocusPropertyHandler.sendAudioFocusState( 246 VehicleAudioFocusState.STATE_GAIN, request[1], 247 VehicleAudioExtFocusFlag.NONE_FLAG); 248 249 sendVolumeKey(true /*vol up*/); 250 callVol++; 251 volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol), 252 createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); 253 } catch (CarNotConnectedException | InterruptedException e) { 254 fail(e.toString()); 255 } 256 } 257 258 private Pair<Integer, Integer> createStreamVolPair(int stream, int vol) { 259 return new Pair<>(stream, vol); 260 } 261 262 private void volumeVerificationPoll(Pair<Integer, Integer>... expectedStreamVolPairs) { 263 boolean isVolExpected = false; 264 int timeElapsedMs = 0; 265 try { 266 while (!isVolExpected && timeElapsedMs <= TIMEOUT_MS) { 267 Thread.sleep(POLL_INTERVAL_MS); 268 isVolExpected = true; 269 for (Pair<Integer, Integer> vol : expectedStreamVolPairs) { 270 if (mCarAudioManager.getStreamVolume(vol.first) != vol.second) { 271 isVolExpected = false; 272 break; 273 } 274 } 275 timeElapsedMs += POLL_INTERVAL_MS; 276 } 277 assertEquals(isVolExpected, true); 278 } catch (InterruptedException | CarNotConnectedException e) { 279 fail(e.toString()); 280 } 281 } 282 283 private void volumeChangeVerificationPoll(int stream, boolean showUI, 284 VolumeController controller) { 285 boolean isVolExpected = false; 286 int timeElapsedMs = 0; 287 try { 288 while (!isVolExpected && timeElapsedMs <= TIMEOUT_MS) { 289 Thread.sleep(POLL_INTERVAL_MS); 290 Pair<Integer, Integer> volChange = controller.getLastVolumeChanges(); 291 if (volChange.first == stream 292 && (((volChange.second.intValue() & AudioManager.FLAG_SHOW_UI) != 0) 293 == showUI)) { 294 isVolExpected = true; 295 break; 296 } 297 timeElapsedMs += POLL_INTERVAL_MS; 298 } 299 assertEquals(true, isVolExpected); 300 } catch (Exception e) { 301 fail(e.toString()); 302 } 303 } 304 305 private class SingleChannelVolumeHandler implements VehicleHalPropertyHandler { 306 private final List<Integer> mMaxs; 307 private final SparseIntArray mCurrent; 308 309 public SingleChannelVolumeHandler(List<Integer> maxs) { 310 mMaxs = maxs; 311 int size = maxs.size(); 312 mCurrent = new SparseIntArray(size); 313 // initialize the vol to be the min volume. 314 for (int i = 0; i < size; i++) { 315 mCurrent.put(i, mMaxs.get(i)); 316 } 317 } 318 319 public void injectVolumeEvent(int context, int volume) { 320 getMockedVehicleHal().injectEvent( 321 VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME) 322 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 323 .addIntValue(context, volume, 0) 324 .build()); 325 } 326 327 @Override 328 public void onPropertySet(VehiclePropValue value) { 329 ArrayList<Integer> v = value.value.int32Values; 330 int stream = v.get(VehicleAudioVolumeIndex.STREAM); 331 int volume = v.get(VehicleAudioVolumeIndex.VOLUME); 332 int state = v.get(VehicleAudioVolumeIndex.STATE); 333 Log.d(TAG, "state " + state); 334 335 mCurrent.put(stream, volume); 336 getMockedVehicleHal().injectEvent( 337 VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME) 338 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 339 .addIntValue(stream, volume, state) 340 .build()); 341 } 342 343 @Override 344 public VehiclePropValue onPropertyGet(VehiclePropValue value) { 345 int stream = value.value.int32Values.get(VehicleAudioVolumeIndex.STREAM); 346 int volume = mCurrent.get(stream); 347 return VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME) 348 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 349 .addIntValue(stream, volume, 0) 350 .build(); 351 } 352 353 @Override 354 public void onPropertySubscribe(int property, int zones, float sampleRate) { 355 } 356 357 @Override 358 public void onPropertyUnsubscribe(int property) { 359 } 360 } 361 362 private final CarAudioFocusTest.FocusPropertyHandler mAudioFocusPropertyHandler = 363 new CarAudioFocusTest.FocusPropertyHandler(this); 364 365 private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler = 366 new VehicleHalPropertyHandler() { 367 @Override 368 public void onPropertySet(VehiclePropValue value) { 369 //TODO 370 } 371 372 @Override 373 public VehiclePropValue onPropertyGet(VehiclePropValue value) { 374 fail("cannot get"); 375 return null; 376 } 377 378 @Override 379 public void onPropertySubscribe(int property, int zones, float sampleRate) { 380 fail("cannot subscribe"); 381 } 382 383 @Override 384 public void onPropertyUnsubscribe(int property) { 385 fail("cannot unsubscribe"); 386 } 387 }; 388 389 private final VehicleHalPropertyHandler mHWKeyHandler = new VehicleHalPropertyHandler() { 390 @Override 391 public void onPropertySet(VehiclePropValue value) { 392 //TODO 393 } 394 395 @Override 396 public VehiclePropValue onPropertyGet(VehiclePropValue value) { 397 return VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) 398 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 399 .addIntValue(0, 0, 0, 0) 400 .build(); 401 } 402 403 @Override 404 public void onPropertySubscribe(int property, int zones, float sampleRate) { 405 // 406 } 407 408 @Override 409 public void onPropertyUnsubscribe(int property) { 410 // 411 } 412 }; 413 414 private SingleChannelVolumeHandler startVolumeEmulation(int supportedAudioVolumeContext, 415 List<Integer> maxs) { 416 SingleChannelVolumeHandler singleChannelVolumeHandler = 417 new SingleChannelVolumeHandler(maxs); 418 int zones = (1<<maxs.size()) - 1; 419 420 ArrayList<Integer> audioVolumeConfigArray = 421 Lists.newArrayList( 422 supportedAudioVolumeContext, 423 0 /* capability flag*/, 424 0, /* reserved */ 425 0 /* reserved */); 426 audioVolumeConfigArray.addAll(maxs); 427 428 addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler) 429 .setConfigArray(audioVolumeConfigArray) 430 .setSupportedAreas(zones); 431 432 addProperty(VehicleProperty.HW_KEY_INPUT, mHWKeyHandler) 433 .setAccess(VehiclePropertyAccess.READ); 434 435 addProperty(VehicleProperty.AUDIO_FOCUS, mAudioFocusPropertyHandler); 436 437 addProperty(VehicleProperty.AUDIO_ROUTING_POLICY, mAudioRoutingPolicyPropertyHandler) 438 .setAccess(VehiclePropertyAccess.WRITE); 439 440 addStaticProperty(VehicleProperty.AUDIO_HW_VARIANT, 441 VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_HW_VARIANT) 442 .addIntValue(-1) 443 .build()) 444 .setConfigArray(Lists.newArrayList(0)); 445 446 reinitializeMockedHal(); 447 return singleChannelVolumeHandler; 448 } 449 450 private void sendVolumeKey(boolean volUp) { 451 int[] actionDown = { 452 VehicleHwKeyInputAction.ACTION_DOWN, 453 volUp ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0}; 454 455 VehiclePropValue injectValue = 456 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) 457 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 458 .addIntValue(actionDown) 459 .build(); 460 461 getMockedVehicleHal().injectEvent(injectValue); 462 463 int[] actionUp = { 464 VehicleHwKeyInputAction.ACTION_UP, 465 volUp ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0 }; 466 467 injectValue = 468 VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) 469 .setTimestamp(SystemClock.elapsedRealtimeNanos()) 470 .addIntValue(actionUp) 471 .build(); 472 473 getMockedVehicleHal().injectEvent(injectValue); 474 } 475 476 private static class VolumeController extends IVolumeController.Stub { 477 @GuardedBy("this") 478 private int mLastStreamChanged = -1; 479 480 @GuardedBy("this") 481 private int mLastFlags = -1; 482 483 public synchronized Pair<Integer, Integer> getLastVolumeChanges() { 484 return new Pair<>(mLastStreamChanged, mLastFlags); 485 } 486 487 @Override 488 public void displaySafeVolumeWarning(int flags) throws RemoteException {} 489 490 @Override 491 public void volumeChanged(int streamType, int flags) throws RemoteException { 492 synchronized (this) { 493 mLastStreamChanged = streamType; 494 mLastFlags = flags; 495 } 496 } 497 498 @Override 499 public void masterMuteChanged(int flags) throws RemoteException {} 500 501 @Override 502 public void setLayoutDirection(int layoutDirection) throws RemoteException { 503 } 504 505 @Override 506 public void dismiss() throws RemoteException { 507 } 508 509 @Override 510 public void setA11yMode(int mode) throws RemoteException { 511 } 512 } 513} 514