| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.test; |
| |
| import static com.android.car.test.AudioTestUtils.doRequestFocus; |
| |
| import com.google.android.collect.Lists; |
| |
| import android.car.Car; |
| import android.car.CarNotConnectedException; |
| import android.car.media.CarAudioManager; |
| import android.content.Context; |
| import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag; |
| import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState; |
| import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeIndex; |
| import android.hardware.automotive.vehicle.V2_0.VehicleHwKeyInputAction; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropValue; |
| import android.hardware.automotive.vehicle.V2_0.VehicleProperty; |
| import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess; |
| import android.media.AudioAttributes; |
| import android.media.AudioManager; |
| import android.media.IVolumeController; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.SparseIntArray; |
| import android.view.KeyEvent; |
| |
| import com.android.car.vehiclehal.VehiclePropValueBuilder; |
| import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler; |
| import com.android.internal.annotations.GuardedBy; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| public class CarVolumeServiceTest extends MockedCarTestBase { |
| private static final String TAG = CarVolumeServiceTest.class.getSimpleName(); |
| |
| private static final int MIN_VOL = 1; |
| private static final int MAX_VOL = 20; |
| private static final long TIMEOUT_MS = 3000; |
| private static final long POLL_INTERVAL_MS = 50; |
| |
| private static final int[] LOGICAL_STREAMS = { |
| AudioManager.STREAM_VOICE_CALL, |
| AudioManager.STREAM_SYSTEM, |
| AudioManager.STREAM_RING, |
| AudioManager.STREAM_MUSIC, |
| AudioManager.STREAM_ALARM, |
| AudioManager.STREAM_NOTIFICATION, |
| AudioManager.STREAM_DTMF, |
| }; |
| |
| private CarAudioManager mCarAudioManager; |
| private AudioManager mAudioManager; |
| |
| @Override |
| protected synchronized void setUp() throws Exception { |
| super.setUp(); |
| // AudioManager should be created in main thread to get focus event. :( |
| runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); |
| } |
| }); |
| |
| List<Integer> maxs = new ArrayList<>(); |
| maxs.add(MAX_VOL); |
| maxs.add(MAX_VOL); |
| |
| // TODO: add tests for audio context supported cases. |
| startVolumeEmulation(0 /*supported audio context*/, maxs); |
| mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE); |
| } |
| |
| public void testVolumeLimits() throws Exception { |
| for (int stream : LOGICAL_STREAMS) { |
| assertEquals(MAX_VOL, mCarAudioManager.getStreamMaxVolume(stream)); |
| } |
| } |
| |
| public void testVolumeSet() { |
| try { |
| int callVol = 10; |
| int musicVol = 15; |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, callVol, 0); |
| |
| volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol), |
| createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); |
| |
| musicVol = MAX_VOL + 1; |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); |
| |
| volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, MAX_VOL), |
| createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); |
| } catch (CarNotConnectedException e) { |
| fail("Car not connected"); |
| } |
| } |
| |
| public void testSuppressVolumeUI() { |
| try { |
| VolumeController volumeController = new VolumeController(); |
| mCarAudioManager.setVolumeController(volumeController); |
| |
| // first give focus to system sound |
| CarAudioFocusTest.AudioFocusListener listenerMusic = |
| new CarAudioFocusTest.AudioFocusListener(); |
| int res = doRequestFocus(mAudioManager, listenerMusic, |
| AudioManager.STREAM_SYSTEM, |
| AudioManager.AUDIOFOCUS_GAIN); |
| assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); |
| int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); |
| mAudioFocusPropertyHandler.sendAudioFocusState( |
| VehicleAudioFocusState.STATE_GAIN, |
| request[1], |
| VehicleAudioExtFocusFlag.NONE_FLAG); |
| |
| // focus gives to Alarm, there should be a audio context change. |
| CarAudioFocusTest.AudioFocusListener listenerAlarm = new |
| CarAudioFocusTest.AudioFocusListener(); |
| AudioAttributes callAttrib = (new AudioAttributes.Builder()). |
| setUsage(AudioAttributes.USAGE_ALARM). |
| build(); |
| res = doRequestFocus(mAudioManager, listenerAlarm, callAttrib, |
| AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); |
| assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); |
| request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); |
| mAudioFocusPropertyHandler.sendAudioFocusState( |
| VehicleAudioFocusState.STATE_GAIN, request[1], |
| VehicleAudioExtFocusFlag.NONE_FLAG); |
| // should not show UI |
| volumeChangeVerificationPoll(AudioManager.STREAM_ALARM, false, volumeController); |
| |
| int alarmVol = mCarAudioManager.getStreamVolume(AudioManager.STREAM_ALARM); |
| // set alarm volume with show_ui flag and a different volume |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, |
| (alarmVol + 1) % mCarAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), |
| AudioManager.FLAG_SHOW_UI); |
| // should show ui |
| volumeChangeVerificationPoll(AudioManager.STREAM_ALARM, true, volumeController); |
| mAudioManager.abandonAudioFocus(listenerAlarm); |
| } catch (Exception e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| public void testVolumeKeys() throws Exception { |
| try { |
| int musicVol = 10; |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVol, 0); |
| int callVol = 12; |
| mCarAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, callVol, 0); |
| |
| CarAudioFocusTest.AudioFocusListener listenerMusic = |
| new CarAudioFocusTest.AudioFocusListener(); |
| int res = doRequestFocus(mAudioManager, listenerMusic, |
| AudioManager.STREAM_MUSIC, |
| AudioManager.AUDIOFOCUS_GAIN); |
| assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); |
| int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); |
| mAudioFocusPropertyHandler.sendAudioFocusState( |
| VehicleAudioFocusState.STATE_GAIN, |
| request[1], |
| VehicleAudioExtFocusFlag.NONE_FLAG); |
| |
| |
| assertEquals(musicVol, mCarAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)); |
| sendVolumeKey(true /*vol up*/); |
| musicVol++; |
| volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol)); |
| |
| // call start |
| CarAudioFocusTest.AudioFocusListener listenerCall = new |
| CarAudioFocusTest.AudioFocusListener(); |
| AudioAttributes callAttrib = (new AudioAttributes.Builder()). |
| setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION). |
| build(); |
| doRequestFocus(mAudioManager, listenerCall, callAttrib, |
| AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); |
| request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); |
| mAudioFocusPropertyHandler.sendAudioFocusState( |
| VehicleAudioFocusState.STATE_GAIN, request[1], |
| VehicleAudioExtFocusFlag.NONE_FLAG); |
| |
| sendVolumeKey(true /*vol up*/); |
| callVol++; |
| volumeVerificationPoll(createStreamVolPair(AudioManager.STREAM_MUSIC, musicVol), |
| createStreamVolPair(AudioManager.STREAM_VOICE_CALL, callVol)); |
| } catch (CarNotConnectedException | InterruptedException e) { |
| fail(e.toString()); |
| } |
| } |
| |
| private Pair<Integer, Integer> createStreamVolPair(int stream, int vol) { |
| return new Pair<>(stream, vol); |
| } |
| |
| private void volumeVerificationPoll(Pair<Integer, Integer>... expectedStreamVolPairs) { |
| boolean isVolExpected = false; |
| int timeElapsedMs = 0; |
| try { |
| while (!isVolExpected && timeElapsedMs <= TIMEOUT_MS) { |
| Thread.sleep(POLL_INTERVAL_MS); |
| isVolExpected = true; |
| for (Pair<Integer, Integer> vol : expectedStreamVolPairs) { |
| if (mCarAudioManager.getStreamVolume(vol.first) != vol.second) { |
| isVolExpected = false; |
| break; |
| } |
| } |
| timeElapsedMs += POLL_INTERVAL_MS; |
| } |
| assertEquals(isVolExpected, true); |
| } catch (InterruptedException | CarNotConnectedException e) { |
| fail(e.toString()); |
| } |
| } |
| |
| private void volumeChangeVerificationPoll(int stream, boolean showUI, |
| VolumeController controller) { |
| boolean isVolExpected = false; |
| int timeElapsedMs = 0; |
| try { |
| while (!isVolExpected && timeElapsedMs <= TIMEOUT_MS) { |
| Thread.sleep(POLL_INTERVAL_MS); |
| Pair<Integer, Integer> volChange = controller.getLastVolumeChanges(); |
| if (volChange.first == stream |
| && (((volChange.second.intValue() & AudioManager.FLAG_SHOW_UI) != 0) |
| == showUI)) { |
| isVolExpected = true; |
| break; |
| } |
| timeElapsedMs += POLL_INTERVAL_MS; |
| } |
| assertEquals(true, isVolExpected); |
| } catch (Exception e) { |
| fail(e.toString()); |
| } |
| } |
| |
| private class SingleChannelVolumeHandler implements VehicleHalPropertyHandler { |
| private final List<Integer> mMaxs; |
| private final SparseIntArray mCurrent; |
| |
| public SingleChannelVolumeHandler(List<Integer> maxs) { |
| mMaxs = maxs; |
| int size = maxs.size(); |
| mCurrent = new SparseIntArray(size); |
| // initialize the vol to be the min volume. |
| for (int i = 0; i < size; i++) { |
| mCurrent.put(i, mMaxs.get(i)); |
| } |
| } |
| |
| @Override |
| public void onPropertySet(VehiclePropValue value) { |
| ArrayList<Integer> v = value.value.int32Values; |
| int stream = v.get(VehicleAudioVolumeIndex.INDEX_STREAM); |
| int volume = v.get(VehicleAudioVolumeIndex.INDEX_VOLUME); |
| int state = v.get(VehicleAudioVolumeIndex.INDEX_STATE); |
| |
| mCurrent.put(stream, volume); |
| getMockedVehicleHal().injectEvent( |
| VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME) |
| .setTimestamp(SystemClock.elapsedRealtimeNanos()) |
| .addIntValue(stream, volume, state) |
| .build()); |
| } |
| |
| @Override |
| public VehiclePropValue onPropertyGet(VehiclePropValue value) { |
| int stream = value.value.int32Values.get(VehicleAudioVolumeIndex.INDEX_STREAM); |
| int volume = mCurrent.get(stream); |
| return VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_VOLUME) |
| .setTimestamp(SystemClock.elapsedRealtimeNanos()) |
| .addIntValue(stream, volume, 0) |
| .build(); |
| } |
| |
| @Override |
| public void onPropertySubscribe(int property, int zones, float sampleRate) { |
| } |
| |
| @Override |
| public void onPropertyUnsubscribe(int property) { |
| } |
| } |
| |
| private final CarAudioFocusTest.FocusPropertyHandler mAudioFocusPropertyHandler = |
| new CarAudioFocusTest.FocusPropertyHandler(this); |
| |
| private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler = |
| new VehicleHalPropertyHandler() { |
| @Override |
| public void onPropertySet(VehiclePropValue value) { |
| //TODO |
| } |
| |
| @Override |
| public VehiclePropValue onPropertyGet(VehiclePropValue value) { |
| fail("cannot get"); |
| return null; |
| } |
| |
| @Override |
| public void onPropertySubscribe(int property, int zones, float sampleRate) { |
| fail("cannot subscribe"); |
| } |
| |
| @Override |
| public void onPropertyUnsubscribe(int property) { |
| fail("cannot unsubscribe"); |
| } |
| }; |
| |
| private final VehicleHalPropertyHandler mHWKeyHandler = new VehicleHalPropertyHandler() { |
| @Override |
| public void onPropertySet(VehiclePropValue value) { |
| //TODO |
| } |
| |
| @Override |
| public VehiclePropValue onPropertyGet(VehiclePropValue value) { |
| return VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) |
| .setTimestamp(SystemClock.elapsedRealtimeNanos()) |
| .addIntValue(0, 0, 0, 0) |
| .build(); |
| } |
| |
| @Override |
| public void onPropertySubscribe(int property, int zones, float sampleRate) { |
| // |
| } |
| |
| @Override |
| public void onPropertyUnsubscribe(int property) { |
| // |
| } |
| }; |
| |
| private void startVolumeEmulation(int supportedAudioVolumeContext, List<Integer> maxs) { |
| SingleChannelVolumeHandler singleChannelVolumeHandler = |
| new SingleChannelVolumeHandler(maxs); |
| int zones = (1<<maxs.size()) - 1; |
| |
| ArrayList<Integer> audioVolumeConfigArray = |
| Lists.newArrayList( |
| supportedAudioVolumeContext, |
| 0 /* capability flag*/, |
| 0, /* reserved */ |
| 0 /* reserved */); |
| audioVolumeConfigArray.addAll(maxs); |
| |
| addProperty(VehicleProperty.AUDIO_VOLUME, singleChannelVolumeHandler) |
| .setConfigArray(audioVolumeConfigArray) |
| .setSupportedAreas(zones); |
| |
| addProperty(VehicleProperty.HW_KEY_INPUT, mHWKeyHandler) |
| .setAccess(VehiclePropertyAccess.READ); |
| |
| addProperty(VehicleProperty.AUDIO_FOCUS, mAudioFocusPropertyHandler); |
| |
| addProperty(VehicleProperty.AUDIO_ROUTING_POLICY, mAudioRoutingPolicyPropertyHandler) |
| .setAccess(VehiclePropertyAccess.WRITE); |
| |
| addStaticProperty(VehicleProperty.AUDIO_HW_VARIANT, |
| VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_HW_VARIANT) |
| .addIntValue(-1) |
| .build()) |
| .setConfigArray(Lists.newArrayList(0)); |
| |
| reinitializeMockedHal(); |
| } |
| |
| private void sendVolumeKey(boolean volUp) { |
| int[] actionDown = { |
| VehicleHwKeyInputAction.ACTION_DOWN, |
| volUp ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0}; |
| |
| VehiclePropValue injectValue = |
| VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) |
| .setTimestamp(SystemClock.elapsedRealtimeNanos()) |
| .addIntValue(actionDown) |
| .build(); |
| |
| getMockedVehicleHal().injectEvent(injectValue); |
| |
| int[] actionUp = { |
| VehicleHwKeyInputAction.ACTION_UP, |
| volUp ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0 }; |
| |
| injectValue = |
| VehiclePropValueBuilder.newBuilder(VehicleProperty.HW_KEY_INPUT) |
| .setTimestamp(SystemClock.elapsedRealtimeNanos()) |
| .addIntValue(actionUp) |
| .build(); |
| |
| getMockedVehicleHal().injectEvent(injectValue); |
| } |
| |
| private static class VolumeController extends IVolumeController.Stub { |
| @GuardedBy("this") |
| private int mLastStreamChanged = -1; |
| |
| @GuardedBy("this") |
| private int mLastFlags = -1; |
| |
| public synchronized Pair<Integer, Integer> getLastVolumeChanges() { |
| return new Pair<>(mLastStreamChanged, mLastFlags); |
| } |
| |
| @Override |
| public void displaySafeVolumeWarning(int flags) throws RemoteException {} |
| |
| @Override |
| public void volumeChanged(int streamType, int flags) throws RemoteException { |
| synchronized (this) { |
| mLastStreamChanged = streamType; |
| mLastFlags = flags; |
| } |
| } |
| |
| @Override |
| public void masterMuteChanged(int flags) throws RemoteException {} |
| |
| @Override |
| public void setLayoutDirection(int layoutDirection) throws RemoteException { |
| } |
| |
| @Override |
| public void dismiss() throws RemoteException { |
| } |
| |
| @Override |
| public void setA11yMode(int mode) throws RemoteException { |
| } |
| } |
| } |