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 android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS;
19import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_STREAM_STATE;
20import static com.android.car.test.AudioTestUtils.doRequestFocus;
21
22import com.google.android.collect.Lists;
23
24import android.car.Car;
25import android.car.media.CarAudioManager;
26import android.content.Context;
27import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
28import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag;
29import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusIndex;
30import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusRequest;
31import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState;
32import android.hardware.automotive.vehicle.V2_0.VehicleAudioStream;
33import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
34import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
35import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
36import android.media.AudioAttributes;
37import android.media.AudioManager;
38import android.os.SystemClock;
39import android.test.suitebuilder.annotation.MediumTest;
40
41import com.android.car.vehiclehal.VehiclePropValueBuilder;
42import com.android.car.vehiclehal.test.MockedVehicleHal.FailingPropertyHandler;
43import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
44
45import java.util.ArrayList;
46import java.util.concurrent.Semaphore;
47import java.util.concurrent.TimeUnit;
48
49/**
50 * Test to check if system sound can be played without having focus.
51 */
52@MediumTest
53public class CarAudioFocusSystemSoundTest extends MockedCarTestBase {
54    private static final String TAG = CarAudioFocusTest.class.getSimpleName();
55
56    private static final long TIMEOUT_MS = 3000;
57
58    private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler =
59            new FailingPropertyHandler() {
60        @Override
61        public void onPropertySet(VehiclePropValue value) {
62            //TODO
63        }
64    };
65
66    private final FocusPropertyHandler mAudioFocusPropertyHandler =
67            new FocusPropertyHandler(this);
68
69    private AudioManager mAudioManager;
70
71    @Override
72    protected synchronized void configureMockedHal() {
73        addProperty(VehicleProperty.AUDIO_ROUTING_POLICY, mAudioRoutingPolicyPropertyHandler)
74                .setAccess(VehiclePropertyAccess.WRITE);
75        addProperty(VehicleProperty.AUDIO_FOCUS, mAudioFocusPropertyHandler);
76        addProperty(VehicleProperty.AUDIO_STREAM_STATE);
77
78
79        addStaticProperty(VehicleProperty.AUDIO_HW_VARIANT,
80                VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_HW_VARIANT)
81                        .addIntValue(-1)
82                        .build())
83                .setConfigArray(Lists.newArrayList(0));
84    }
85
86    @Override
87    protected void setUp() throws Exception {
88        super.setUp();
89        // AudioManager should be created in main thread to get focus event. :(
90        runOnMainSync(new Runnable() {
91            @Override
92            public void run() {
93                mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
94            }
95        });
96    }
97
98    private void notifyStreamState(int streamNumber, boolean active) {
99        getMockedVehicleHal().injectEvent(VehiclePropValueBuilder.newBuilder(AUDIO_STREAM_STATE)
100                .setTimestamp()
101                .addIntValue(new int[] { active ? 1 : 0, streamNumber })
102                .build());
103    }
104
105    public void testSystemSoundPlayStop() throws Exception {
106        //system sound start
107        notifyStreamState(1, true);
108        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
109        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_NO_DUCK,
110                request[0]);
111        assertEquals(0x2, request[1]);
112        assertEquals(0, request[2]);
113        assertEquals(VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, request[3]);
114        mAudioFocusPropertyHandler.sendAudioFocusState(
115                VehicleAudioFocusState.STATE_GAIN_TRANSIENT,
116                request[1],
117                VehicleAudioExtFocusFlag.NONE_FLAG);
118        // system sound stop
119        notifyStreamState(1, false);
120        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
121        assertEquals(VehicleAudioFocusRequest.REQUEST_RELEASE,
122                request[0]);
123        assertEquals(0, request[1]);
124        assertEquals(0, request[2]);
125        assertEquals(0, request[3]);
126        mAudioFocusPropertyHandler.sendAudioFocusState(
127                VehicleAudioFocusState.STATE_LOSS,
128                request[1],
129                VehicleAudioExtFocusFlag.NONE_FLAG);
130    }
131
132    public void testRadioSystemSound() throws Exception {
133        // radio start
134        AudioFocusListener listenerRadio = new AudioFocusListener();
135        CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager(
136                Car.AUDIO_SERVICE);
137        assertNotNull(carAudioManager);
138        AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(
139                CarAudioManager.CAR_AUDIO_USAGE_RADIO);
140        int res = doRequestFocus(mAudioManager, listenerRadio,
141                radioAttributes, AudioManager.AUDIOFOCUS_GAIN);
142        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
143        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
144        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
145        assertEquals(0, request[1]);
146        assertEquals(VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG,
147                request[2]);
148        assertEquals(VehicleAudioContextFlag.RADIO_FLAG, request[3]);
149        mAudioFocusPropertyHandler.sendAudioFocusState(
150                VehicleAudioFocusState.STATE_GAIN,
151                0,
152                VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG);
153        // system sound start
154        notifyStreamState(1, true);
155        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
156        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
157        assertEquals(0x2, request[1]);
158        assertEquals(VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG,
159                request[2]);
160        assertEquals(VehicleAudioContextFlag.RADIO_FLAG |
161                VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, request[3]);
162        mAudioFocusPropertyHandler.sendAudioFocusState(
163                VehicleAudioFocusState.STATE_GAIN,
164                request[1],
165                VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG);
166        // system sound stop
167        notifyStreamState(1, false);
168        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
169        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
170        assertEquals(0, request[1]);
171        assertEquals(VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG,
172                request[2]);
173        assertEquals(VehicleAudioContextFlag.RADIO_FLAG, request[3]);
174        mAudioFocusPropertyHandler.sendAudioFocusState(
175                VehicleAudioFocusState.STATE_GAIN,
176                0,
177                VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG);
178        // radio stop
179        mAudioManager.abandonAudioFocus(listenerRadio);
180        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
181        assertEquals(VehicleAudioFocusRequest.REQUEST_RELEASE, request[0]);
182        assertEquals(0, request[1]);
183        assertEquals(0, request[2]);
184        assertEquals(0, request[3]);
185        mAudioFocusPropertyHandler.sendAudioFocusState(
186                VehicleAudioFocusState.STATE_LOSS,
187                request[1],
188                VehicleAudioExtFocusFlag.NONE_FLAG);
189    }
190
191    public void testMusicSystemSound() throws Exception {
192        // music start
193        AudioFocusListener listenerMusic = new AudioFocusListener();
194        int res = doRequestFocus(mAudioManager, listenerMusic,
195                AudioManager.STREAM_MUSIC,
196                AudioManager.AUDIOFOCUS_GAIN);
197        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
198        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
199        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
200        assertEquals(0x1 << VehicleAudioStream.STREAM0, request[1]);
201        assertEquals(0, request[2]);
202        assertEquals(VehicleAudioContextFlag.MUSIC_FLAG, request[3]);
203        mAudioFocusPropertyHandler.sendAudioFocusState(
204                VehicleAudioFocusState.STATE_GAIN,
205                request[1],
206                VehicleAudioExtFocusFlag.NONE_FLAG);
207        // system sound start
208        notifyStreamState(1, true);
209        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
210        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
211        assertEquals(0x1 | 0x2, request[1]);
212        assertEquals(0, request[2]);
213        assertEquals(VehicleAudioContextFlag.MUSIC_FLAG |
214                VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, request[3]);
215        mAudioFocusPropertyHandler.sendAudioFocusState(
216                VehicleAudioFocusState.STATE_GAIN,
217                request[1],
218                VehicleAudioExtFocusFlag.NONE_FLAG);
219        // system sound stop
220        notifyStreamState(1, false);
221        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
222        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN, request[0]);
223        assertEquals(0x1 << VehicleAudioStream.STREAM0, request[1]);
224        assertEquals(0, request[2]);
225        assertEquals(VehicleAudioContextFlag.MUSIC_FLAG, request[3]);
226        mAudioFocusPropertyHandler.sendAudioFocusState(
227                VehicleAudioFocusState.STATE_GAIN,
228                request[1],
229                VehicleAudioExtFocusFlag.NONE_FLAG);
230        // music stop
231        mAudioManager.abandonAudioFocus(listenerMusic);
232        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
233        assertEquals(VehicleAudioFocusRequest.REQUEST_RELEASE, request[0]);
234        assertEquals(0, request[1]);
235        assertEquals(0, request[2]);
236        assertEquals(0, request[3]);
237        mAudioFocusPropertyHandler.sendAudioFocusState(
238                VehicleAudioFocusState.STATE_LOSS,
239                request[1],
240                VehicleAudioExtFocusFlag.NONE_FLAG);
241    }
242
243    public void testNavigationSystemSound() throws Exception {
244        // nav guidance start
245        AudioFocusListener listenerNav = new AudioFocusListener();
246        AudioAttributes navAttrib = (new AudioAttributes.Builder()).
247                setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).
248                setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
249                build();
250        int res = doRequestFocus(mAudioManager, listenerNav, navAttrib,
251                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
252        assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
253        int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
254        assertEquals(VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK,
255                request[0]);
256        assertEquals(0x2, request[1]);
257        assertEquals(0, request[2]);
258        assertEquals(VehicleAudioContextFlag.NAVIGATION_FLAG, request[3]);
259        mAudioFocusPropertyHandler.sendAudioFocusState(
260                VehicleAudioFocusState.STATE_GAIN_TRANSIENT,
261                request[1],
262                VehicleAudioExtFocusFlag.NONE_FLAG);
263        // system sound start
264        notifyStreamState(1, true);
265        // cannot distinguish this case from nav only. so no focus change.
266        mAudioFocusPropertyHandler.assertNoFocusRequest(1000);
267        // cannot distinguish this case from nav only. so no focus change.
268        notifyStreamState(1, false);
269        mAudioFocusPropertyHandler.assertNoFocusRequest(1000);
270        // nav guidance stop
271        mAudioManager.abandonAudioFocus(listenerNav);
272        request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS);
273        assertEquals(VehicleAudioFocusRequest.REQUEST_RELEASE, request[0]);
274        assertEquals(0, request[1]);
275        assertEquals(0, request[2]);
276        assertEquals(0, request[3]);
277        mAudioFocusPropertyHandler.sendAudioFocusState(
278                VehicleAudioFocusState.STATE_LOSS,
279                request[1],
280                VehicleAudioExtFocusFlag.NONE_FLAG);
281    }
282
283    private static class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
284        private final Semaphore mFocusChangeWait = new Semaphore(0);
285        private int mLastFocusChange;
286
287        // TODO: not used?
288        public int waitAndGetFocusChange(long timeoutMs) throws Exception {
289            if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
290                fail("timeout waiting for focus change");
291            }
292            return mLastFocusChange;
293        }
294
295        public void waitForFocus(long timeoutMs, int expectedFocus) throws Exception {
296            while (mLastFocusChange != expectedFocus) {
297                if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
298                    fail("timeout waiting for focus change");
299                }
300            }
301        }
302
303        @Override
304        public void onAudioFocusChange(int focusChange) {
305            mLastFocusChange = focusChange;
306            mFocusChangeWait.release();
307        }
308    }
309
310    private static class FocusPropertyHandler implements VehicleHalPropertyHandler {
311
312        private int mState = VehicleAudioFocusState.STATE_LOSS;
313        private int mStreams = 0;
314        private int mExtFocus = 0;
315        private int mRequest;
316        private int mRequestedStreams;
317        private int mRequestedExtFocus;
318        private int mRequestedAudioContexts;
319        private final MockedCarTestBase mCarTest;
320
321        private final Semaphore mSetWaitSemaphore = new Semaphore(0);
322
323        FocusPropertyHandler(MockedCarTestBase carTest) {
324            mCarTest = carTest;
325        }
326
327        void sendAudioFocusState(int state, int streams, int extFocus) {
328            synchronized (this) {
329                mState = state;
330                mStreams = streams;
331                mExtFocus = extFocus;
332            }
333            mCarTest.getMockedVehicleHal().injectEvent(
334                    VehiclePropValueBuilder.newBuilder(AUDIO_FOCUS)
335                            .setTimestamp(SystemClock.elapsedRealtimeNanos())
336                            .addIntValue(state, streams, extFocus, 0)
337                            .build());
338        }
339
340        int[] waitForAudioFocusRequest(long timeoutMs) throws Exception {
341            if (!mSetWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
342                fail("timeout");
343            }
344            synchronized (this) {
345                return new int[] { mRequest, mRequestedStreams, mRequestedExtFocus,
346                        mRequestedAudioContexts };
347            }
348        }
349
350        void assertNoFocusRequest(long timeoutMs) throws Exception {
351            if (mSetWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
352                fail("should not get focus request");
353            }
354        }
355
356        @Override
357        public void onPropertySet(VehiclePropValue value) {
358            assertEquals(AUDIO_FOCUS, value.prop);
359            ArrayList<Integer> v = value.value.int32Values;
360            synchronized (this) {
361                mRequest = v.get(VehicleAudioFocusIndex.FOCUS);
362                mRequestedStreams = v.get(VehicleAudioFocusIndex.STREAMS);
363                mRequestedExtFocus = v.get(VehicleAudioFocusIndex.EXTERNAL_FOCUS_STATE);
364                mRequestedAudioContexts = v.get(VehicleAudioFocusIndex.AUDIO_CONTEXTS);
365            }
366            mSetWaitSemaphore.release();
367        }
368
369        @Override
370        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
371            assertEquals(VehicleProperty.AUDIO_FOCUS, value.prop);
372            int state, streams, extFocus;
373            synchronized (this) {
374                state = mState;
375                streams = mStreams;
376                extFocus = mExtFocus;
377            }
378            return VehiclePropValueBuilder.newBuilder(AUDIO_FOCUS)
379                    .setTimestamp(SystemClock.elapsedRealtimeNanos())
380                    .addIntValue(state, streams, extFocus, 0)
381                    .build();
382        }
383
384        @Override
385        public void onPropertySubscribe(int property, int zones, float sampleRate) {
386            assertEquals(VehicleProperty.AUDIO_FOCUS, property);
387        }
388
389        @Override
390        public void onPropertyUnsubscribe(int property) {
391            assertEquals(VehicleProperty.AUDIO_FOCUS, property);
392        }
393    }
394}
395