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