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