1 /* 2 * Copyright (C) 2020 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 com.android.car.audio; 18 19 import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; 20 import static android.media.AudioAttributes.USAGE_MEDIA; 21 import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION; 22 import static android.media.AudioManager.AUDIOFOCUS_GAIN; 23 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; 24 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 25 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED; 26 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 27 28 import static com.google.common.truth.Truth.assertThat; 29 30 import static org.mockito.ArgumentMatchers.anyInt; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.Mockito.never; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.car.media.CarAudioManager; 37 import android.content.ContentResolver; 38 import android.content.pm.PackageManager; 39 import android.media.AudioAttributes; 40 import android.media.AudioFocusInfo; 41 import android.media.AudioManager; 42 import android.media.audiopolicy.AudioPolicy; 43 import android.os.Build; 44 import android.os.Bundle; 45 import android.util.SparseArray; 46 47 import com.android.car.audio.CarZonesAudioFocus.CarFocusCallback; 48 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.mockito.ArgumentCaptor; 53 import org.mockito.Mock; 54 import org.mockito.junit.MockitoJUnitRunner; 55 56 import java.util.List; 57 58 @RunWith(MockitoJUnitRunner.class) 59 public class CarZonesAudioFocusTest { 60 private static final String MEDIA_CLIENT_ID = "media-client-id"; 61 private static final String NAVIGATION_CLIENT_ID = "nav-client-id"; 62 private static final String CALL_CLIENT_ID = "call-client-id"; 63 private static final String PACKAGE_NAME = "com.android.car.audio"; 64 private static final int AUDIOFOCUS_FLAG = 0; 65 private static final int PRIMARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE; 66 private static final int SECONDARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 1; 67 private static final int MEDIA_CLIENT_UID_1 = 1086753; 68 private static final int MEDIA_CLIENT_UID_2 = 1000009; 69 private static final int NAVIGATION_CLIENT_UID = 1010101; 70 private static final int TEST_USER_ID = 10; 71 private static final int CALL_CLIENT_UID = 1086753; 72 73 @Mock 74 private AudioManager mMockAudioManager; 75 @Mock 76 private AudioPolicy mAudioPolicy; 77 @Mock 78 private CarAudioService mCarAudioService; 79 @Mock 80 private ContentResolver mContentResolver; 81 @Mock 82 private CarAudioSettings mCarAudioSettings; 83 @Mock 84 private CarFocusCallback mMockCarFocusCallback; 85 @Mock 86 private PackageManager mMockPackageManager; 87 88 private SparseArray<CarAudioZone> mCarAudioZones; 89 90 @Before setUp()91 public void setUp() { 92 mCarAudioZones = generateAudioZones(); 93 when(mCarAudioService.getZoneIdForUid(MEDIA_CLIENT_UID_1)).thenReturn(PRIMARY_ZONE_ID); 94 } 95 96 @Test onAudioFocusRequest_withNoCurrentFocusHolder_requestGranted()97 public void onAudioFocusRequest_withNoCurrentFocusHolder_requestGranted() { 98 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 99 AudioFocusInfo audioFocusInfo = generateMediaRequestForPrimaryZone(); 100 101 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfo); 102 103 verify(mMockAudioManager, never()) 104 .dispatchAudioFocusChange(eq(audioFocusInfo), anyInt(), eq(mAudioPolicy)); 105 } 106 107 @Test onAudioFocusRequest_forTwoDifferentZones_requestGranted()108 public void onAudioFocusRequest_forTwoDifferentZones_requestGranted() { 109 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 110 AudioFocusInfo audioFocusInfoClient1 = generateMediaRequestForPrimaryZone(); 111 112 113 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoClient1); 114 115 when(mCarAudioService.getZoneIdForUid(MEDIA_CLIENT_UID_2)).thenReturn(SECONDARY_ZONE_ID); 116 AudioFocusInfo audioFocusInfoClient2 = new AudioFocusInfoBuilder().setUsage(USAGE_MEDIA) 117 .setClientId(MEDIA_CLIENT_ID).setGainRequest(AUDIOFOCUS_GAIN) 118 .setClientUid(MEDIA_CLIENT_UID_2).createAudioFocusInfo(); 119 120 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoClient2); 121 122 verify(mMockAudioManager, never()) 123 .dispatchAudioFocusChange(eq(audioFocusInfoClient1), anyInt(), eq(mAudioPolicy)); 124 125 verify(mMockAudioManager, never()) 126 .dispatchAudioFocusChange(eq(audioFocusInfoClient2), anyInt(), eq(mAudioPolicy)); 127 } 128 129 @Test onAudioFocusRequest_forTwoDifferentZones_abandonInOne_requestGranted()130 public void onAudioFocusRequest_forTwoDifferentZones_abandonInOne_requestGranted() { 131 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 132 AudioFocusInfo audioFocusInfoClient1 = generateMediaRequestForPrimaryZone(); 133 134 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoClient1); 135 136 when(mCarAudioService.getZoneIdForUid(MEDIA_CLIENT_UID_2)).thenReturn(SECONDARY_ZONE_ID); 137 AudioFocusInfo audioFocusInfoClient2 = new AudioFocusInfoBuilder().setUsage(USAGE_MEDIA) 138 .setClientId(MEDIA_CLIENT_ID).setGainRequest(AUDIOFOCUS_GAIN) 139 .setClientUid(MEDIA_CLIENT_UID_2).createAudioFocusInfo(); 140 141 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoClient2); 142 143 carZonesAudioFocus.onAudioFocusAbandon(audioFocusInfoClient2); 144 145 verify(mMockAudioManager, never()) 146 .dispatchAudioFocusChange(eq(audioFocusInfoClient1), anyInt(), eq(mAudioPolicy)); 147 148 verify(mMockAudioManager, never()) 149 .dispatchAudioFocusChange(eq(audioFocusInfoClient2), anyInt(), eq(mAudioPolicy)); 150 } 151 152 @Test onAudioFocusRequest_withBundleFocusRequest_requestGranted()153 public void onAudioFocusRequest_withBundleFocusRequest_requestGranted() { 154 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 155 when(mCarAudioService.isAudioZoneIdValid(PRIMARY_ZONE_ID)).thenReturn(true); 156 157 Bundle bundle = new Bundle(); 158 bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID, 159 PRIMARY_ZONE_ID); 160 AudioFocusInfo audioFocusInfoClient = new AudioFocusInfoBuilder().setUsage(USAGE_MEDIA) 161 .setClientId(MEDIA_CLIENT_ID).setGainRequest(AUDIOFOCUS_GAIN) 162 .setClientUid(MEDIA_CLIENT_UID_1).setBundle(bundle).createAudioFocusInfo(); 163 164 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoClient); 165 166 verify(mMockAudioManager, never()) 167 .dispatchAudioFocusChange(eq(audioFocusInfoClient), anyInt(), eq(mAudioPolicy)); 168 } 169 170 @Test onAudioFocusRequest_repeatForSameZone_requestGranted()171 public void onAudioFocusRequest_repeatForSameZone_requestGranted() { 172 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 173 AudioFocusInfo audioFocusInfoMediaClient = generateMediaRequestForPrimaryZone(); 174 175 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoMediaClient); 176 177 when(mCarAudioService.getZoneIdForUid(NAVIGATION_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 178 AudioFocusInfo audioFocusInfoNavClient = 179 new AudioFocusInfoBuilder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) 180 .setClientId(NAVIGATION_CLIENT_ID).setGainRequest(AUDIOFOCUS_GAIN) 181 .setClientUid(NAVIGATION_CLIENT_UID).createAudioFocusInfo(); 182 183 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoNavClient); 184 185 verify(mMockAudioManager) 186 .dispatchAudioFocusChange(eq(audioFocusInfoMediaClient), 187 anyInt(), eq(mAudioPolicy)); 188 189 verify(mMockAudioManager, never()) 190 .dispatchAudioFocusChange(eq(audioFocusInfoNavClient), 191 anyInt(), eq(mAudioPolicy)); 192 } 193 194 @Test onAudioFocusRequest_forNavigationWhileOnCall_rejectNavOnCall_requestFailed()195 public void onAudioFocusRequest_forNavigationWhileOnCall_rejectNavOnCall_requestFailed() { 196 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 197 when(mCarAudioService.isAudioZoneIdValid(PRIMARY_ZONE_ID)).thenReturn(true); 198 setUpRejectNavigationOnCallValue(true); 199 carZonesAudioFocus.updateUserForZoneId(PRIMARY_ZONE_ID, TEST_USER_ID); 200 201 when(mCarAudioService.getZoneIdForUid(CALL_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 202 AudioFocusInfo audioFocusInfoCallClient = new AudioFocusInfoBuilder() 203 .setUsage(USAGE_VOICE_COMMUNICATION) 204 .setGainRequest(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 205 .setClientId(CALL_CLIENT_ID) 206 .setClientUid(CALL_CLIENT_UID).createAudioFocusInfo(); 207 208 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoCallClient); 209 210 when(mCarAudioService.getZoneIdForUid(NAVIGATION_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 211 AudioFocusInfo audioFocusInfoNavClient = 212 new AudioFocusInfoBuilder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) 213 .setGainRequest(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 214 .setClientId(NAVIGATION_CLIENT_ID) 215 .setClientUid(NAVIGATION_CLIENT_UID).createAudioFocusInfo(); 216 217 carZonesAudioFocus 218 .onAudioFocusRequest(audioFocusInfoNavClient, AUDIOFOCUS_REQUEST_GRANTED); 219 verify(mMockAudioManager).setFocusRequestResult(audioFocusInfoNavClient, 220 AUDIOFOCUS_REQUEST_FAILED, mAudioPolicy); 221 } 222 223 @Test onAudioFocusRequest_forNavigationWhileOnCall_noRejectNavOnCall_requestSucceeds()224 public void onAudioFocusRequest_forNavigationWhileOnCall_noRejectNavOnCall_requestSucceeds() { 225 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false); 226 when(mCarAudioService.isAudioZoneIdValid(PRIMARY_ZONE_ID)).thenReturn(true); 227 setUpRejectNavigationOnCallValue(false); 228 carZonesAudioFocus.updateUserForZoneId(PRIMARY_ZONE_ID, TEST_USER_ID); 229 230 when(mCarAudioService.getZoneIdForUid(CALL_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 231 AudioFocusInfo audioFocusInfoCallClient = new AudioFocusInfoBuilder() 232 .setUsage(USAGE_VOICE_COMMUNICATION) 233 .setGainRequest(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 234 .setClientId(CALL_CLIENT_ID) 235 .setClientUid(CALL_CLIENT_UID).createAudioFocusInfo(); 236 237 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoCallClient); 238 239 when(mCarAudioService.getZoneIdForUid(NAVIGATION_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 240 AudioFocusInfo audioFocusInfoNavClient = 241 new AudioFocusInfoBuilder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) 242 .setGainRequest(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 243 .setClientId(NAVIGATION_CLIENT_ID) 244 .setClientUid(NAVIGATION_CLIENT_UID).createAudioFocusInfo(); 245 246 247 carZonesAudioFocus 248 .onAudioFocusRequest(audioFocusInfoNavClient, AUDIOFOCUS_REQUEST_GRANTED); 249 verify(mMockAudioManager).setFocusRequestResult(audioFocusInfoNavClient, 250 AUDIOFOCUS_REQUEST_GRANTED, mAudioPolicy); 251 } 252 253 @Test onAudioFocusRequest_forMediaWhileOnCall_withDelayedEnable_delayedSucceeds()254 public void onAudioFocusRequest_forMediaWhileOnCall_withDelayedEnable_delayedSucceeds() { 255 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(true); 256 when(mCarAudioService.isAudioZoneIdValid(PRIMARY_ZONE_ID)).thenReturn(true); 257 258 when(mCarAudioService.getZoneIdForUid(CALL_CLIENT_UID)).thenReturn(PRIMARY_ZONE_ID); 259 AudioFocusInfo audioFocusInfoCallClient = new AudioFocusInfoBuilder() 260 .setUsage(USAGE_VOICE_COMMUNICATION) 261 .setGainRequest(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 262 .setClientId(CALL_CLIENT_ID) 263 .setClientUid(CALL_CLIENT_UID).createAudioFocusInfo(); 264 265 requestFocusAndAssertIfRequestNotGranted(carZonesAudioFocus, audioFocusInfoCallClient); 266 267 AudioFocusInfo audioFocusMediaClient = 268 new AudioFocusInfoBuilder().setUsage(USAGE_MEDIA) 269 .setGainRequest(AUDIOFOCUS_GAIN) 270 .setClientId(MEDIA_CLIENT_ID) 271 .setDelayedFocusRequestEnable(true) 272 .setClientUid(MEDIA_CLIENT_UID_1).createAudioFocusInfo(); 273 274 275 carZonesAudioFocus 276 .onAudioFocusRequest(audioFocusMediaClient, AUDIOFOCUS_REQUEST_GRANTED); 277 verify(mMockAudioManager).setFocusRequestResult(audioFocusMediaClient, 278 AUDIOFOCUS_REQUEST_DELAYED, mAudioPolicy); 279 } 280 281 @Test onAudioFocusRequest_notifiesFocusCallback()282 public void onAudioFocusRequest_notifiesFocusCallback() { 283 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(true); 284 AudioFocusInfo audioFocusInfo = generateMediaRequestForPrimaryZone(); 285 286 carZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED); 287 288 ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor = 289 ArgumentCaptor.forClass(SparseArray.class); 290 verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}), 291 focusHoldersCaptor.capture()); 292 assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID)) 293 .containsExactly(audioFocusInfo); 294 } 295 296 @Test onAudioFocusAbandon_notifiesFocusCallback()297 public void onAudioFocusAbandon_notifiesFocusCallback() { 298 CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(true); 299 AudioFocusInfo audioFocusInfo = generateMediaRequestForPrimaryZone(); 300 301 carZonesAudioFocus.onAudioFocusAbandon(audioFocusInfo); 302 303 ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor = 304 ArgumentCaptor.forClass(SparseArray.class); 305 verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}), 306 focusHoldersCaptor.capture()); 307 assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID)).isEmpty(); 308 } 309 generateMediaRequestForPrimaryZone()310 private AudioFocusInfo generateMediaRequestForPrimaryZone() { 311 return new AudioFocusInfoBuilder().setUsage(USAGE_MEDIA) 312 .setClientId(MEDIA_CLIENT_ID).setGainRequest(AUDIOFOCUS_GAIN) 313 .setClientUid(MEDIA_CLIENT_UID_1).createAudioFocusInfo(); 314 } 315 requestFocusAndAssertIfRequestNotGranted(CarZonesAudioFocus carZonesAudioFocus, AudioFocusInfo audioFocusClient)316 private void requestFocusAndAssertIfRequestNotGranted(CarZonesAudioFocus carZonesAudioFocus, 317 AudioFocusInfo audioFocusClient) { 318 requestFocusAndAssertIfRequestDiffers(carZonesAudioFocus, audioFocusClient, 319 AUDIOFOCUS_REQUEST_GRANTED); 320 } 321 requestFocusAndAssertIfRequestDiffers(CarZonesAudioFocus carZonesAudioFocus, AudioFocusInfo audioFocusClient, int expectedAudioFocusResults)322 private void requestFocusAndAssertIfRequestDiffers(CarZonesAudioFocus carZonesAudioFocus, 323 AudioFocusInfo audioFocusClient, int expectedAudioFocusResults) { 324 carZonesAudioFocus.onAudioFocusRequest(audioFocusClient, expectedAudioFocusResults); 325 verify(mMockAudioManager) 326 .setFocusRequestResult(audioFocusClient, expectedAudioFocusResults, mAudioPolicy); 327 } 328 generateAudioZones()329 private SparseArray<CarAudioZone> generateAudioZones() { 330 SparseArray<CarAudioZone> zones = new SparseArray<>(); 331 zones.put(PRIMARY_ZONE_ID, new CarAudioZone(PRIMARY_ZONE_ID, "Primary zone")); 332 zones.put(SECONDARY_ZONE_ID, new CarAudioZone(SECONDARY_ZONE_ID, "Secondary zone")); 333 return zones; 334 } 335 getCarZonesAudioFocus(boolean enableDelayedFocus)336 private CarZonesAudioFocus getCarZonesAudioFocus(boolean enableDelayedFocus) { 337 CarZonesAudioFocus carZonesAudioFocus = 338 CarZonesAudioFocus.createCarZonesAudioFocus(mMockAudioManager, mMockPackageManager, 339 mCarAudioZones, 340 mCarAudioSettings, enableDelayedFocus, mMockCarFocusCallback); 341 carZonesAudioFocus.setOwningPolicy(mCarAudioService, mAudioPolicy); 342 343 344 return carZonesAudioFocus; 345 } 346 setUpRejectNavigationOnCallValue(boolean rejectNavigationOnCall)347 private void setUpRejectNavigationOnCallValue(boolean rejectNavigationOnCall) { 348 when(mCarAudioSettings.getContentResolver()).thenReturn(mContentResolver); 349 when(mCarAudioSettings.isRejectNavigationOnCallEnabledInSettings(TEST_USER_ID)) 350 .thenReturn(rejectNavigationOnCall); 351 } 352 353 public class AudioFocusInfoBuilder { 354 private int mUsage; 355 private int mClientUid; 356 private String mClientId; 357 private int mGainRequest; 358 private String mPackageName = PACKAGE_NAME; 359 private Bundle mBundle = null; 360 private int mLossReceived = AudioManager.AUDIOFOCUS_NONE; 361 private int mFlags = AUDIOFOCUS_FLAG; 362 private int mSdk = Build.VERSION.SDK_INT; 363 private boolean mDelayedFocusRequestEnabled = false; 364 setUsage(int usage)365 public AudioFocusInfoBuilder setUsage(int usage) { 366 mUsage = usage; 367 return this; 368 } 369 setClientUid(int clientUid)370 public AudioFocusInfoBuilder setClientUid(int clientUid) { 371 mClientUid = clientUid; 372 return this; 373 } 374 setClientId(String clientId)375 public AudioFocusInfoBuilder setClientId(String clientId) { 376 mClientId = clientId; 377 return this; 378 } 379 setPackageName(String packageName)380 public AudioFocusInfoBuilder setPackageName(String packageName) { 381 mPackageName = packageName; 382 return this; 383 } 384 setGainRequest(int gainRequest)385 public AudioFocusInfoBuilder setGainRequest(int gainRequest) { 386 mGainRequest = gainRequest; 387 return this; 388 } 389 setLossReceived(int lossReceived)390 public AudioFocusInfoBuilder setLossReceived(int lossReceived) { 391 mLossReceived = lossReceived; 392 return this; 393 } 394 setSdk(int sdk)395 public AudioFocusInfoBuilder setSdk(int sdk) { 396 mSdk = sdk; 397 return this; 398 } 399 setBundle(Bundle bundle)400 public AudioFocusInfoBuilder setBundle(Bundle bundle) { 401 mBundle = bundle; 402 return this; 403 } 404 setDelayedFocusRequestEnable(boolean b)405 public AudioFocusInfoBuilder setDelayedFocusRequestEnable(boolean b) { 406 mDelayedFocusRequestEnabled = b; 407 return this; 408 } 409 createAudioFocusInfo()410 public AudioFocusInfo createAudioFocusInfo() { 411 AudioAttributes.Builder builder = new AudioAttributes.Builder().setUsage(mUsage); 412 if (mBundle != null) { 413 builder = builder.addBundle(mBundle); 414 } 415 if (mDelayedFocusRequestEnabled) { 416 mFlags = mFlags | AudioManager.AUDIOFOCUS_FLAG_DELAY_OK; 417 } 418 AudioAttributes audioAttributes = builder.build(); 419 return new AudioFocusInfo(audioAttributes, mClientUid, mClientId, 420 mPackageName, mGainRequest, mLossReceived, mFlags, mSdk); 421 } 422 } 423 } 424