• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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  */
16 
17 package android.car.cts;
18 
19 import static android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME;
20 import static android.car.media.CarAudioManager.AUDIO_FEATURE_AUDIO_MIRRORING;
21 import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
22 import static android.car.media.CarAudioManager.AUDIO_FEATURE_OEM_AUDIO_SERVICE;
23 import static android.car.media.CarAudioManager.AUDIO_FEATURE_PERSIST_FADE_BALANCE_VALUES;
24 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_EVENTS;
25 import static android.car.media.CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING;
26 import static android.car.media.CarAudioManager.AUDIO_MIRROR_CAN_ENABLE;
27 import static android.car.media.CarAudioManager.INVALID_AUDIO_ZONE;
28 import static android.car.media.CarAudioManager.INVALID_REQUEST_ID;
29 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
30 import static android.car.test.mocks.JavaMockitoHelper.await;
31 import static android.media.AudioAttributes.USAGE_MEDIA;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 import static com.google.common.truth.Truth.assertWithMessage;
35 
36 import static org.junit.Assert.assertThrows;
37 import static org.junit.Assume.assumeFalse;
38 import static org.junit.Assume.assumeTrue;
39 
40 import android.app.UiAutomation;
41 import android.car.Car;
42 import android.car.CarOccupantZoneManager;
43 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
44 import android.car.PlatformVersion;
45 import android.car.cts.utils.ProtoDumpUtils;
46 import android.car.feature.Flags;
47 import android.car.media.AudioZonesMirrorStatusCallback;
48 import android.car.media.CarAudioManager;
49 import android.car.media.CarAudioZoneConfigInfo;
50 import android.car.media.CarVolumeGroupInfo;
51 import android.car.media.MediaAudioRequestStatusCallback;
52 import android.car.media.PrimaryZoneMediaAudioRequestCallback;
53 import android.car.media.SwitchAudioZoneConfigCallback;
54 import android.car.test.PermissionsCheckerRule;
55 import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
56 import android.car.test.util.CarAudioManagerTestUtils;
57 import android.media.AudioDeviceAttributes;
58 import android.media.AudioDeviceInfo;
59 import android.os.SystemClock;
60 import android.platform.test.annotations.AppModeFull;
61 import android.platform.test.annotations.RequiresFlagsDisabled;
62 import android.platform.test.annotations.RequiresFlagsEnabled;
63 import android.util.Log;
64 import android.util.SparseArray;
65 import android.view.KeyEvent;
66 
67 import androidx.test.ext.junit.runners.AndroidJUnit4;
68 import androidx.test.platform.app.InstrumentationRegistry;
69 
70 import com.android.car.audio.CarAudioDumpProto;
71 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneConfigProto;
72 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneProto;
73 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto;
74 import com.android.compatibility.common.util.ApiTest;
75 import com.android.compatibility.common.util.ShellUtils;
76 
77 import org.junit.After;
78 import org.junit.Before;
79 import org.junit.Rule;
80 import org.junit.Test;
81 import org.junit.runner.RunWith;
82 
83 import java.util.ArrayList;
84 import java.util.List;
85 import java.util.concurrent.CountDownLatch;
86 import java.util.concurrent.Executor;
87 import java.util.concurrent.Executors;
88 import java.util.regex.Matcher;
89 import java.util.regex.Pattern;
90 import java.util.stream.Collectors;
91 
92 @RunWith(AndroidJUnit4.class)
93 @AppModeFull(reason = "Instant Apps cannot get car related permissions")
94 public final class CarAudioManagerTest extends AbstractCarTestCase {
95 
96     private static final String TAG = CarAudioManagerTest.class.getSimpleName();
97 
98     private static final long WAIT_TIMEOUT_MS = 5_000;
99 
100     private static final Pattern ZONE_PATTERN = Pattern.compile(
101             "CarAudioZone\\(.*:(\\d?)\\) isPrimary\\? (.*?)\n.*Current Config Id: (\\d?)");
102     private static final Pattern VOLUME_GROUP_PATTERN = Pattern.compile(
103             "CarVolumeGroup\\((\\d?)\\)\n.*Name\\((.*?)\\)\n.*Zone Id\\((\\d?)\\)\n"
104                     + ".*Configuration Id\\((\\d?)\\)");
105     private static final Pattern ZONE_CONFIG_PATTERN = Pattern.compile(
106             "CarAudioZoneConfig\\((.*?):(\\d?)\\) of zone (\\d?) isDefault\\? (.*?)");
107     private static final Pattern PRIMARY_ZONE_MEDIA_REQUEST_APPROVERS_PATTERN =
108             Pattern.compile("Media request callbacks\\[(\\d+)\\]:");
109 
110     private static final int USAGE_INVALID = -1;
111     private static final int VOLUME_FLAGS = 0;
112     private static final int INVALID_VOLUME_GROUP_ID = -1;
113     private static final int LEGACY_ZONE_ID = 0;
114     private static final int LEGACY_VOLUME_GROUP_ID = 0;
115     private static final int LEGACY_GROUP_VOLUME_COUNT = 3;
116 
117     @Rule
118     public final PermissionsCheckerRule mPermissionsCheckerRule = new PermissionsCheckerRule();
119 
120     private static final UiAutomation UI_AUTOMATION =
121             InstrumentationRegistry.getInstrumentation().getUiAutomation();
122 
123     private CarAudioManager mCarAudioManager;
124     private CarAudioManagerTestUtils.SyncCarVolumeCallback mCallback;
125     private int mZoneId = LEGACY_ZONE_ID;
126     private int mConfigId = -1;
127     private int mVolumeGroupId = -1;
128     private int mVolumeGroupCount = LEGACY_GROUP_VOLUME_COUNT;
129     private CarOccupantZoneManager mCarOccupantZoneManager;
130     private TestPrimaryZoneMediaAudioRequestStatusCallback mRequestCallback;
131     private long mMediaRequestId = INVALID_REQUEST_ID;
132     private String mCarAudioServiceDump;
133     private CarAudioDumpProto mCarAudioServiceProtoDump;
134     private TestAudioZonesMirrorStatusCallback mAudioZonesMirrorCallback;
135     private long mMirrorRequestId = INVALID_REQUEST_ID;
136     private CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback mEventCallback;
137 
138     @Before
setUp()139     public void setUp() throws Exception {
140         mCarAudioManager = getCar().getCarManager(CarAudioManager.class);
141         mCarOccupantZoneManager = getCar().getCarManager(CarOccupantZoneManager.class);
142         if (Flags.carDumpToProto()) {
143             mCarAudioServiceProtoDump = CarAudioDumpProto.parseFrom(
144                     ProtoDumpUtils.executeProtoDumpShellCommand("CarAudioService"));
145         } else {
146             mCarAudioServiceDump = ShellUtils.runShellCommand(
147                     "dumpsys car_service --services CarAudioService");
148         }
149     }
150 
151     @After
cleanUp()152     public void cleanUp() {
153         if (mCallback != null) {
154             // Unregistering the last callback requires PERMISSION_CAR_CONTROL_AUDIO_VOLUME
155             runWithCarControlAudioVolumePermission(
156                     () -> mCarAudioManager.unregisterCarVolumeCallback(mCallback));
157         }
158 
159         if (mMediaRequestId != INVALID_REQUEST_ID) {
160             Log.w(TAG, "Cancelling media request " +  mMediaRequestId);
161             mCarAudioManager.cancelMediaAudioOnPrimaryZone(mMediaRequestId);
162         }
163 
164         if (mRequestCallback != null) {
165             Log.w(TAG, "Releasing media request callback");
166             mCarAudioManager.clearPrimaryZoneMediaAudioRequestCallback();
167         }
168 
169         if (mAudioZonesMirrorCallback != null) {
170             Log.i(TAG, "Releasing audio mirror request callback");
171             mCarAudioManager.clearAudioZonesMirrorStatusCallback();
172         }
173 
174         if (mMirrorRequestId != INVALID_REQUEST_ID) {
175             Log.i(TAG, "Disabling audio mirror for request: " + mMirrorRequestId);
176             mCarAudioManager.disableAudioMirror(mMirrorRequestId);
177         }
178 
179         if (mEventCallback != null && Car.getPlatformVersion().isAtLeast(
180                 PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0)) {
181             runWithCarControlAudioVolumePermission(
182                     () -> mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback));
183         }
184     }
185 
186     @Test
187     @ApiTest(apis = {"android.car.media.CarAudioManager#isAudioFeatureEnabled"})
isAudioFeatureEnabled_withVolumeGroupMuteFeature_succeeds()188     public void isAudioFeatureEnabled_withVolumeGroupMuteFeature_succeeds() {
189         boolean volumeGroupMutingEnabled = mCarAudioManager.isAudioFeatureEnabled(
190                         AUDIO_FEATURE_VOLUME_GROUP_MUTING);
191 
192         assertThat(volumeGroupMutingEnabled).isAnyOf(true, false);
193     }
194 
195     @Test
196     @ApiTest(apis = {"android.car.media.CarAudioManager#isAudioFeatureEnabled"})
isAudioFeatureEnabled_withDynamicRoutingFeature_succeeds()197     public void isAudioFeatureEnabled_withDynamicRoutingFeature_succeeds() {
198         boolean dynamicRoutingEnabled = mCarAudioManager.isAudioFeatureEnabled(
199                         AUDIO_FEATURE_DYNAMIC_ROUTING);
200 
201         assertThat(dynamicRoutingEnabled).isAnyOf(true, false);
202     }
203 
204     @Test
205     @ApiTest(apis = {"android.car.media.CarAudioManager#isAudioFeatureEnabled"})
isAudioFeatureEnabled_withVolumeGroupEventsFeature_succeeds()206     public void isAudioFeatureEnabled_withVolumeGroupEventsFeature_succeeds() {
207         boolean volumeGroupEventsEnabled = mCarAudioManager.isAudioFeatureEnabled(
208                 AUDIO_FEATURE_VOLUME_GROUP_EVENTS);
209 
210         assertWithMessage("Car volume group events feature").that(volumeGroupEventsEnabled)
211                 .isAnyOf(true, false);
212     }
213 
214     @Test
215     @ApiTest(apis = {"android.car.media.CarAudioManager#isAudioFeatureEnabled"})
isAudioFeatureEnabled_withNonAudioFeature_fails()216     public void isAudioFeatureEnabled_withNonAudioFeature_fails() {
217         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
218                 () -> mCarAudioManager.isAudioFeatureEnabled(-1));
219 
220         assertThat(exception).hasMessageThat().contains("Unknown Audio Feature");
221     }
222 
223     @Test
224     @ApiTest(apis = {"android.car.media.CarAudioManager#isAudioFeatureEnabled(int)",
225             "android.car.media.CarAudioManager#AUDIO_FEATURE_AUDIO_MIRRORING"})
isAudioFeatureEnabled_withAudioMirrorFeature_succeeds()226     public void isAudioFeatureEnabled_withAudioMirrorFeature_succeeds() {
227         boolean audioMirroringEnabled = mCarAudioManager.isAudioFeatureEnabled(
228                 AUDIO_FEATURE_AUDIO_MIRRORING);
229 
230         assertThat(audioMirroringEnabled).isAnyOf(true, false);
231     }
232 
233     @Test
234     @ApiTest(
235             apis = {
236                 "android.car.media.CarAudioManager#isAudioFeatureEnabled(int)",
237                 "android.car.media.CarAudioManager#AUDIO_FEATURE_PERSIST_FADE_BALANCE_VALUES"
238             })
239     @RequiresFlagsEnabled(Flags.FLAG_AUDIO_FADE_BALANCE_GETTER_APIS)
isAudioFeatureEnabled_withPersistFadeBalanceLevelsFeature_succeeds()240     public void isAudioFeatureEnabled_withPersistFadeBalanceLevelsFeature_succeeds() {
241         assumeDynamicRoutingIsEnabled();
242 
243         boolean isPersisted =
244                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_PERSIST_FADE_BALANCE_VALUES);
245 
246         assertWithMessage("Persist fade balance values feature")
247                 .that(isPersisted)
248                 .isAnyOf(true, false);
249     }
250 
251     @Test
252     @ApiTest(
253             apis = {
254                 "android.car.media.CarAudioManager#isAudioFeatureEnabled(int)",
255                 "android.car.media.CarAudioManager#AUDIO_FEATURE_PERSIST_FADE_BALANCE_VALUES"
256             })
257     @RequiresFlagsEnabled(Flags.FLAG_AUDIO_FADE_BALANCE_GETTER_APIS)
isAudioFeatureEnabled_forPersistFadeBalance_whenDisabledDynamicRouting()258     public void isAudioFeatureEnabled_forPersistFadeBalance_whenDisabledDynamicRouting() {
259         assumeDynamicRoutingIsDisabled();
260 
261         boolean isPersisted =
262                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_PERSIST_FADE_BALANCE_VALUES);
263 
264         assertWithMessage("Persist fade balance values feature when dynamic routing is disabled")
265                 .that(isPersisted)
266                 .isFalse();
267     }
268 
269     @Test
270     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
271     @ApiTest(apis = {"android.car.media.CarAudioManager#canEnableAudioMirror"})
canEnableAudioMirror()272     public void canEnableAudioMirror() {
273         assumeAudioMirrorEnabled();
274 
275         int status = mCarAudioManager.canEnableAudioMirror();
276 
277         assertWithMessage("Can enable audio mirror status with feature supported")
278                 .that(status).isEqualTo(AUDIO_MIRROR_CAN_ENABLE);
279     }
280 
281     @Test
282     @ApiTest(apis = {
283             "android.car.media.CarAudioManager#registerCarVolumeCallback"})
registerCarVolumeCallback_nullCallback_throwsNPE()284     public void registerCarVolumeCallback_nullCallback_throwsNPE() {
285         assertThrows(NullPointerException.class,
286                 () -> mCarAudioManager.registerCarVolumeCallback(null));
287     }
288 
289     @Test
290     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
291     @ApiTest(apis = {
292             "android.car.media.CarAudioManager#registerCarVolumeCallback"})
registerCarVolumeCallback_onGroupVolumeChanged()293     public void registerCarVolumeCallback_onGroupVolumeChanged() throws Exception {
294         assumeOemServiceIsDisabled();
295         assumeDynamicRoutingIsEnabled();
296         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
297 
298         mCarAudioManager.registerCarVolumeCallback(mCallback);
299 
300         injectVolumeDownKeyEvent();
301         assertWithMessage("CarVolumeCallback#onGroupVolumeChanged should be called")
302                 .that(mCallback.receivedGroupVolumeChanged())
303                 .isTrue();
304     }
305 
306     @Test
307     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
308     @ApiTest(apis = {
309             "android.car.media.CarAudioManager#registerCarVolumeCallback"})
registerCarVolumeCallback_onMasterMuteChanged()310     public void registerCarVolumeCallback_onMasterMuteChanged() throws Exception {
311         assumeDynamicRoutingIsEnabled();
312         assumeVolumeGroupMutingIsDisabled();
313         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
314 
315         mCarAudioManager.registerCarVolumeCallback(mCallback);
316 
317         injectVolumeMuteKeyEvent();
318         try {
319             assertWithMessage("CarVolumeCallback#onMasterMuteChanged should be called")
320                 .that(mCallback.receivedMasterMuteChanged())
321                 .isTrue();
322         } finally {
323             injectVolumeMuteKeyEvent();
324         }
325     }
326 
327     @Test
328     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
329     @ApiTest(apis = {
330             "android.car.media.CarAudioManager#registerCarVolumeCallback"})
registerCarVolumeCallback_onGroupMuteChanged()331     public void registerCarVolumeCallback_onGroupMuteChanged() throws Exception {
332         assumeDynamicRoutingIsEnabled();
333         assumeVolumeGroupMutingIsEnabled();
334         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
335 
336         readFirstZoneAndVolumeGroup();
337         mCarAudioManager.registerCarVolumeCallback(mCallback);
338         setVolumeGroupMute(mZoneId, mVolumeGroupId, /* mute= */ true);
339         setVolumeGroupMute(mZoneId, mVolumeGroupId, /* mute= */ false);
340 
341         assertWithMessage("CarVolumeCallback#onGroupMuteChanged should be called")
342             .that(mCallback.receivedGroupMuteChanged()).isTrue();
343         assertWithMessage("CarVolumeCallback#onGroupMuteChanged wrong zoneId")
344             .that(mCallback.zoneId).isEqualTo(mZoneId);
345         assertWithMessage("CarVolumeCallback#onGroupMuteChanged wrong groupId")
346             .that(mCallback.groupId).isEqualTo(mVolumeGroupId);
347     }
348 
349     @Test
350     @ApiTest(apis = {
351             "android.car.media.CarAudioManager#unregisterCarVolumeCallback"})
unregisterCarVolumeCallback_nullCallback_throws()352     public void unregisterCarVolumeCallback_nullCallback_throws() {
353         assertThrows(NullPointerException.class,
354                 () -> mCarAudioManager.unregisterCarVolumeCallback(null));
355     }
356 
357     @Test
358     @ApiTest(apis = {
359             "android.car.media.CarAudioManager#unregisterCarVolumeCallback"})
unregisterCarVolumeCallback_unregisteredCallback_doesNotReceiveCallback()360     public void unregisterCarVolumeCallback_unregisteredCallback_doesNotReceiveCallback()
361             throws Exception {
362         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
363 
364         mCarAudioManager.unregisterCarVolumeCallback(mCallback);
365 
366         assertWithMessage("CarVolumeCallback#onGroupVolumeChanged should not be called")
367                 .that(mCallback.receivedGroupVolumeChanged())
368                 .isFalse();
369     }
370 
371     @Test
372     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
373     @ApiTest(apis = {
374             "android.car.media.CarAudioManager#unregisterCarVolumeCallback"})
unregisterCarVolumeCallback_noLongerReceivesCallback()375     public void unregisterCarVolumeCallback_noLongerReceivesCallback() throws Exception {
376         assumeDynamicRoutingIsEnabled();
377         CarAudioManagerTestUtils.SyncCarVolumeCallback callback =
378                 new CarAudioManagerTestUtils.SyncCarVolumeCallback();
379         mCarAudioManager.registerCarVolumeCallback(callback);
380         mCarAudioManager.unregisterCarVolumeCallback(callback);
381 
382         injectVolumeDownKeyEvent();
383 
384         assertWithMessage("CarVolumeCallback#onGroupVolumeChanged should not be called")
385                 .that(callback.receivedGroupVolumeChanged())
386                 .isFalse();
387     }
388 
389     @Test
390     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
391     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupInfo"})
392     @RequiresFlagsDisabled(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME)
getVolumeGroupInfo_withMinMaxActivationVolumeFlagDisabled()393     public void getVolumeGroupInfo_withMinMaxActivationVolumeFlagDisabled() {
394         assumeDynamicRoutingIsEnabled();
395         int groupCount = mCarAudioManager.getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
396 
397         for (int index = 0; index < groupCount; index++) {
398             int minIndex = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, index);
399             int maxIndex = mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, index);
400             CarVolumeGroupInfo info =
401                     mCarAudioManager.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, index);
402             expectWithMessage("Car volume group id for info %s and group %s", info, index)
403                     .that(info.getId()).isEqualTo(index);
404             expectWithMessage("Car volume group info zone for info %s and group %s",
405                     info, index).that(info.getZoneId()).isEqualTo(PRIMARY_AUDIO_ZONE);
406             expectWithMessage("Car volume group info max index for info %s and group %s",
407                     info, index).that(info.getMaxVolumeGainIndex()).isEqualTo(maxIndex);
408             expectWithMessage("Car volume group info min index for info %s and group %s",
409                     info, index).that(info.getMinVolumeGainIndex()).isEqualTo(minIndex);
410         }
411     }
412 
413     @Test
414     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
415     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupInfo",
416             "android.car.media.CarVolumeGroupInfo#getMaxActivationVolumeGainIndex",
417             "android.car.media.CarVolumeGroupInfo#getMinActivationVolumeGainIndex"})
418     @RequiresFlagsEnabled(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME)
getVolumeGroupInfo_withMinMaxActivationVolumeFlagEnabled()419     public void getVolumeGroupInfo_withMinMaxActivationVolumeFlagEnabled() {
420         assumeDynamicRoutingIsEnabled();
421         int groupCount = mCarAudioManager.getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
422 
423         for (int index = 0; index < groupCount; index++) {
424             int minIndex = mCarAudioManager.getGroupMinVolume(PRIMARY_AUDIO_ZONE, index);
425             int maxIndex = mCarAudioManager.getGroupMaxVolume(PRIMARY_AUDIO_ZONE, index);
426             CarVolumeGroupInfo info =
427                     mCarAudioManager.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, index);
428             int minActivationIndex = info.getMinActivationVolumeGainIndex();
429             int maxActivationIndex = info.getMaxActivationVolumeGainIndex();
430 
431             expectWithMessage("Car volume group id for info %s and group %s", info, index)
432                     .that(info.getId()).isEqualTo(index);
433             expectWithMessage("Car volume group info zone for info %s and group %s",
434                     info, index).that(info.getZoneId()).isEqualTo(PRIMARY_AUDIO_ZONE);
435             expectWithMessage("Car volume group info max index for info %s and group %s",
436                     info, index).that(info.getMaxVolumeGainIndex()).isEqualTo(maxIndex);
437             expectWithMessage("Car volume group info min index for info %s and group %s",
438                     info, index).that(info.getMinVolumeGainIndex()).isEqualTo(minIndex);
439             expectWithMessage("Car volume group info min activation volume for info %s and"
440                     + " group %s", info, index).that(minActivationIndex).isAtLeast(minIndex);
441             expectWithMessage("Car volume group info max activation volume for info %s and"
442                     + " group %s", info, index).that(maxActivationIndex).isAtMost(maxIndex);
443             expectWithMessage("Max activation volume and min activation volume for info %s"
444                     + " and group %s", info, index).that(maxActivationIndex)
445                     .isGreaterThan(minActivationIndex);
446         }
447     }
448 
449     @Test
450     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
451     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupInfosForZone"})
getVolumeGroupInfosForZone()452     public void getVolumeGroupInfosForZone() {
453         assumeDynamicRoutingIsEnabled();
454         int groupCount = mCarAudioManager.getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
455 
456         List<CarVolumeGroupInfo> infos =
457                 mCarAudioManager.getVolumeGroupInfosForZone(PRIMARY_AUDIO_ZONE);
458 
459         expectWithMessage("Car volume group infos for primary zone")
460                 .that(infos).hasSize(groupCount);
461         for (int index = 0; index < groupCount; index++) {
462             CarVolumeGroupInfo info =
463                     mCarAudioManager.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, index);
464             expectWithMessage("Car volume group infos for info %s and group %s", info, index)
465                     .that(infos).contains(info);
466         }
467     }
468 
469     @Test
470     @EnsureHasPermission({Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME,
471             Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS})
472     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupInfosForZone",
473             "android.car.media.CarVolumeGroupInfo#getAudioDeviceAttributes",
474             "android.car.media.CarVolumeGroupInfo#isDefault"})
475     @RequiresFlagsEnabled(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES)
getVolumeGroupInfosForZone_forPrimaryZone_withAudioDeviceAttributes()476     public void getVolumeGroupInfosForZone_forPrimaryZone_withAudioDeviceAttributes()
477             throws Exception {
478         assumeDynamicRoutingIsEnabled();
479         setUpDefaultCarAudioConfigurationForZone(PRIMARY_AUDIO_ZONE);
480 
481         List<CarVolumeGroupInfo> infos =
482                 mCarAudioManager.getVolumeGroupInfosForZone(PRIMARY_AUDIO_ZONE);
483 
484         expectWithMessage("Primary zone's car volume groups").that(infos).isNotEmpty();
485         for (int count = 0; count < infos.size(); count++) {
486             CarVolumeGroupInfo info = infos.get(count);
487             List<Integer> types = info.getAudioDeviceAttributes().stream()
488                     .map(AudioDeviceAttributes::getType).collect(Collectors.toList());
489             expectWithMessage("Primary zone's car volume group %s types", info.getId())
490                     .that(types).isNotEmpty();
491             for (int index = 0; index < types.size(); index++)  {
492                 expectWithMessage("Primary zone's car volume group device type "
493                     + " for group %s device index %s", info.getId(), index)
494                         .that(types.get(index)).isEqualTo(AudioDeviceInfo.TYPE_BUS);
495             }
496         }
497     }
498 
499     @Test
500     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
501     @ApiTest(apis = {"android.car.media.CarAudioManager"
502             + "#getAudioAttributesForVolumeGroup"})
getAudioAttributesForVolumeGroup()503     public void getAudioAttributesForVolumeGroup() {
504         assumeDynamicRoutingIsEnabled();
505         CarVolumeGroupInfo info =
506                 mCarAudioManager.getVolumeGroupInfo(PRIMARY_AUDIO_ZONE, /* groupId= */ 0);
507 
508         expectWithMessage("Car volume audio attributes")
509                 .that(mCarAudioManager.getAudioAttributesForVolumeGroup(info))
510                 .isNotEmpty();
511     }
512 
513     @Test
514     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
515     @ApiTest(apis = {"android.car.media.CarAudioManager#getGroupMaxVolume",
516             "android.car.media.CarAudioManager#getGroupMinVolume",
517             "android.car.media.CarAudioManager#getGroupVolume"})
getGroupVolume()518     public void getGroupVolume() {
519         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
520             assumePrimaryZone();
521         }
522         int maxIndex = mCarAudioManager.getGroupMaxVolume(mVolumeGroupId);
523         int minIndex = mCarAudioManager.getGroupMinVolume(mVolumeGroupId);
524 
525         int currentIndex = mCarAudioManager.getGroupVolume(mVolumeGroupId);
526 
527         assertWithMessage("Current maximum volume for primary zone")
528                 .that(currentIndex).isAtMost(maxIndex);
529         assertWithMessage("Current minimum volume for primary zone")
530                 .that(currentIndex).isAtLeast(minIndex);
531     }
532 
533     @Test
534     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
535     @ApiTest(apis = {"android.car.media.CarAudioManager#getGroupMaxVolume",
536             "android.car.media.CarAudioManager#getGroupMinVolume",
537             "android.car.media.CarAudioManager#getGroupVolume"})
getGroupVolume_withZoneId()538     public void getGroupVolume_withZoneId() {
539         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
540             readFirstZoneAndVolumeGroup();
541         }
542         int maxIndex = mCarAudioManager.getGroupMaxVolume(mZoneId, mVolumeGroupId);
543         int minIndex = mCarAudioManager.getGroupMinVolume(mZoneId, mVolumeGroupId);
544 
545         int currentIndex = mCarAudioManager.getGroupVolume(mZoneId, mVolumeGroupId);
546 
547         assertWithMessage("Current maximum volume for zone %s volume group %s", mZoneId,
548                 mVolumeGroupId).that(currentIndex).isAtMost(maxIndex);
549         assertWithMessage("Current minimum volume for zone %s volume group %s", mZoneId,
550                 mVolumeGroupId).that(currentIndex).isAtLeast(minIndex);
551     }
552 
553     @Test
554     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
555     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_toMax_succeeds()556     public void setGroupVolume_toMax_succeeds() {
557         assumeDynamicRoutingIsEnabled();
558         assumePrimaryZone();
559         int prevIndex = mCarAudioManager.getGroupVolume(mZoneId, mVolumeGroupId);
560         int maxIndex = mCarAudioManager.getGroupMaxVolume(mVolumeGroupId);
561 
562         mCarAudioManager.setGroupVolume(mVolumeGroupId, maxIndex, VOLUME_FLAGS);
563 
564         try {
565             assertWithMessage("Current volume after setting to max for primary zone volume group"
566                     + " %s", mVolumeGroupId).that(mCarAudioManager.getGroupVolume(mVolumeGroupId))
567                     .isEqualTo(maxIndex);
568         } finally {
569             mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, prevIndex, VOLUME_FLAGS);
570         }
571     }
572 
573     @Test
574     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
575     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_toMin_succeeds()576     public void setGroupVolume_toMin_succeeds() {
577         assumeDynamicRoutingIsEnabled();
578         assumePrimaryZone();
579         int prevIndex = mCarAudioManager.getGroupVolume(mZoneId, mVolumeGroupId);
580         int minIndex = mCarAudioManager.getGroupMinVolume(mVolumeGroupId);
581 
582         mCarAudioManager.setGroupVolume(mVolumeGroupId, minIndex, VOLUME_FLAGS);
583 
584         try {
585             assertWithMessage("Current volume after setting to min for primary zone volume group"
586                     + " %s", mVolumeGroupId).that(mCarAudioManager.getGroupVolume(mVolumeGroupId))
587                     .isEqualTo(minIndex);
588         } finally {
589             mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, prevIndex, VOLUME_FLAGS);
590         }
591     }
592 
593     @Test
594     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
595     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_withZone_toMax_succeeds()596     public void setGroupVolume_withZone_toMax_succeeds() {
597         assumeDynamicRoutingIsEnabled();
598         readFirstZoneAndVolumeGroup();
599         int prevIndex = mCarAudioManager.getGroupVolume(mZoneId, mVolumeGroupId);
600         int maxIndex = mCarAudioManager.getGroupMaxVolume(mZoneId, mVolumeGroupId);
601 
602         mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, maxIndex, VOLUME_FLAGS);
603 
604         try {
605             assertWithMessage("Current volume after setting to max for zone %s volume group %s",
606                     mZoneId, mVolumeGroupId).that(mCarAudioManager.getGroupVolume(mZoneId,
607                     mVolumeGroupId)).isEqualTo(maxIndex);
608         } finally {
609             mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, prevIndex, VOLUME_FLAGS);
610         }
611     }
612 
613     @Test
614     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
615     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_withZone_toMin_succeeds()616     public void setGroupVolume_withZone_toMin_succeeds() {
617         assumeDynamicRoutingIsEnabled();
618         readFirstZoneAndVolumeGroup();
619         int prevIndex = mCarAudioManager.getGroupVolume(mZoneId, mVolumeGroupId);
620         int minIndex = mCarAudioManager.getGroupMinVolume(mZoneId, mVolumeGroupId);
621 
622         mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, minIndex, VOLUME_FLAGS);
623 
624         try {
625             assertWithMessage("Current volume after setting to min for zone %s volume group %s",
626                     mZoneId, mVolumeGroupId).that(mCarAudioManager.getGroupVolume(mZoneId,
627                     mVolumeGroupId)).isEqualTo(minIndex);
628         } finally {
629             mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, prevIndex, VOLUME_FLAGS);
630         }
631     }
632 
633     @Test
634     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
635     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_aboveMax_throwsException()636     public void setGroupVolume_aboveMax_throwsException() {
637         assumeDynamicRoutingIsEnabled();
638         readFirstZoneAndVolumeGroup();
639         int maxIndex = mCarAudioManager.getGroupMaxVolume(mZoneId, mVolumeGroupId);
640 
641         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
642                 () -> mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, maxIndex + 1,
643                         VOLUME_FLAGS));
644 
645         assertWithMessage("Exception for setting volume above max")
646                 .that(exception).hasMessageThat().contains("Gain out of range");
647     }
648 
649     @Test
650     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
651     @ApiTest(apis = {"android.car.media.CarAudioManager#setGroupVolume"})
setGroupVolume_belowMin_throwsException()652     public void setGroupVolume_belowMin_throwsException() {
653         assumeDynamicRoutingIsEnabled();
654         readFirstZoneAndVolumeGroup();
655         int minIndex = mCarAudioManager.getGroupMinVolume(mZoneId, mVolumeGroupId);
656 
657         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
658                 () -> mCarAudioManager.setGroupVolume(mZoneId, mVolumeGroupId, minIndex - 1,
659                         VOLUME_FLAGS));
660 
661         assertWithMessage("Exception for setting volume below min")
662                 .that(exception).hasMessageThat().contains("Gain out of range");
663     }
664 
665     @Test
666     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
667     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupCount"})
getVolumeGroupCount()668     public void getVolumeGroupCount() {
669         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
670             assumePrimaryZone();
671         }
672 
673         assertWithMessage("Primary zone volume group count")
674                 .that(mCarAudioManager.getVolumeGroupCount()).isEqualTo(mVolumeGroupCount);
675     }
676 
677     @Test
678     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
679     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupCount"})
getVolumeGroupCount_withZoneIdAndDynamicRouting()680     public void getVolumeGroupCount_withZoneIdAndDynamicRouting() {
681         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
682             readFirstZoneAndVolumeGroup();
683         }
684 
685         assertWithMessage("Zone %s volume group count", mZoneId)
686                 .that(mCarAudioManager.getVolumeGroupCount(mZoneId)).isEqualTo(mVolumeGroupCount);
687     }
688 
689     @Test
690     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
691     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupCount"})
getVolumeGroupCount_withInvalidZoneId_throwsException()692     public void getVolumeGroupCount_withInvalidZoneId_throwsException() {
693         assumeDynamicRoutingIsEnabled();
694         assumePrimaryZone();
695 
696         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
697                 () -> mCarAudioManager.getVolumeGroupCount(INVALID_AUDIO_ZONE));
698 
699         assertWithMessage("Invalid zone volume group count exception").that(exception)
700                 .hasMessageThat().contains("Invalid audio zone Id");
701     }
702 
703     @Test
704     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
705     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupIdForUsage"})
getVolumeGroupIdForUsage_forInvalidUsage_returnsInvalidId()706     public void getVolumeGroupIdForUsage_forInvalidUsage_returnsInvalidId() {
707         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
708             assumePrimaryZone();
709         }
710 
711         int volumeGroupId = mCarAudioManager.getVolumeGroupIdForUsage(USAGE_INVALID);
712 
713         assertWithMessage("Volume group id for invalid usage").that(volumeGroupId)
714                 .isEqualTo(INVALID_VOLUME_GROUP_ID);
715     }
716 
717     @Test
718     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
719     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupIdForUsage"})
getVolumeGroupIdForUsage_withInvalidZoneId_throwsException()720     public void getVolumeGroupIdForUsage_withInvalidZoneId_throwsException() {
721         assumeDynamicRoutingIsEnabled();
722 
723         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
724                 () -> mCarAudioManager.getVolumeGroupIdForUsage(INVALID_AUDIO_ZONE, USAGE_MEDIA));
725 
726         assertWithMessage("Invalid zone volume group for media usage exception").that(exception)
727                 .hasMessageThat().contains("Invalid audio zone Id");
728     }
729 
730     @Test
731     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
732     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupIdForUsage"})
getVolumeGroupIdForUsage_returnsValidId()733     public void getVolumeGroupIdForUsage_returnsValidId() {
734         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
735             assumePrimaryZone();
736         }
737 
738         int volumeGroupId = mCarAudioManager.getVolumeGroupIdForUsage(USAGE_MEDIA);
739 
740         assertWithMessage("Valid volume group id in primary zone")
741                 .that(volumeGroupId).isNotEqualTo(INVALID_VOLUME_GROUP_ID);
742     }
743 
744     @Test
745     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
746     @ApiTest(apis = {"android.car.media.CarAudioManager#getVolumeGroupIdForUsage"})
getVolumeGroupIdForUsage_withZoneId_returnsValidId()747     public void getVolumeGroupIdForUsage_withZoneId_returnsValidId() {
748         assumeDynamicRoutingIsEnabled();
749         readFirstZoneAndVolumeGroup();
750 
751         int volumeGroupId = mCarAudioManager.getVolumeGroupIdForUsage(mZoneId, USAGE_MEDIA);
752 
753         assertWithMessage("Valid volume group id for media usage in zone %s", mZoneId)
754                 .that(volumeGroupId).isNotEqualTo(INVALID_VOLUME_GROUP_ID);
755     }
756 
757     @Test
758     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
759     @ApiTest(apis = {"android.car.media.CarAudioManager#getUsagesForVolumeGroupId"})
getUsagesForVolumeGroupId_returnsAtLeastOneUsage()760     public void getUsagesForVolumeGroupId_returnsAtLeastOneUsage() {
761         assumeDynamicRoutingIsEnabled();
762         assumePrimaryZone();
763 
764         int[] usages = mCarAudioManager.getUsagesForVolumeGroupId(mVolumeGroupId);
765 
766         assertVolumeGroupIdForUsages(PRIMARY_AUDIO_ZONE, mVolumeGroupId, usages,
767                 "with dynamic routing");
768     }
769 
770     @Test
771     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
772     @ApiTest(apis = {"android.car.media.CarAudioManager#getUsagesForVolumeGroupId"})
getUsagesForVolumeGroupId_withoutDynamicRouting_returnsAtLeastOneUsage()773     public void getUsagesForVolumeGroupId_withoutDynamicRouting_returnsAtLeastOneUsage() {
774         assumeDynamicRoutingIsDisabled();
775 
776         int[] usages = mCarAudioManager.getUsagesForVolumeGroupId(LEGACY_VOLUME_GROUP_ID);
777 
778         assertVolumeGroupIdForUsages(PRIMARY_AUDIO_ZONE, LEGACY_VOLUME_GROUP_ID,
779                 usages, "with dynamic routing disabled");
780     }
781 
782     @Test
783     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
784     @ApiTest(apis = {"android.car.media.CarAudioManager#getUsagesForVolumeGroupId"})
getUsagesForVolumeGroupId_withZoneId_returnsAtLeastOneUsage()785     public void getUsagesForVolumeGroupId_withZoneId_returnsAtLeastOneUsage() {
786         assumeDynamicRoutingIsEnabled();
787         readFirstZoneAndVolumeGroup();
788 
789         int[] usages = mCarAudioManager.getUsagesForVolumeGroupId(mVolumeGroupId);
790 
791         assertVolumeGroupIdForUsages(mZoneId, mVolumeGroupId, usages,
792                 "with id and dynamic routing");
793     }
794 
795     @Test
796     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
797     @ApiTest(apis = {"android.car.media.CarAudioManager#getUsagesForVolumeGroupId"})
getUsagesForVolumeGroupId_withZoneIdWithoutDynamicRouting_returnsAtLeastOneUsage()798     public void getUsagesForVolumeGroupId_withZoneIdWithoutDynamicRouting_returnsAtLeastOneUsage() {
799         assumeDynamicRoutingIsDisabled();
800 
801         int[] usages = mCarAudioManager.getUsagesForVolumeGroupId(LEGACY_VOLUME_GROUP_ID);
802 
803         assertVolumeGroupIdForUsages(LEGACY_ZONE_ID, LEGACY_VOLUME_GROUP_ID, usages,
804                 "with id and with dynamic routing disabled");
805     }
806 
807     @Test
808     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
809     @ApiTest(apis = {"android.car.media.CarAudioManager#getUsagesForVolumeGroupId"})
getUsagesForVolumeGroupId_withInvalidZone_throwsException()810     public void getUsagesForVolumeGroupId_withInvalidZone_throwsException() {
811         assumeDynamicRoutingIsEnabled();
812         assumePrimaryZone();
813 
814         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
815                 () -> mCarAudioManager.getUsagesForVolumeGroupId(INVALID_AUDIO_ZONE,
816                         mVolumeGroupId));
817 
818         assertWithMessage("Exception for getting usage for volume group in invalid zone")
819                 .that(exception).hasMessageThat().contains("Invalid audio zone Id");
820     }
821 
822     @Test
823     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
824     @ApiTest(apis = {"android.car.media.CarAudioManager#getOutputDeviceForUsage"})
getOutputDeviceForUsage_withoutDynamicRouting_throws()825     public void getOutputDeviceForUsage_withoutDynamicRouting_throws() {
826         assumeDynamicRoutingIsDisabled();
827 
828         IllegalStateException exception = assertThrows(IllegalStateException.class,
829                 () -> mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA));
830 
831         assertWithMessage("Exception for getting media output device with dynamic routing disabled")
832                 .that(exception).hasMessageThat().contains("Non legacy routing");
833     }
834 
835     @Test
836     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
837     @ApiTest(apis = {"android.car.media.CarAudioManager#getOutputDeviceForUsage"})
getOutputDeviceForUsage_withInvalidZoneId_throws()838     public void getOutputDeviceForUsage_withInvalidZoneId_throws() {
839         assumeDynamicRoutingIsEnabled();
840 
841         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
842                 () -> mCarAudioManager.getOutputDeviceForUsage(INVALID_AUDIO_ZONE, USAGE_MEDIA));
843 
844         assertWithMessage("Exception for getting invalid zone media output device")
845                 .that(exception).hasMessageThat().contains("Invalid audio zone Id");
846     }
847 
848     @Test
849     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
850     @ApiTest(apis = {"android.car.media.CarAudioManager#getOutputDeviceForUsage"})
getOutputDeviceForUsage_withInvalidUsage_throws()851     public void getOutputDeviceForUsage_withInvalidUsage_throws() {
852         assumeDynamicRoutingIsEnabled();
853 
854         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
855                 () -> mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE, USAGE_INVALID));
856 
857         assertWithMessage("Invalid usage output device exception")
858                 .that(exception).hasMessageThat().contains("Invalid audio attribute");
859     }
860 
861     @Test
862     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
863     @ApiTest(apis = {"android.car.media.CarAudioManager#getOutputDeviceForUsage"})
getOutputDeviceForUsage_succeeds()864     public void getOutputDeviceForUsage_succeeds() {
865         assumeDynamicRoutingIsEnabled();
866 
867         AudioDeviceInfo deviceInfo = mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE,
868                 USAGE_MEDIA);
869 
870         assertWithMessage("Device for media in primary zone")
871                 .that(deviceInfo).isNotNull();
872         assertWithMessage("Sink device for media in primary zone")
873                 .that(deviceInfo.isSink()).isTrue();
874     }
875 
876     @Test
877     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
878     @ApiTest(apis = {"android.car.media.CarAudioManager#getInputDevicesForZoneId"})
getInputDevicesForZoneId_withoutDynamicRouting_throws()879     public void getInputDevicesForZoneId_withoutDynamicRouting_throws() {
880         assumeDynamicRoutingIsDisabled();
881 
882         IllegalStateException exception = assertThrows(IllegalStateException.class,
883                 () -> mCarAudioManager.getInputDevicesForZoneId(PRIMARY_AUDIO_ZONE));
884 
885         assertWithMessage("Exception for getting input devices with dynamic routing disabled")
886                 .that(exception).hasMessageThat().contains("Non legacy routing");
887     }
888 
889     @Test
890     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
891     @ApiTest(apis = {"android.car.media.CarAudioManager#getInputDevicesForZoneId"})
getInputDevicesForZoneId_withInvalidZoneId_throws()892     public void getInputDevicesForZoneId_withInvalidZoneId_throws() {
893         assumeDynamicRoutingIsEnabled();
894 
895         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
896                 () -> mCarAudioManager.getInputDevicesForZoneId(INVALID_AUDIO_ZONE));
897 
898         assertWithMessage("Exception for getting input devices with invalid volume group id")
899                 .that(exception).hasMessageThat().contains("Invalid audio zone Id");
900     }
901 
902     @Test
903     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
904     @ApiTest(apis = {"android.car.media.CarAudioManager#getInputDevicesForZoneId"})
getInputDevicesForZoneId_succeeds()905     public void getInputDevicesForZoneId_succeeds() {
906         assumeDynamicRoutingIsEnabled();
907 
908         List<AudioDeviceInfo> audioDeviceInfos = mCarAudioManager.getInputDevicesForZoneId(
909                 PRIMARY_AUDIO_ZONE);
910 
911         assertWithMessage("Input devices for primary zone")
912                 .that(audioDeviceInfos).isNotNull();
913         for (int i = 0; i < audioDeviceInfos.size(); i++) {
914             assertWithMessage("Source device %s in primary zone", audioDeviceInfos.get(i)
915                     .getAddress()).that(audioDeviceInfos.get(i).isSource()).isTrue();
916         }
917     }
918 
919     @Test
920     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
921     @ApiTest(apis = {"android.car.media.CarAudioManager"
922             + "#isMediaAudioAllowedInPrimaryZone"})
isMediaAudioAllowedInPrimaryZone_byDefault()923     public void isMediaAudioAllowedInPrimaryZone_byDefault() {
924         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
925         OccupantZoneInfo info =
926                         mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
927 
928         assertWithMessage("Default media allowed status")
929                 .that(mCarAudioManager.isMediaAudioAllowedInPrimaryZone(info)).isFalse();
930     }
931 
932     @Test
933     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
934     @ApiTest(apis = {"android.car.media.CarAudioManager"
935             + "#isMediaAudioAllowedInPrimaryZone"})
isMediaAudioAllowedInPrimaryZone_afterAllowed()936     public void isMediaAudioAllowedInPrimaryZone_afterAllowed() throws Exception {
937         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
938         setupMediaAudioRequestCallback();
939         OccupantZoneInfo info =
940                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
941         requestToPlayMediaInPrimaryZone(info);
942         mRequestCallback.receivedMediaRequest();
943         mCarAudioManager.allowMediaAudioOnPrimaryZone(mMediaRequestId, /* allow= */ true);
944 
945         boolean approved = mCarAudioManager.isMediaAudioAllowedInPrimaryZone(info);
946 
947         assertWithMessage("Approved media allowed status")
948                 .that(approved).isTrue();
949     }
950 
951     @Test
952     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
953     @ApiTest(apis = {"android.car.media.CarAudioManager"
954             + "#isMediaAudioAllowedInPrimaryZone"})
isMediaAudioAllowedInPrimaryZone_afterRejected()955     public void isMediaAudioAllowedInPrimaryZone_afterRejected() throws Exception {
956         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
957         setupMediaAudioRequestCallback();
958         OccupantZoneInfo info =
959                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
960         requestToPlayMediaInPrimaryZone(info);
961         mRequestCallback.receivedMediaRequest();
962         mCarAudioManager.allowMediaAudioOnPrimaryZone(mMediaRequestId, /* allow= */ false);
963 
964         boolean approved = mCarAudioManager.isMediaAudioAllowedInPrimaryZone(info);
965 
966         assertWithMessage("Unapproved media allowed status")
967                 .that(approved).isFalse();
968     }
969 
970     @Test
971     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
972     @ApiTest(apis = {"android.car.media.CarAudioManager"
973             + "#setPrimaryZoneMediaAudioRequestCallback"})
setPrimaryZoneMediaAudioRequestCallback()974     public void setPrimaryZoneMediaAudioRequestCallback() {
975         assumePassengerWithValidAudioZone();
976         Executor executor = Executors.newFixedThreadPool(1);
977         TestPrimaryZoneMediaAudioRequestStatusCallback
978                 requestCallback = new TestPrimaryZoneMediaAudioRequestStatusCallback();
979 
980         boolean registered =
981                 mCarAudioManager.setPrimaryZoneMediaAudioRequestCallback(executor, requestCallback);
982 
983         assertWithMessage("Set status of media request callback")
984                 .that(registered).isTrue();
985     }
986 
987     @Test
988     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
989     @ApiTest(apis = {"android.car.media.CarAudioManager"
990             + "#requestMediaAudioOnPrimaryZone"})
requestMediaAudioOnPrimaryZone()991     public void requestMediaAudioOnPrimaryZone() throws Exception {
992         assumeDynamicRoutingIsEnabled();
993         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
994         setupMediaAudioRequestCallback();
995         OccupantZoneInfo info =
996                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
997         Executor callbackExecutor = Executors.newFixedThreadPool(1);
998         TestMediaAudioRequestStatusCallback callback = new TestMediaAudioRequestStatusCallback();
999 
1000         mMediaRequestId =
1001                 mCarAudioManager.requestMediaAudioOnPrimaryZone(info, callbackExecutor, callback);
1002 
1003         mRequestCallback.receivedMediaRequest();
1004         assertWithMessage("Received request id").that(mRequestCallback.mRequestId)
1005                 .isEqualTo(mMediaRequestId);
1006         assertWithMessage("Received occupant info").that(mRequestCallback.mOccupantZoneInfo)
1007                 .isEqualTo(info);
1008     }
1009 
1010     @Test
1011     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1012     @ApiTest(apis = {"android.car.media.CarAudioManager"
1013             + "#requestMediaAudioOnPrimaryZone"})
requestMediaAudioOnPrimaryZone_withoutApprover()1014     public void requestMediaAudioOnPrimaryZone_withoutApprover() throws Exception {
1015         assumeDynamicRoutingIsEnabled();
1016         assumeNoPrimaryZoneAudioMediaApprovers();
1017         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
1018         OccupantZoneInfo info =
1019                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
1020         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1021         TestMediaAudioRequestStatusCallback callback = new TestMediaAudioRequestStatusCallback();
1022 
1023         mMediaRequestId =
1024                 mCarAudioManager.requestMediaAudioOnPrimaryZone(info, callbackExecutor, callback);
1025 
1026         callback.receivedApproval();
1027         assertWithMessage("Request id for rejected request")
1028                 .that(callback.mRequestId).isEqualTo(mMediaRequestId);
1029         assertWithMessage("Rejected request status").that(callback.mStatus)
1030                 .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_REJECTED);
1031     }
1032 
1033     @Test
1034     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1035     @ApiTest(apis = {"android.car.media.CarAudioManager"
1036             + "#allowMediaAudioOnPrimaryZone"})
allowMediaAudioOnPrimaryZone()1037     public void allowMediaAudioOnPrimaryZone() throws Exception {
1038         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
1039         setupMediaAudioRequestCallback();
1040         OccupantZoneInfo info =
1041                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
1042         TestMediaAudioRequestStatusCallback callback =
1043                 requestToPlayMediaInPrimaryZone(info);
1044         mRequestCallback.receivedMediaRequest();
1045 
1046         boolean succeeded = mCarAudioManager.allowMediaAudioOnPrimaryZone(mMediaRequestId,
1047                 /* allow= */ true);
1048 
1049         callback.receivedApproval();
1050         assertWithMessage("Approved results for request in primary zone")
1051                 .that(succeeded).isTrue();
1052         assertWithMessage("Approved request id in primary zone")
1053                 .that(callback.mRequestId).isEqualTo(mMediaRequestId);
1054         assertWithMessage("Approved request occupant in primary zone")
1055                 .that(callback.mOccupantZoneInfo).isEqualTo(info);
1056         assertWithMessage("Audio status in primary zone").that(callback.mStatus)
1057                 .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_APPROVED);
1058     }
1059 
1060     @Test
1061     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1062     @ApiTest(apis = {"android.car.media.CarAudioManager"
1063             + "#allowMediaAudioOnPrimaryZone"})
allowMediaAudioOnPrimaryZone_withReject()1064     public void allowMediaAudioOnPrimaryZone_withReject() throws Exception {
1065         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
1066         setupMediaAudioRequestCallback();
1067         OccupantZoneInfo info =
1068                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
1069         TestMediaAudioRequestStatusCallback callback = requestToPlayMediaInPrimaryZone(info);
1070         mRequestCallback.receivedMediaRequest();
1071 
1072         boolean succeeded = mCarAudioManager.allowMediaAudioOnPrimaryZone(mMediaRequestId,
1073                 /* allow= */ false);
1074 
1075         callback.receivedApproval();
1076         long tempRequestId = mMediaRequestId;
1077         mMediaRequestId = INVALID_REQUEST_ID;
1078         assertWithMessage("Unapproved results for request in primary zone")
1079                 .that(succeeded).isTrue();
1080         assertWithMessage("Unapproved request id in primary zone")
1081                 .that(callback.mRequestId).isEqualTo(tempRequestId);
1082         assertWithMessage("Unapproved audio status in primary zone").that(callback.mStatus)
1083                 .isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_REJECTED);
1084     }
1085 
1086     @Test
1087     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1088     @ApiTest(apis = {"android.car.media.CarAudioManager"
1089             + "#allowMediaAudioOnPrimaryZone"})
allowMediaAudioOnPrimaryZone_withInvalidRequestId()1090     public void allowMediaAudioOnPrimaryZone_withInvalidRequestId() throws Exception {
1091         assumePassengerWithValidAudioZone();
1092         setupMediaAudioRequestCallback();
1093 
1094         boolean succeeded = mCarAudioManager
1095                         .allowMediaAudioOnPrimaryZone(INVALID_REQUEST_ID, /* allow= */ true);
1096 
1097         assertWithMessage("Invalid request id allowed results")
1098                 .that(succeeded).isFalse();
1099     }
1100 
1101     @Test
1102     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1103     @ApiTest(apis = {"android.car.media.CarAudioManager"
1104             + "#cancelMediaAudioOnPrimaryZone"})
cancelMediaAudioOnPrimaryZone()1105     public void cancelMediaAudioOnPrimaryZone() throws Exception {
1106         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
1107         setupMediaAudioRequestCallback();
1108         OccupantZoneInfo info =
1109                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
1110         TestMediaAudioRequestStatusCallback callback = requestToPlayMediaInPrimaryZone(info);
1111         mRequestCallback.receivedMediaRequest();
1112 
1113         mCarAudioManager.cancelMediaAudioOnPrimaryZone(mMediaRequestId);
1114 
1115         mMediaRequestId = INVALID_REQUEST_ID;
1116         callback.receivedApproval();
1117         assertWithMessage("Cancelled audio status in primary zone")
1118                 .that(callback.mStatus).isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_CANCELLED);
1119     }
1120 
1121     @Test
1122     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1123     @ApiTest(apis = {"android.car.media.CarAudioManager"
1124             + "#resetMediaAudioOnPrimaryZone"})
resetMediaAudioOnPrimaryZone()1125     public void resetMediaAudioOnPrimaryZone() throws Exception {
1126         int passengerAudioZoneId = assumePassengerWithValidAudioZone();
1127         setupMediaAudioRequestCallback();
1128         OccupantZoneInfo info =
1129                 mCarOccupantZoneManager.getOccupantForAudioZoneId(passengerAudioZoneId);
1130         TestMediaAudioRequestStatusCallback callback = requestToPlayMediaInPrimaryZone(info);
1131         mRequestCallback.receivedMediaRequest();
1132         mCarAudioManager.allowMediaAudioOnPrimaryZone(mMediaRequestId, /* allow= */ true);
1133         callback.receivedApproval();
1134         callback.reset();
1135 
1136         mCarAudioManager.resetMediaAudioOnPrimaryZone(info);
1137 
1138         long tempRequestId = mMediaRequestId;
1139         mMediaRequestId = INVALID_REQUEST_ID;
1140         callback.receivedApproval();
1141         assertWithMessage("Reset request id in primary zone")
1142                 .that(callback.mRequestId).isEqualTo(tempRequestId);
1143         assertWithMessage("Reset audio status in primary zone")
1144                 .that(callback.mStatus).isEqualTo(CarAudioManager.AUDIO_REQUEST_STATUS_STOPPED);
1145     }
1146 
1147     @Test
1148     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1149     @ApiTest(apis = {"android.car.media.CarAudioManager#getCurrentAudioZoneConfigInfo"})
getCurrentAudioZoneConfigInfo()1150     public void getCurrentAudioZoneConfigInfo() {
1151         assumeDynamicRoutingIsEnabled();
1152         List<TestZoneConfigIdInfo> zoneConfigs = assumeSecondaryZoneConfigs();
1153 
1154         CarAudioZoneConfigInfo currentZoneConfigInfo =
1155                 mCarAudioManager.getCurrentAudioZoneConfigInfo(mZoneId);
1156 
1157         assertWithMessage("Current zone config info")
1158                 .that(TestZoneConfigIdInfo.getZoneConfigFromInfo(currentZoneConfigInfo))
1159                 .isIn(zoneConfigs);
1160     }
1161 
1162     @Test
1163     @EnsureHasPermission({Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS,
1164             Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME})
1165     @ApiTest(apis = {"android.car.media.CarAudioManager#getCurrentAudioZoneConfigInfo",
1166             "android.car.media.CarAudioZoneConfigInfo#isActive",
1167             "android.car.media.CarAudioZoneConfigInfo#isSelected",
1168             "android.car.media.CarAudioZoneConfigInfo#isDefault",
1169             "android.car.media.CarAudioZoneConfigInfo#getConfigVolumeGroups"})
1170     @RequiresFlagsEnabled(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES)
getCurrentAudioZoneConfigInfo_withDynamicDevicesEnabled()1171     public void getCurrentAudioZoneConfigInfo_withDynamicDevicesEnabled() throws Exception {
1172         assumeDynamicRoutingIsEnabled();
1173         List<TestZoneConfigIdInfo> zoneConfigs = assumeSecondaryZoneConfigs();
1174         setUpDefaultCarAudioConfigurationForZone(mZoneId);
1175 
1176         CarAudioZoneConfigInfo currentZoneConfigInfo =
1177                 mCarAudioManager.getCurrentAudioZoneConfigInfo(mZoneId);
1178 
1179         assertWithMessage("Current zone config info, with dynamic device enabled")
1180                 .that(TestZoneConfigIdInfo.getZoneConfigFromInfo(currentZoneConfigInfo))
1181                 .isIn(zoneConfigs);
1182         assertWithMessage("Current zone config info active status")
1183                 .that(currentZoneConfigInfo.isActive()).isTrue();
1184         assertWithMessage("Current zone config info selected status")
1185                 .that(currentZoneConfigInfo.isSelected()).isTrue();
1186         assertWithMessage("Current zone config info default indicator")
1187                 .that(currentZoneConfigInfo.isDefault()).isTrue();
1188         List<CarVolumeGroupInfo> currentInfos =
1189                 mCarAudioManager.getVolumeGroupInfosForZone(mZoneId);
1190         for (CarVolumeGroupInfo info : currentInfos) {
1191             assertWithMessage("Current zone config info volume group %s", info.getId())
1192                     .that(currentZoneConfigInfo.getConfigVolumeGroups()).contains(info);
1193         }
1194     }
1195 
1196     @Test
1197     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1198     @ApiTest(apis = {"android.car.media.CarAudioManager#getCurrentAudioZoneConfigInfo"})
getCurrentAudioZoneConfigInfo_withInvalidZoneId_fails()1199     public void getCurrentAudioZoneConfigInfo_withInvalidZoneId_fails() {
1200         assumeDynamicRoutingIsEnabled();
1201 
1202         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
1203                 () -> mCarAudioManager.getCurrentAudioZoneConfigInfo(INVALID_AUDIO_ZONE));
1204 
1205         assertThat(exception).hasMessageThat().contains("Invalid audio zone Id");
1206     }
1207 
1208     @Test
1209     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1210     @ApiTest(apis = {"android.car.media.CarAudioManager#getAudioZoneConfigInfos"})
getAudioZoneConfigInfos_forPrimaryZone()1211     public void getAudioZoneConfigInfos_forPrimaryZone() {
1212         assumeDynamicRoutingIsEnabled();
1213         List<TestZoneConfigIdInfo> primaryZoneConfigsFromDump = parsePrimaryZoneConfigs();
1214 
1215         List<CarAudioZoneConfigInfo> zoneConfigInfos =
1216                 mCarAudioManager.getAudioZoneConfigInfos(PRIMARY_AUDIO_ZONE);
1217 
1218         assertWithMessage("Primary audio zone config")
1219                 .that(TestZoneConfigIdInfo.getZoneConfigListFromInfoList(zoneConfigInfos))
1220                 .containsExactlyElementsIn(primaryZoneConfigsFromDump);
1221     }
1222 
1223     @Test
1224     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1225     @ApiTest(apis = {"android.car.media.CarAudioManager#getAudioZoneConfigInfos"})
getAudioZoneConfigInfos_forSecondaryZone()1226     public void getAudioZoneConfigInfos_forSecondaryZone() {
1227         assumeDynamicRoutingIsEnabled();
1228         List<TestZoneConfigIdInfo> zoneConfigs = assumeSecondaryZoneConfigs();
1229 
1230         List<CarAudioZoneConfigInfo> zoneConfigInfosFromDump =
1231                 mCarAudioManager.getAudioZoneConfigInfos(mZoneId);
1232 
1233         assertWithMessage("All zone config infos")
1234                 .that(TestZoneConfigIdInfo.getZoneConfigListFromInfoList(zoneConfigInfosFromDump))
1235                 .containsExactlyElementsIn(zoneConfigs);
1236     }
1237 
1238     @Test
1239     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1240     @ApiTest(apis = {"android.car.media.CarAudioManager#getAudioZoneConfigInfos"})
getAudioZoneConfigInfos_withInvalidZoneId_fails()1241     public void getAudioZoneConfigInfos_withInvalidZoneId_fails() {
1242         assumeDynamicRoutingIsEnabled();
1243 
1244         IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
1245                 () -> mCarAudioManager.getAudioZoneConfigInfos(INVALID_AUDIO_ZONE));
1246 
1247         assertThat(exception).hasMessageThat().contains("Invalid audio zone Id");
1248     }
1249 
1250     @Test
1251     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1252     @ApiTest(apis = {"android.car.media.CarAudioManager#switchAudioZoneToConfig"})
1253     @RequiresFlagsDisabled(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES)
switchAudioZoneToConfig()1254     public void switchAudioZoneToConfig() throws Exception {
1255         assumeDynamicRoutingIsEnabled();
1256         assumeSecondaryZoneConfigs();
1257         CarAudioZoneConfigInfo zoneConfigInfoSaved =
1258                 mCarAudioManager.getCurrentAudioZoneConfigInfo(mZoneId);
1259         CarAudioZoneConfigInfo zoneConfigInfoSwitchedTo = getNonCurrentZoneConfig(mZoneId);
1260         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1261         TestSwitchAudioZoneConfigCallback callback = new TestSwitchAudioZoneConfigCallback();
1262 
1263         mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSwitchedTo, callbackExecutor,
1264                 callback);
1265 
1266         callback.receivedApproval();
1267         assertWithMessage("Zone configuration switching status")
1268                 .that(callback.mIsSuccessful).isTrue();
1269         assertWithMessage("Updated zone configuration")
1270                 .that(callback.mZoneConfigInfo).isEqualTo(zoneConfigInfoSwitchedTo);
1271         callback.reset();
1272         mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSaved, callbackExecutor, callback);
1273         callback.receivedApproval();
1274     }
1275 
1276     @Test
1277     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1278     @ApiTest(apis = {"android.car.media.CarAudioManager#switchAudioZoneToConfig",
1279             "android.car.media.CarAudioZoneConfigInfo#isActive",
1280             "android.car.media.CarAudioZoneConfigInfo#isSelected",
1281             "android.car.media.CarAudioZoneConfigInfo#getConfigVolumeGroups"})
1282     @RequiresFlagsEnabled(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES)
switchAudioZoneToConfig_withDynamicDevicesEnabled()1283     public void switchAudioZoneToConfig_withDynamicDevicesEnabled() throws Exception {
1284         assumeDynamicRoutingIsEnabled();
1285         assumeSecondaryZoneConfigs();
1286         CarAudioZoneConfigInfo zoneConfigInfoSaved =
1287                 mCarAudioManager.getCurrentAudioZoneConfigInfo(mZoneId);
1288         CarAudioZoneConfigInfo zoneConfigInfoSwitchedTo = getNonCurrentZoneConfig(mZoneId);
1289         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1290         TestSwitchAudioZoneConfigCallback callback = new TestSwitchAudioZoneConfigCallback();
1291 
1292         mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSwitchedTo, callbackExecutor,
1293                 callback);
1294 
1295         callback.receivedApproval();
1296         assertWithMessage("Zone configuration switching status with dynamic devices enabled")
1297                 .that(callback.mIsSuccessful).isTrue();
1298         assertWithMessage("Updated zone configuration with dynamic devices enabled")
1299                 .that(callback.mZoneConfigInfo.hasSameConfigInfo(zoneConfigInfoSwitchedTo))
1300                 .isTrue();
1301         assertWithMessage("Switched configuration active status with dynamic devices enable")
1302                 .that(callback.mZoneConfigInfo.isActive()).isTrue();
1303         assertWithMessage("Switched configuration selected status with dynamic devices enable")
1304                 .that(callback.mZoneConfigInfo.isSelected()).isTrue();
1305         CarAudioZoneConfigInfo updatedPreviousConfig =
1306                 mCarAudioManager.getAudioZoneConfigInfos(mZoneId).stream()
1307                         .filter(info -> info.getConfigId() == zoneConfigInfoSaved.getConfigId())
1308                         .findFirst().orElseThrow();
1309         assertWithMessage("Previous configuration selected status with dynamic devices enable")
1310                 .that(updatedPreviousConfig.isSelected()).isFalse();
1311         callback.reset();
1312         mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSaved, callbackExecutor, callback);
1313         callback.receivedApproval();
1314     }
1315 
1316     @Test
1317     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1318     @ApiTest(apis = {"android.car.media.CarAudioManager#switchAudioZoneToConfig"})
switchAudioZoneToConfig_withNullConfig_fails()1319     public void switchAudioZoneToConfig_withNullConfig_fails() throws Exception {
1320         assumeDynamicRoutingIsEnabled();
1321         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1322         TestSwitchAudioZoneConfigCallback callback = new TestSwitchAudioZoneConfigCallback();
1323 
1324         NullPointerException exception = assertThrows(NullPointerException.class,
1325                 () -> mCarAudioManager.switchAudioZoneToConfig(/* zoneConfig= */ null,
1326                         callbackExecutor, callback));
1327 
1328         assertThat(exception).hasMessageThat().contains("Audio zone configuration can not be null");
1329     }
1330 
1331     @Test
1332     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1333     @ApiTest(apis = {"android.car.media.CarAudioManager#switchAudioZoneToConfig"})
switchAudioZoneToConfig_withNullExecutor_fails()1334     public void switchAudioZoneToConfig_withNullExecutor_fails() throws Exception {
1335         assumeDynamicRoutingIsEnabled();
1336         assumeSecondaryZoneConfigs();
1337         CarAudioZoneConfigInfo zoneConfigInfoSwitchedTo = getNonCurrentZoneConfig(mZoneId);
1338         TestSwitchAudioZoneConfigCallback callback = new TestSwitchAudioZoneConfigCallback();
1339 
1340         NullPointerException exception = assertThrows(NullPointerException.class,
1341                 () -> mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSwitchedTo,
1342                         /* executor= */ null, callback));
1343 
1344         assertThat(exception).hasMessageThat().contains("Executor can not be null");
1345     }
1346 
1347     @Test
1348     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1349     @ApiTest(apis = {"android.car.media.CarAudioManager#switchAudioZoneToConfig"})
switchAudioZoneToConfig_withNullCallback_fails()1350     public void switchAudioZoneToConfig_withNullCallback_fails() throws Exception {
1351         assumeDynamicRoutingIsEnabled();
1352         assumeSecondaryZoneConfigs();
1353         CarAudioZoneConfigInfo zoneConfigInfoSwitchedTo = getNonCurrentZoneConfig(mZoneId);
1354         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1355 
1356         NullPointerException exception = assertThrows(NullPointerException.class,
1357                 () -> mCarAudioManager.switchAudioZoneToConfig(zoneConfigInfoSwitchedTo,
1358                         callbackExecutor, /* callback= */ null));
1359 
1360         assertThat(exception).hasMessageThat().contains("callback can not be null");
1361     }
1362 
1363     @Test
1364     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1365     @ApiTest(apis = {"android.car.media.CarAudioManager"
1366             + "#setAudioZoneMirrorStatusCallback"})
setAudioZoneMirrorStatusCallback()1367     public void setAudioZoneMirrorStatusCallback() {
1368         assumeAudioMirrorEnabled();
1369         assumePassengersForAudioMirror();
1370         Executor executor = Executors.newFixedThreadPool(1);
1371         TestAudioZonesMirrorStatusCallback callback;
1372         boolean registered = false;
1373 
1374         try {
1375             callback = new TestAudioZonesMirrorStatusCallback();
1376             registered = mCarAudioManager.setAudioZoneMirrorStatusCallback(executor, callback);
1377         } finally {
1378             if (registered) {
1379                 mCarAudioManager.clearAudioZonesMirrorStatusCallback();
1380             }
1381         }
1382 
1383         assertWithMessage("Audio zone mirror status callback registered status")
1384                 .that(registered).isTrue();
1385     }
1386 
1387     @Test
1388     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1389     @ApiTest(apis = {"android.car.media.CarAudioManager#enableMirrorForAudioZones"})
enableMirrorForAudioZones()1390     public void enableMirrorForAudioZones() throws Exception {
1391         assumeAudioMirrorEnabled();
1392         List<Integer> audioZones = assumePassengersForAudioMirror();
1393         setupAudioMirrorStatusCallback();
1394 
1395         mMirrorRequestId = mCarAudioManager.enableMirrorForAudioZones(audioZones);
1396 
1397         mAudioZonesMirrorCallback.waitForCallback();
1398         assertWithMessage("Enabled mirror for audio zone status")
1399                 .that(mAudioZonesMirrorCallback.mStatus).isEqualTo(
1400                         CarAudioManager.AUDIO_REQUEST_STATUS_APPROVED);
1401         assertWithMessage("Enabled mirror audio zones")
1402                 .that(mAudioZonesMirrorCallback.mAudioZones).containsExactlyElementsIn(audioZones);
1403     }
1404 
1405     @Test
1406     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1407     @ApiTest(apis = {"android.car.media.CarAudioManager#enableMirrorForAudioZones"})
enableMirrorForAudioZones_withNullList()1408     public void enableMirrorForAudioZones_withNullList() throws Exception {
1409         assumeAudioMirrorEnabled();
1410         setupAudioMirrorStatusCallback();
1411 
1412         NullPointerException thrown = assertThrows(NullPointerException.class, () ->
1413                 mMirrorRequestId = mCarAudioManager
1414                         .enableMirrorForAudioZones(/* audioZonesToMirror= */ null));
1415 
1416         assertWithMessage("Null enable mirror audio zones exception")
1417                 .that(thrown).hasMessageThat().contains("Audio zones to mirror");
1418     }
1419 
1420     @Test
1421     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1422     @ApiTest(apis = {"android.car.media.CarAudioManager#disableAudioMirrorForZone"})
disableAudioMirrorForZone()1423     public void disableAudioMirrorForZone() throws Exception {
1424         assumeAudioMirrorEnabled();
1425         List<Integer> audioZones = assumePassengersForAudioMirror();
1426         int zoneToDisable = audioZones.get(0);
1427         setupAudioMirrorStatusCallback();
1428         mMirrorRequestId = mCarAudioManager.enableMirrorForAudioZones(audioZones);
1429         mAudioZonesMirrorCallback.waitForCallback();
1430         mAudioZonesMirrorCallback.reset();
1431 
1432         mCarAudioManager.disableAudioMirrorForZone(zoneToDisable);
1433 
1434         mMirrorRequestId = INVALID_REQUEST_ID;
1435         mAudioZonesMirrorCallback.waitForCallback();
1436         assertWithMessage("Disable mirror status for audio zone %s", zoneToDisable)
1437                 .that(mAudioZonesMirrorCallback.mStatus).isEqualTo(
1438                         CarAudioManager.AUDIO_REQUEST_STATUS_STOPPED);
1439         assertWithMessage("Disable mirror zones for audio zone %s", zoneToDisable)
1440                 .that(mAudioZonesMirrorCallback.mAudioZones).containsExactlyElementsIn(audioZones);
1441     }
1442 
1443     @Test
1444     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1445     @ApiTest(apis = {"android.car.media.CarAudioManager#disableAudioMirror"})
disableAudioMirror()1446     public void disableAudioMirror() throws Exception {
1447         assumeAudioMirrorEnabled();
1448         List<Integer> audioZones = assumePassengersForAudioMirror();
1449         setupAudioMirrorStatusCallback();
1450         mMirrorRequestId = mCarAudioManager.enableMirrorForAudioZones(audioZones);
1451         mAudioZonesMirrorCallback.waitForCallback();
1452         mAudioZonesMirrorCallback.reset();
1453 
1454         mCarAudioManager.disableAudioMirror(mMirrorRequestId);
1455 
1456         mMirrorRequestId = INVALID_REQUEST_ID;
1457         mAudioZonesMirrorCallback.waitForCallback();
1458         assertWithMessage("Disable mirror status for audio zones")
1459                 .that(mAudioZonesMirrorCallback.mStatus).isEqualTo(
1460                         CarAudioManager.AUDIO_REQUEST_STATUS_STOPPED);
1461         assertWithMessage("Disable mirror zones for audio zones")
1462                 .that(mAudioZonesMirrorCallback.mAudioZones).containsExactlyElementsIn(audioZones);
1463     }
1464 
1465     @Test
1466     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1467     @ApiTest(apis = {"android.car.media.CarAudioManager#getMirrorAudioZonesForAudioZone"})
getMirrorAudioZonesForAudioZone()1468     public void getMirrorAudioZonesForAudioZone() throws Exception {
1469         assumeAudioMirrorEnabled();
1470         List<Integer> audioZones = assumePassengersForAudioMirror();
1471         int zoneToQuery = audioZones.get(0);
1472         setupAudioMirrorStatusCallback();
1473         mMirrorRequestId = mCarAudioManager.enableMirrorForAudioZones(audioZones);
1474         mAudioZonesMirrorCallback.waitForCallback();
1475 
1476         List<Integer> queriedZones = mCarAudioManager.getMirrorAudioZonesForAudioZone(zoneToQuery);
1477 
1478         assertWithMessage("Queried audio zones").that(queriedZones)
1479                 .containsExactlyElementsIn(audioZones);
1480     }
1481 
1482     @Test
1483     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1484     @ApiTest(apis = {"android.car.media.CarAudioManager#getMirrorAudioZonesForMirrorRequest"})
getMirrorAudioZonesForMirrorRequest()1485     public void getMirrorAudioZonesForMirrorRequest() throws Exception {
1486         assumeAudioMirrorEnabled();
1487         List<Integer> audioZones = assumePassengersForAudioMirror();
1488         setupAudioMirrorStatusCallback();
1489         mMirrorRequestId = mCarAudioManager.enableMirrorForAudioZones(audioZones);
1490         mAudioZonesMirrorCallback.waitForCallback();
1491 
1492         List<Integer> queriedZones = mCarAudioManager.getMirrorAudioZonesForMirrorRequest(
1493                 mMirrorRequestId);
1494 
1495         assertWithMessage("Queried audio zones for request %s", mMirrorRequestId).that(queriedZones)
1496                 .containsExactlyElementsIn(audioZones);
1497     }
1498 
1499     @Test
1500     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
1501     @ApiTest(apis = {"android.car.media.CarAudioManager#getAudioZoneIds"})
getAudioZoneIds()1502     public void getAudioZoneIds() throws Exception {
1503         assumeDynamicRoutingIsEnabled();
1504 
1505         List<Integer> zoneIds = mCarAudioManager.getAudioZoneIds();
1506 
1507         assertWithMessage("Audio Zone Ids").that(zoneIds).contains(PRIMARY_AUDIO_ZONE);
1508     }
1509 
requestToPlayMediaInPrimaryZone( OccupantZoneInfo info)1510     private TestMediaAudioRequestStatusCallback requestToPlayMediaInPrimaryZone(
1511             OccupantZoneInfo info) {
1512         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1513         TestMediaAudioRequestStatusCallback callback = new TestMediaAudioRequestStatusCallback();
1514         mMediaRequestId =
1515                 mCarAudioManager.requestMediaAudioOnPrimaryZone(info, callbackExecutor, callback);
1516         return callback;
1517     }
1518 
setupMediaAudioRequestCallback()1519     private void setupMediaAudioRequestCallback() {
1520         Executor requestExecutor = Executors.newFixedThreadPool(1);
1521         mRequestCallback = new TestPrimaryZoneMediaAudioRequestStatusCallback();
1522         mCarAudioManager.setPrimaryZoneMediaAudioRequestCallback(requestExecutor, mRequestCallback);
1523     }
1524 
setupAudioMirrorStatusCallback()1525     private void setupAudioMirrorStatusCallback() {
1526         Executor executor = Executors.newFixedThreadPool(1);
1527         mAudioZonesMirrorCallback = new TestAudioZonesMirrorStatusCallback();
1528         mCarAudioManager.setAudioZoneMirrorStatusCallback(executor, mAudioZonesMirrorCallback);
1529     }
1530 
assumePassengerWithValidAudioZone()1531     private int assumePassengerWithValidAudioZone() {
1532         List<Integer> audioZonesWithPassengers = assumePassengersWithValidAudioZones(
1533                 /* count= */ 1, "Audio share to primary zone");
1534 
1535         return audioZonesWithPassengers.get(0);
1536     }
1537 
assumePassengersWithValidAudioZones(int minimumCount, String message)1538     private List<Integer> assumePassengersWithValidAudioZones(int minimumCount, String message) {
1539         List<Integer> audioZonesWithPassengers = getAvailablePassengerAudioZone();
1540         assumeTrue(message + ": Need at least " + minimumCount
1541                         + " passenger(s) with valid audio zone id",
1542                 audioZonesWithPassengers.size() >= minimumCount);
1543 
1544         return audioZonesWithPassengers;
1545     }
1546 
assumePassengersForAudioMirror()1547     private List<Integer> assumePassengersForAudioMirror() {
1548         List<Integer> audioZonesWithPassengers = assumePassengersWithValidAudioZones(/* count= */ 2,
1549                 "Passenger audio mirror");
1550 
1551         return audioZonesWithPassengers.subList(/* fromIndex= */ 0, /* toIndex= */ 2);
1552     }
1553 
getAvailablePassengerAudioZone()1554     private List<Integer> getAvailablePassengerAudioZone() {
1555         return mCarOccupantZoneManager.getAllOccupantZones().stream()
1556                 .filter(occupant -> mCarOccupantZoneManager.getUserForOccupant(occupant)
1557                         != CarOccupantZoneManager.INVALID_USER_ID)
1558                 .map(occupant -> mCarOccupantZoneManager.getAudioZoneIdForOccupant(occupant))
1559                 .filter(audioZoneId -> audioZoneId != INVALID_AUDIO_ZONE
1560                         && audioZoneId != PRIMARY_AUDIO_ZONE)
1561                 .collect(Collectors.toList());
1562     }
1563 
assumeSecondaryZoneConfigs()1564     private List<TestZoneConfigIdInfo> assumeSecondaryZoneConfigs() {
1565         List<Integer> audioZonesWithPassengers = getAvailablePassengerAudioZone();
1566         assumeFalse("Requires a zone with a passenger/user", audioZonesWithPassengers.isEmpty());
1567         SparseArray<List<TestZoneConfigIdInfo>> zoneConfigs = parseAudioZoneConfigs();
1568         List<TestZoneConfigIdInfo> secondaryZoneConfigs = null;
1569         for (int index = 0; index < audioZonesWithPassengers.size(); index++) {
1570             int zoneId = audioZonesWithPassengers.get(index);
1571             if (zoneConfigs.contains(zoneId) && zoneConfigs.get(zoneId).size() > 1) {
1572                 mZoneId = zoneId;
1573                 secondaryZoneConfigs = zoneConfigs.get(zoneId);
1574                 break;
1575             }
1576         }
1577         assumeTrue("Secondary zones requires multiple zone configurations",
1578                 secondaryZoneConfigs != null);
1579         return secondaryZoneConfigs;
1580     }
1581 
parsePrimaryZoneConfigs()1582     private List<TestZoneConfigIdInfo> parsePrimaryZoneConfigs() {
1583         SparseArray<List<TestZoneConfigIdInfo>> zoneConfigs = parseAudioZoneConfigs();
1584         List<TestZoneConfigIdInfo> primaryZoneConfigs = zoneConfigs.get(PRIMARY_AUDIO_ZONE);
1585         assertWithMessage("Dumped primary audio zone configuration")
1586                 .that(primaryZoneConfigs).isNotNull();
1587         return primaryZoneConfigs;
1588     }
1589 
parseAudioZoneConfigs()1590     private SparseArray<List<TestZoneConfigIdInfo>> parseAudioZoneConfigs() {
1591         SparseArray<List<TestZoneConfigIdInfo>> zoneConfigs = new SparseArray<>();
1592         if (Flags.carDumpToProto()) {
1593             List<CarAudioZoneProto> zoneProtoList = mCarAudioServiceProtoDump
1594                     .getCarAudioZonesList();
1595             for (int zoneIndex = 0; zoneIndex < zoneProtoList.size(); zoneIndex++) {
1596                 int zoneId = zoneProtoList.get(zoneIndex).getId();
1597                 List<CarAudioZoneConfigProto> zoneConfigProtoList = zoneProtoList.get(zoneIndex)
1598                         .getZoneConfigsList();
1599                 for (int configIndex = 0; configIndex < zoneConfigProtoList.size(); configIndex++) {
1600                     if (!zoneConfigs.contains(zoneId)) {
1601                         zoneConfigs.put(zoneId, new ArrayList<>());
1602                     }
1603                     int zoneConfigId = zoneConfigProtoList.get(configIndex).getId();
1604                     String configName = zoneConfigProtoList.get(configIndex).getName();
1605                     zoneConfigs.get(zoneId).add(new TestZoneConfigIdInfo(zoneId, zoneConfigId,
1606                             configName));
1607                 }
1608             }
1609         } else {
1610             Matcher zoneConfigMatcher = ZONE_CONFIG_PATTERN.matcher(mCarAudioServiceDump);
1611             while (zoneConfigMatcher.find()) {
1612                 int zoneId = Integer.parseInt(zoneConfigMatcher.group(3));
1613                 int zoneConfigId = Integer.parseInt(zoneConfigMatcher.group(2));
1614                 String configName = zoneConfigMatcher.group(1);
1615                 if (!zoneConfigs.contains(zoneId)) {
1616                     zoneConfigs.put(zoneId, new ArrayList<>());
1617                 }
1618                 zoneConfigs.get(zoneId).add(new TestZoneConfigIdInfo(zoneId, zoneConfigId,
1619                         configName));
1620             }
1621         }
1622         return zoneConfigs;
1623     }
1624 
getNonCurrentZoneConfig(int zoneId)1625     private CarAudioZoneConfigInfo getNonCurrentZoneConfig(int zoneId) {
1626         List<CarAudioZoneConfigInfo> zoneConfigInfos =
1627                 mCarAudioManager.getAudioZoneConfigInfos(zoneId);
1628         CarAudioZoneConfigInfo currentZoneConfigInfo =
1629                 mCarAudioManager.getCurrentAudioZoneConfigInfo(zoneId);
1630 
1631         CarAudioZoneConfigInfo differentZoneConfig = null;
1632         for (int index = 0; index < zoneConfigInfos.size(); index++) {
1633             if (!currentZoneConfigInfo.equals(zoneConfigInfos.get(index))) {
1634                 differentZoneConfig = zoneConfigInfos.get(index);
1635                 break;
1636             }
1637         }
1638 
1639         return differentZoneConfig;
1640     }
1641 
1642     @Test
1643     @ApiTest(apis = {"android.car.media.CarAudioManager"
1644             + "#registerCarVolumeGroupEventCallback"})
registerCarVolumeGroupEventCallback_nullCallback_throwsNPE()1645     public void registerCarVolumeGroupEventCallback_nullCallback_throwsNPE() {
1646         Executor executor = Executors.newFixedThreadPool(1);
1647 
1648         NullPointerException exception = assertThrows(NullPointerException.class,
1649                 () -> mCarAudioManager.registerCarVolumeGroupEventCallback(executor,
1650                         /* callback= */ null));
1651 
1652         assertWithMessage("Register car volume group event with null callback exception")
1653                 .that(exception).hasMessageThat()
1654                 .contains("Car volume event callback can not be null");
1655     }
1656 
1657     @Test
1658     @ApiTest(apis = {"android.car.media.CarAudioManager"
1659             + "#registerCarVolumeGroupEventCallback"})
registerCarVolumeGroupEventCallback_nullExecutor_throwsNPE()1660     public void registerCarVolumeGroupEventCallback_nullExecutor_throwsNPE() {
1661         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1662 
1663         NullPointerException exception = assertThrows(NullPointerException.class,
1664                 () -> mCarAudioManager.registerCarVolumeGroupEventCallback(/* executor= */ null,
1665                         mEventCallback));
1666 
1667         mEventCallback = null;
1668         assertWithMessage("Register car volume group event with null executor exception")
1669                 .that(exception).hasMessageThat().contains("Executor can not be null");
1670     }
1671 
1672     @Test
1673     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1674     @ApiTest(apis = {"android.car.media.CarAudioManager"
1675             + "#registerCarVolumeGroupEventCallback"})
registerCarVolumeGroupEventCallback_volumeGroupEventsDisabled()1676     public void registerCarVolumeGroupEventCallback_volumeGroupEventsDisabled() throws Exception {
1677         assumeDynamicRoutingIsEnabled();
1678         assumeVolumeGroupEventsIsDisabled();
1679         Executor executor = Executors.newFixedThreadPool(1);
1680         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1681 
1682         Exception exception = assertThrows(IllegalStateException.class,
1683                 () -> mCarAudioManager.registerCarVolumeGroupEventCallback(executor,
1684                         mEventCallback));
1685 
1686         mEventCallback = null;
1687         assertWithMessage("Register car volume group event with feature disabled")
1688                 .that(exception).hasMessageThat().contains("Car Volume Group Event is required");
1689     }
1690 
1691     @Test
1692     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1693     @ApiTest(apis = {
1694             "android.car.media.CarAudioManager#registerCarVolumeGroupEventCallback"})
registerCarVolumeGroupEventCallback_onVolumeGroupEvent()1695     public void registerCarVolumeGroupEventCallback_onVolumeGroupEvent() throws Exception {
1696         assumeDynamicRoutingIsEnabled();
1697         assumeVolumeGroupEventsIsEnabled();
1698         Executor executor = Executors.newFixedThreadPool(1);
1699         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1700 
1701         boolean status = mCarAudioManager.registerCarVolumeGroupEventCallback(executor,
1702                 mEventCallback);
1703 
1704         injectVolumeUpKeyEvent();
1705         assertWithMessage("Car volume group event callback")
1706                 .that(mEventCallback.receivedVolumeGroupEvents()).isTrue();
1707     }
1708 
1709     @Test
1710     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1711     @ApiTest(apis = {"android.car.media.CarAudioManager"
1712             + "#registerCarVolumeGroupEventCallback"})
registerCarVolumeGroupEventCallback_registerCarVolumeCallback_onVolumeGroupEvent()1713     public void registerCarVolumeGroupEventCallback_registerCarVolumeCallback_onVolumeGroupEvent()
1714             throws Exception {
1715         assumeDynamicRoutingIsEnabled();
1716         assumeVolumeGroupEventsIsEnabled();
1717         Executor executor = Executors.newFixedThreadPool(1);
1718         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1719         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
1720         mCarAudioManager.registerCarVolumeGroupEventCallback(executor, mEventCallback);
1721 
1722         mCarAudioManager.registerCarVolumeCallback(mCallback);
1723 
1724         injectVolumeDownKeyEvent();
1725         assertWithMessage("Car volume group event for registered callback")
1726             .that(mEventCallback.receivedVolumeGroupEvents()).isTrue();
1727         assertWithMessage("Car group volume changed for deprioritized callback")
1728             .that(mCallback.receivedGroupVolumeChanged()).isFalse();
1729     }
1730 
1731     @Test
1732     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1733     @ApiTest(apis = {"android.car.media.CarAudioManager"
1734             + "#registerCarVolumeGroupEventCallback",
1735             "android.car.media.CarAudioManager"
1736                     + "#unregisterCarVolumeGroupEventCallback"})
unregisterCarVolumeGroupEventCallback_onGroupVolumeChanged()1737     public void unregisterCarVolumeGroupEventCallback_onGroupVolumeChanged()
1738             throws Exception {
1739         assumeDynamicRoutingIsEnabled();
1740         assumeVolumeGroupEventsIsEnabled();
1741         Executor executor = Executors.newFixedThreadPool(1);
1742         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1743         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
1744         mCarAudioManager.registerCarVolumeGroupEventCallback(executor, mEventCallback);
1745         mCarAudioManager.registerCarVolumeCallback(mCallback);
1746 
1747         mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback);
1748 
1749         injectVolumeUpKeyEvent();
1750         assertWithMessage("Car volume group event for unregistered callback")
1751             .that(mEventCallback.receivedVolumeGroupEvents()).isFalse();
1752         assertWithMessage("Car group volume changed for reprioritized callback")
1753             .that(mCallback.receivedGroupVolumeChanged()).isTrue();
1754     }
1755 
1756     @Test
1757     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1758     @ApiTest(apis = {"android.car.media.CarAudioManager"
1759             + "#registerCarVolumeGroupEventCallback",
1760             "android.car.media.CarAudioManager"
1761                     + "#unregisterCarVolumeGroupEventCallback"})
reRegisterCarVolumeGroupEventCallback_eventCallbackReprioritized()1762     public void reRegisterCarVolumeGroupEventCallback_eventCallbackReprioritized()
1763             throws Exception {
1764         assumeDynamicRoutingIsEnabled();
1765         assumeVolumeGroupEventsIsEnabled();
1766         Executor executor = Executors.newFixedThreadPool(1);
1767         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1768         mCallback = new CarAudioManagerTestUtils.SyncCarVolumeCallback();
1769         mCarAudioManager.registerCarVolumeGroupEventCallback(executor, mEventCallback);
1770         mCarAudioManager.registerCarVolumeCallback(mCallback);
1771         mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback);
1772 
1773         mCarAudioManager.registerCarVolumeGroupEventCallback(executor, mEventCallback);
1774 
1775         injectVolumeDownKeyEvent();
1776         assertWithMessage("Car volume group event for re-registered callback")
1777             .that(mEventCallback.receivedVolumeGroupEvents()).isTrue();
1778         assertWithMessage("Car group volume changed for deprioritized callback")
1779             .that(mCallback.receivedGroupVolumeChanged()).isFalse();
1780     }
1781 
1782     @Test
1783     @ApiTest(apis = {"android.car.media.CarAudioManager"
1784             + "#unregisterCarVolumeGroupEventCallback"})
unregisterCarVolumeGroupEventCallback_nullCallback_throwsNPE()1785     public void unregisterCarVolumeGroupEventCallback_nullCallback_throwsNPE() {
1786         NullPointerException exception = assertThrows(NullPointerException.class,
1787                 () -> mCarAudioManager.unregisterCarVolumeGroupEventCallback(/* callback= */ null));
1788 
1789         assertWithMessage("Unregister car volume group event with null callback exception")
1790                 .that(exception).hasMessageThat()
1791                 .contains("Car volume event callback can not be null");
1792     }
1793 
1794     @Test
1795     @ApiTest(apis = {"android.car.media.CarAudioManager"
1796             + "#unregisterCarVolumeGroupEventCallback"})
unregisterCarVolumeGroupEventCallback_withoutPermission_throws()1797     public void unregisterCarVolumeGroupEventCallback_withoutPermission_throws()
1798             throws Exception {
1799         assumeDynamicRoutingIsEnabled();
1800         assumeVolumeGroupEventsIsEnabled();
1801         Executor executor = Executors.newFixedThreadPool(1);
1802         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1803         runWithCarControlAudioVolumePermission(
1804                 () -> mCarAudioManager.registerCarVolumeGroupEventCallback(executor,
1805                         mEventCallback));
1806 
1807         Exception exception = assertThrows(SecurityException.class,
1808                 () -> mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback));
1809 
1810         assertWithMessage("Unregister car volume group event callback without permission exception")
1811                 .that(exception).hasMessageThat().contains(PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1812 
1813     }
1814 
1815     @Test
1816     @ApiTest(apis = {"android.car.media.CarAudioManager"
1817             + "#unregisterCarVolumeGroupEventCallback"})
unregisterCarVolumeGroupEventCallback_withoutPermission_receivesCallback()1818     public void unregisterCarVolumeGroupEventCallback_withoutPermission_receivesCallback()
1819             throws Exception {
1820         assumeDynamicRoutingIsEnabled();
1821         assumeVolumeGroupEventsIsEnabled();
1822         Executor executor = Executors.newFixedThreadPool(1);
1823         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1824         runWithCarControlAudioVolumePermission(
1825                 () -> mCarAudioManager.registerCarVolumeGroupEventCallback(executor,
1826                         mEventCallback));
1827 
1828         Exception exception = assertThrows(SecurityException.class,
1829                 () -> mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback));
1830 
1831         injectVolumeDownKeyEvent();
1832         assertWithMessage("Car volume group event after unregister security exception")
1833                 .that(mEventCallback.receivedVolumeGroupEvents()).isTrue();
1834     }
1835 
1836     @Test
1837     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1838     @ApiTest(apis = {"android.car.media.CarAudioManager"
1839             + "#registerCarVolumeGroupEventCallback",
1840             "android.car.media.CarAudioManager"
1841                     + "#unregisterCarVolumeGroupEventCallback"})
unregisterCarVolumeGroupEventCallback_noLongerReceivesEventCallback()1842     public void unregisterCarVolumeGroupEventCallback_noLongerReceivesEventCallback()
1843             throws Exception {
1844         assumeDynamicRoutingIsEnabled();
1845         assumeVolumeGroupEventsIsEnabled();
1846         Executor executor = Executors.newFixedThreadPool(1);
1847         mEventCallback = new CarAudioManagerTestUtils.TestCarVolumeGroupEventCallback();
1848         mCarAudioManager.registerCarVolumeGroupEventCallback(executor, mEventCallback);
1849 
1850         mCarAudioManager.unregisterCarVolumeGroupEventCallback(mEventCallback);
1851 
1852         injectVolumeUpKeyEvent();
1853         assertWithMessage("Car volume group event for unregistered callback")
1854             .that(mEventCallback.receivedVolumeGroupEvents()).isFalse();
1855     }
1856 
1857     @Test
1858     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1859     @ApiTest(apis = {"android.car.media.CarAudioManager#isVolumeGroupMuted",
1860             "android.car.media.CarVolumeGroupInfo#isMuted"})
isVolumeGroupMuted()1861     public void isVolumeGroupMuted() {
1862         assumeDynamicRoutingIsEnabled();
1863         readFirstZoneAndVolumeGroup();
1864         CarVolumeGroupInfo volumeGroupInfo = mCarAudioManager.getVolumeGroupInfo(mZoneId,
1865                 mVolumeGroupId);
1866 
1867         boolean isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, mVolumeGroupId);
1868 
1869         assertWithMessage("Mute state for zone %s group %s", mZoneId, mVolumeGroupId)
1870                 .that(isMuted).isEqualTo(volumeGroupInfo.isMuted());
1871     }
1872 
1873     @Test
1874     @EnsureHasPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
1875     @ApiTest(apis = {"android.car.media.CarAudioManager#setVolumeGroupMute",
1876             "android.car.media.CarAudioManager#isVolumeGroupMuted",
1877             "android.car.media.CarVolumeGroupInfo#isMutedBySystem"})
1878     @RequiresFlagsEnabled(Flags.FLAG_CAR_AUDIO_MUTE_AMBIGUITY)
setVolumeGroupMute_withMuteAmbiguityFlagEnabled()1879     public void setVolumeGroupMute_withMuteAmbiguityFlagEnabled() {
1880         assumeDynamicRoutingIsEnabled();
1881         assumeVolumeGroupMutingIsEnabled();
1882         readFirstZoneAndVolumeGroup();
1883         boolean isMuted = mCarAudioManager.isVolumeGroupMuted(mZoneId, mVolumeGroupId);
1884         boolean isMutedBySystem = mCarAudioManager.getVolumeGroupInfo(mZoneId,
1885                 mVolumeGroupId).isMutedBySystem();
1886         String muteOperationStr = isMuted ? "unmuting" : "muting";
1887 
1888         try {
1889             mCarAudioManager.setVolumeGroupMute(mZoneId, mVolumeGroupId, !isMuted, /* flags= */ 0);
1890 
1891             assertWithMessage("Mute state for zone %s group %s after %s", mZoneId, mVolumeGroupId,
1892                     muteOperationStr)
1893                     .that(mCarAudioManager.isVolumeGroupMuted(mZoneId, mVolumeGroupId))
1894                     .isEqualTo(mCarAudioManager.getVolumeGroupInfo(mZoneId, mVolumeGroupId)
1895                             .isMuted());
1896             assertWithMessage("Mute by system state for zone %s group %s after %s", mZoneId,
1897                     mVolumeGroupId, muteOperationStr)
1898                     .that(mCarAudioManager.getVolumeGroupInfo(mZoneId, mVolumeGroupId)
1899                             .isMutedBySystem()).isEqualTo(isMutedBySystem);
1900         } finally {
1901             mCarAudioManager.setVolumeGroupMute(mZoneId, mVolumeGroupId, isMuted, /* flags= */ 0);
1902         }
1903     }
1904 
getNumberOfPrimaryZoneAudioMediaCallbacks()1905     private int getNumberOfPrimaryZoneAudioMediaCallbacks() {
1906         if (Flags.carDumpToProto()) {
1907             return mCarAudioServiceProtoDump.getMediaRequestHandler().hasMediaRequestCallbackCount()
1908                     ? mCarAudioServiceProtoDump.getMediaRequestHandler()
1909                     .getMediaRequestCallbackCount() : 0;
1910         }
1911         Matcher matchCount = PRIMARY_ZONE_MEDIA_REQUEST_APPROVERS_PATTERN
1912                 .matcher(mCarAudioServiceDump);
1913         assertWithMessage("No Car Audio Media in dump").that(matchCount.find()).isTrue();
1914         return Integer.parseInt(matchCount.group(1));
1915     }
1916 
assumeNoPrimaryZoneAudioMediaApprovers()1917     private void assumeNoPrimaryZoneAudioMediaApprovers() {
1918         assumeTrue("Primary zone audio media approvers must be empty",
1919                 getNumberOfPrimaryZoneAudioMediaCallbacks() == 0);
1920     }
1921 
setUpDefaultCarAudioConfigurationForZone(int zoneId)1922     private void setUpDefaultCarAudioConfigurationForZone(int zoneId) throws Exception {
1923         CarAudioZoneConfigInfo current =
1924                 mCarAudioManager.getCurrentAudioZoneConfigInfo(zoneId);
1925         if (current != null && current.isDefault()) {
1926             return;
1927         }
1928 
1929         CarAudioZoneConfigInfo defaultConfig =
1930                 mCarAudioManager.getAudioZoneConfigInfos(zoneId).stream()
1931                         .filter(CarAudioZoneConfigInfo::isDefault).findFirst().orElseThrow();
1932         Executor callbackExecutor = Executors.newFixedThreadPool(1);
1933         TestSwitchAudioZoneConfigCallback callback = new TestSwitchAudioZoneConfigCallback();
1934         mCarAudioManager.switchAudioZoneToConfig(defaultConfig, callbackExecutor, callback);
1935         callback.receivedApproval();
1936     }
1937 
assumeDynamicRoutingIsEnabled()1938     private void assumeDynamicRoutingIsEnabled() {
1939         assumeTrue("Requires dynamic audio routing",
1940                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING));
1941     }
1942 
assumeDynamicRoutingIsDisabled()1943     private void assumeDynamicRoutingIsDisabled() {
1944         assumeFalse("Requires dynamic audio routing disabled",
1945                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING));
1946     }
1947 
assumeOemServiceIsDisabled()1948     private void assumeOemServiceIsDisabled() {
1949         assumeFalse("Requires OEM service disabled",
1950                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_OEM_AUDIO_SERVICE));
1951     }
1952 
assumeVolumeGroupMutingIsEnabled()1953     private void assumeVolumeGroupMutingIsEnabled() {
1954         assumeTrue("Requires volume group muting",
1955                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING));
1956     }
1957 
assumeVolumeGroupMutingIsDisabled()1958     private void assumeVolumeGroupMutingIsDisabled() {
1959         assumeFalse("Requires volume group muting disabled",
1960                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_MUTING));
1961     }
1962 
assumeAudioMirrorEnabled()1963     private void assumeAudioMirrorEnabled() {
1964         assumeTrue("Requires audio mirroring",
1965                 mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_AUDIO_MIRRORING));
1966     }
1967 
assumeVolumeGroupEventsIsEnabled()1968     private void assumeVolumeGroupEventsIsEnabled() {
1969         assumeTrue(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_EVENTS));
1970     }
1971 
assumeVolumeGroupEventsIsDisabled()1972     private void assumeVolumeGroupEventsIsDisabled() {
1973         assumeFalse(mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_VOLUME_GROUP_EVENTS));
1974     }
1975 
runWithCarControlAudioVolumePermission(Runnable runnable)1976     private void runWithCarControlAudioVolumePermission(Runnable runnable) {
1977         UI_AUTOMATION.adoptShellPermissionIdentity(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
1978         try {
1979             runnable.run();
1980         } finally {
1981             UI_AUTOMATION.dropShellPermissionIdentity();
1982         }
1983     }
1984 
injectKeyEvent(int keyCode)1985     private void injectKeyEvent(int keyCode) {
1986         long downTime = SystemClock.uptimeMillis();
1987         KeyEvent volumeDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
1988             keyCode, 0);
1989         UI_AUTOMATION.injectInputEvent(volumeDown, true);
1990     }
1991 
injectVolumeDownKeyEvent()1992     private void injectVolumeDownKeyEvent() {
1993         injectKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN);
1994     }
1995 
injectVolumeUpKeyEvent()1996     private void injectVolumeUpKeyEvent() {
1997         injectKeyEvent(KeyEvent.KEYCODE_VOLUME_UP);
1998     }
1999 
injectVolumeMuteKeyEvent()2000     private void injectVolumeMuteKeyEvent() {
2001         injectKeyEvent(KeyEvent.KEYCODE_VOLUME_MUTE);
2002     }
2003 
readFirstZoneAndVolumeGroup()2004     private void readFirstZoneAndVolumeGroup() {
2005         if (Flags.carDumpToProto()) {
2006             List<CarAudioZoneProto> zoneProtoList = mCarAudioServiceProtoDump
2007                     .getCarAudioZonesList();
2008             assertWithMessage("No CarAudioZone in proto dump").that(zoneProtoList)
2009                     .isNotEmpty();
2010             mZoneId = zoneProtoList.get(0).getId();
2011             readFirstVolumeGroupAndVolumeGroupCountFromProto(zoneProtoList.get(0));
2012             return;
2013         }
2014         Matcher matchZone = ZONE_PATTERN.matcher(mCarAudioServiceDump);
2015         assertWithMessage("No CarAudioZone in dump").that(matchZone.find()).isTrue();
2016         mZoneId = Integer.parseInt(matchZone.group(1));
2017         mConfigId = Integer.parseInt(matchZone.group(3));
2018         readFirstVolumeGroupAndVolumeGroupCount(mZoneId, mConfigId);
2019     }
2020 
assumePrimaryZone()2021     private void assumePrimaryZone() {
2022         if (Flags.carDumpToProto()) {
2023             List<CarAudioZoneProto> zoneProtoList = mCarAudioServiceProtoDump
2024                     .getCarAudioZonesList();
2025             for (int zoneIndex = 0; zoneIndex < zoneProtoList.size(); zoneIndex++) {
2026                 int zoneId = zoneProtoList.get(zoneIndex).getId();
2027                 if (zoneId == PRIMARY_AUDIO_ZONE) {
2028                     mZoneId = zoneId;
2029                     readFirstVolumeGroupAndVolumeGroupCountFromProto(zoneProtoList.get(zoneIndex));
2030                     break;
2031                 }
2032             }
2033         } else {
2034             Matcher matchZone = ZONE_PATTERN.matcher(mCarAudioServiceDump);
2035             while (matchZone.find()) {
2036                 if (Integer.parseInt(matchZone.group(1)) == PRIMARY_AUDIO_ZONE) {
2037                     mZoneId = PRIMARY_AUDIO_ZONE;
2038                     mConfigId = Integer.parseInt(matchZone.group(3));
2039                     readFirstVolumeGroupAndVolumeGroupCount(mZoneId, mConfigId);
2040                     break;
2041                 }
2042             }
2043         }
2044         assumeTrue("Primary zone exists", mZoneId == PRIMARY_AUDIO_ZONE);
2045     }
2046 
readFirstVolumeGroupAndVolumeGroupCount(int zoneId, int currentConfigId)2047     private void readFirstVolumeGroupAndVolumeGroupCount(int zoneId, int currentConfigId) {
2048         Matcher matchGroup = VOLUME_GROUP_PATTERN.matcher(mCarAudioServiceDump);
2049         boolean findVolumeGroup = false;
2050         mVolumeGroupCount = 0;
2051         while (matchGroup.find()) {
2052             if (Integer.parseInt(matchGroup.group(3)) == zoneId
2053                     && Integer.parseInt(matchGroup.group(4)) == currentConfigId) {
2054                 if (!findVolumeGroup) {
2055                     mVolumeGroupId = Integer.parseInt(matchGroup.group(1));
2056                     findVolumeGroup = true;
2057                 }
2058                 mVolumeGroupCount++;
2059             }
2060         }
2061         assertWithMessage("No CarVolumeGroup in dump").that(findVolumeGroup).isTrue();
2062     }
2063 
readFirstVolumeGroupAndVolumeGroupCountFromProto(CarAudioZoneProto zoneProto)2064     private void readFirstVolumeGroupAndVolumeGroupCountFromProto(CarAudioZoneProto zoneProto) {
2065         mConfigId = zoneProto.getCurrentZoneConfigId();
2066         List<CarAudioZoneConfigProto> zoneConfigProtoList = zoneProto.getZoneConfigsList();
2067         for (int configIndex = 0; configIndex < zoneConfigProtoList.size(); configIndex++) {
2068             if (zoneConfigProtoList.get(configIndex).getId() != mConfigId) {
2069                 continue;
2070             }
2071             List<CarVolumeGroupProto> volumeGroupProtoList = zoneConfigProtoList
2072                     .get(configIndex).getVolumeGroupsList();
2073             assertWithMessage("No CarVolumeGroup in dump").that(volumeGroupProtoList)
2074                     .isNotEmpty();
2075             mVolumeGroupCount = volumeGroupProtoList.size();
2076             mVolumeGroupId = volumeGroupProtoList.get(0).getId();
2077         }
2078     }
2079 
setVolumeGroupMute(int zoneId, int groupId, boolean mute)2080     private void setVolumeGroupMute(int zoneId, int groupId, boolean mute) {
2081         ShellUtils.runShellCommand("cmd car_service set-mute-car-volume-group %d %d %s",
2082                 zoneId, groupId, mute ? "mute" : "unmute");
2083     }
2084 
assertVolumeGroupIdForUsages(int zoneId, int volumeGroupId, int[] usages, String message)2085     private void assertVolumeGroupIdForUsages(int zoneId, int volumeGroupId, int[] usages,
2086             String message) {
2087         assertWithMessage("Usage for volume group %s in zone %s %s", volumeGroupId, zoneId,
2088                 message).that(usages).isNotEmpty();
2089         for (int i = 0; i < usages.length; i++) {
2090             assertWithMessage("Usage %s in volume group %s in zone %s %s", usages[i], volumeGroupId,
2091                     zoneId, message).that(mCarAudioManager.getVolumeGroupIdForUsage(zoneId,
2092                     usages[i])).isEqualTo(volumeGroupId);
2093         }
2094     }
2095 
2096     private static final class TestZoneConfigIdInfo {
2097         private final int mZoneId;
2098         private final int mConfigId;
2099         private final String mConfigName;
2100 
TestZoneConfigIdInfo(int zoneId, int configId, String configName)2101         TestZoneConfigIdInfo(int zoneId, int configId, String configName) {
2102             mZoneId = zoneId;
2103             mConfigId = configId;
2104             mConfigName = configName;
2105         }
2106 
2107         @Override
equals(Object o)2108         public boolean equals(Object o) {
2109             if (this == o) {
2110                 return true;
2111             }
2112 
2113             if (!(o instanceof TestZoneConfigIdInfo)) {
2114                 return false;
2115             }
2116 
2117             TestZoneConfigIdInfo that = (TestZoneConfigIdInfo) o;
2118 
2119             return mZoneId == that.mZoneId && mConfigId == that.mConfigId
2120                     && mConfigName.equals(that.mConfigName);
2121         }
2122 
getZoneConfigFromInfo(CarAudioZoneConfigInfo configInfo)2123         static TestZoneConfigIdInfo getZoneConfigFromInfo(CarAudioZoneConfigInfo configInfo) {
2124             return new TestZoneConfigIdInfo(configInfo.getZoneId(), configInfo.getConfigId(),
2125                     configInfo.getName());
2126         }
2127 
getZoneConfigListFromInfoList( List<CarAudioZoneConfigInfo> configInfo)2128         static List<TestZoneConfigIdInfo> getZoneConfigListFromInfoList(
2129                 List<CarAudioZoneConfigInfo> configInfo) {
2130             List<TestZoneConfigIdInfo> zoneConfigs = new ArrayList<>(configInfo.size());
2131             Log.e(TAG, "getZoneConfigListFromInfoList " + configInfo.size());
2132             for (int index = 0; index < configInfo.size(); index++) {
2133                 zoneConfigs.add(getZoneConfigFromInfo(configInfo.get(index)));
2134             }
2135             return zoneConfigs;
2136         }
2137     }
2138 
2139     private static final class TestPrimaryZoneMediaAudioRequestStatusCallback implements
2140             PrimaryZoneMediaAudioRequestCallback {
2141 
2142         private final CountDownLatch mRequestAudioLatch = new CountDownLatch(1);
2143         private long mRequestId;
2144         private OccupantZoneInfo mOccupantZoneInfo;
2145 
2146         @Override
onRequestMediaOnPrimaryZone(OccupantZoneInfo info, long requestId)2147         public void onRequestMediaOnPrimaryZone(OccupantZoneInfo info, long requestId) {
2148             mOccupantZoneInfo = info;
2149             mRequestId = requestId;
2150             Log.i(TAG, "onRequestMediaOnPrimaryZone info " + info + " request id " + requestId);
2151             mRequestAudioLatch.countDown();
2152         }
2153 
2154         @Override
onMediaAudioRequestStatusChanged(OccupantZoneInfo info, long requestId, int status)2155         public void onMediaAudioRequestStatusChanged(OccupantZoneInfo info,
2156                 long requestId, int status) {
2157         }
2158 
receivedMediaRequest()2159         void receivedMediaRequest() throws InterruptedException {
2160             await(mRequestAudioLatch, WAIT_TIMEOUT_MS);
2161         }
2162     }
2163 
2164     private static final class TestMediaAudioRequestStatusCallback implements
2165             MediaAudioRequestStatusCallback {
2166 
2167         private CountDownLatch mRequestAudioLatch = new CountDownLatch(1);
2168         private long mRequestId;
2169         private OccupantZoneInfo mOccupantZoneInfo;
2170         private int mStatus;
2171 
receivedApproval()2172         void receivedApproval() throws InterruptedException {
2173             await(mRequestAudioLatch, WAIT_TIMEOUT_MS);
2174         }
2175 
reset()2176         void reset() {
2177             mRequestAudioLatch = new CountDownLatch(1);
2178             mRequestId = INVALID_REQUEST_ID;
2179             mOccupantZoneInfo = null;
2180             mStatus = 0;
2181         }
2182 
2183         @Override
onMediaAudioRequestStatusChanged(OccupantZoneInfo info, long requestId, int status)2184         public void onMediaAudioRequestStatusChanged(OccupantZoneInfo info,
2185                 long requestId, int status) {
2186             mOccupantZoneInfo = info;
2187             mRequestId = requestId;
2188             mStatus = status;
2189             Log.i(TAG, "onMediaAudioRequestStatusChanged info " + info + " request id "
2190                     + requestId + " status " + status);
2191             mRequestAudioLatch.countDown();
2192         }
2193     }
2194 
2195     private static final class TestSwitchAudioZoneConfigCallback implements
2196             SwitchAudioZoneConfigCallback {
2197 
2198         private CountDownLatch mRequestAudioLatch = new CountDownLatch(1);
2199         private CarAudioZoneConfigInfo mZoneConfigInfo;
2200         private boolean mIsSuccessful;
2201 
receivedApproval()2202         void receivedApproval() throws InterruptedException {
2203             await(mRequestAudioLatch, WAIT_TIMEOUT_MS);
2204         }
2205 
reset()2206         void reset() {
2207             mRequestAudioLatch = new CountDownLatch(1);
2208             mZoneConfigInfo = null;
2209             mIsSuccessful = false;
2210         }
2211 
2212         @Override
onAudioZoneConfigSwitched(CarAudioZoneConfigInfo zoneConfigInfo, boolean isSuccessful)2213         public void onAudioZoneConfigSwitched(CarAudioZoneConfigInfo zoneConfigInfo,
2214                 boolean isSuccessful) {
2215             mZoneConfigInfo = zoneConfigInfo;
2216             mIsSuccessful = isSuccessful;
2217             Log.i(TAG, "onAudioZoneConfigSwitched zoneConfig " + zoneConfigInfo + " is successful? "
2218                     + isSuccessful);
2219             mRequestAudioLatch.countDown();
2220         }
2221     }
2222 
2223     private static final class TestAudioZonesMirrorStatusCallback implements
2224             AudioZonesMirrorStatusCallback {
2225 
2226         private CountDownLatch mRequestAudioLatch = new CountDownLatch(1);
2227         public List<Integer> mAudioZones;
2228         public int mStatus;
2229 
2230         @Override
onAudioZonesMirrorStatusChanged(List<Integer> mirroredAudioZones, int status)2231         public void onAudioZonesMirrorStatusChanged(List<Integer> mirroredAudioZones, int status) {
2232             mAudioZones = mirroredAudioZones;
2233             mStatus = status;
2234             Log.i(TAG, "onAudioZonesMirrorStatusChanged: audio zones " + mirroredAudioZones
2235                     + " status " + status);
2236             mRequestAudioLatch.countDown();
2237         }
2238 
waitForCallback()2239         private void waitForCallback() throws InterruptedException {
2240             await(mRequestAudioLatch, WAIT_TIMEOUT_MS);
2241         }
2242 
reset()2243         public void reset() {
2244             mRequestAudioLatch = new CountDownLatch(1);
2245         }
2246     }
2247 
2248 }
2249