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