1 /* 2 * Copyright (C) 2023 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.server.media; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.mockito.Mockito.anyInt; 23 import static org.mockito.Mockito.when; 24 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.media.AudioManager; 28 import android.media.AudioRoutesInfo; 29 import android.media.IAudioRoutesObserver; 30 import android.media.MediaRoute2Info; 31 import android.os.RemoteException; 32 33 import com.android.internal.R; 34 import com.android.server.audio.AudioService; 35 36 import org.junit.Before; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.JUnit4; 40 import org.mockito.ArgumentCaptor; 41 import org.mockito.Captor; 42 import org.mockito.Mock; 43 import org.mockito.MockitoAnnotations; 44 45 @RunWith(JUnit4.class) 46 public class AudioPoliciesDeviceRouteControllerTest { 47 48 private static final String ROUTE_NAME_DEFAULT = "default"; 49 private static final String ROUTE_NAME_DOCK = "dock"; 50 private static final String ROUTE_NAME_HEADPHONES = "headphones"; 51 52 private static final int VOLUME_SAMPLE_1 = 25; 53 54 @Mock 55 private Context mContext; 56 @Mock 57 private Resources mResources; 58 @Mock 59 private AudioManager mAudioManager; 60 @Mock 61 private AudioService mAudioService; 62 @Mock 63 private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; 64 65 @Captor 66 private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor; 67 68 private AudioPoliciesDeviceRouteController mController; 69 70 private IAudioRoutesObserver.Stub mAudioRoutesObserver; 71 72 @Before setUp()73 public void setUp() { 74 MockitoAnnotations.initMocks(this); 75 76 when(mContext.getResources()).thenReturn(mResources); 77 when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT); 78 79 // Setting built-in speaker as default speaker. 80 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 81 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER; 82 when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture())) 83 .thenReturn(audioRoutesInfo); 84 85 mController = new AudioPoliciesDeviceRouteController( 86 mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener); 87 88 mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue(); 89 } 90 91 @Test getDeviceRoute_noSelectedRoutes_returnsDefaultDevice()92 public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() { 93 MediaRoute2Info route2Info = mController.getDeviceRoute(); 94 95 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT); 96 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); 97 } 98 99 @Test getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService()100 public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() { 101 when(mResources.getText(R.string.default_audio_route_name_headphones)) 102 .thenReturn(ROUTE_NAME_HEADPHONES); 103 104 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 105 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 106 callAudioRoutesObserver(audioRoutesInfo); 107 108 MediaRoute2Info route2Info = mController.getDeviceRoute(); 109 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); 110 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); 111 } 112 113 @Test getDeviceRoute_selectDevice_returnsSelectedRoute()114 public void getDeviceRoute_selectDevice_returnsSelectedRoute() { 115 when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) 116 .thenReturn(ROUTE_NAME_DOCK); 117 118 mController.selectRoute(MediaRoute2Info.TYPE_DOCK); 119 120 MediaRoute2Info route2Info = mController.getDeviceRoute(); 121 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); 122 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); 123 } 124 125 @Test getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute()126 public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() { 127 when(mResources.getText(R.string.default_audio_route_name_headphones)) 128 .thenReturn(ROUTE_NAME_HEADPHONES); 129 when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) 130 .thenReturn(ROUTE_NAME_DOCK); 131 132 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 133 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 134 callAudioRoutesObserver(audioRoutesInfo); 135 136 mController.selectRoute(MediaRoute2Info.TYPE_DOCK); 137 138 MediaRoute2Info route2Info = mController.getDeviceRoute(); 139 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK); 140 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); 141 } 142 143 @Test getDeviceRoute_unselectRoute_returnsAudioServiceRoute()144 public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() { 145 when(mResources.getText(R.string.default_audio_route_name_headphones)) 146 .thenReturn(ROUTE_NAME_HEADPHONES); 147 when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) 148 .thenReturn(ROUTE_NAME_DOCK); 149 150 mController.selectRoute(MediaRoute2Info.TYPE_DOCK); 151 152 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 153 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 154 callAudioRoutesObserver(audioRoutesInfo); 155 156 mController.selectRoute(null); 157 158 MediaRoute2Info route2Info = mController.getDeviceRoute(); 159 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); 160 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); 161 } 162 163 @Test getDeviceRoute_selectRouteFails_returnsAudioServiceRoute()164 public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() { 165 when(mResources.getText(R.string.default_audio_route_name_headphones)) 166 .thenReturn(ROUTE_NAME_HEADPHONES); 167 168 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 169 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 170 callAudioRoutesObserver(audioRoutesInfo); 171 172 mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); 173 174 MediaRoute2Info route2Info = mController.getDeviceRoute(); 175 assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES); 176 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); 177 } 178 179 @Test selectRoute_selectWiredRoute_returnsTrue()180 public void selectRoute_selectWiredRoute_returnsTrue() { 181 assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue(); 182 } 183 184 @Test selectRoute_selectBluetoothRoute_returnsFalse()185 public void selectRoute_selectBluetoothRoute_returnsFalse() { 186 assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse(); 187 } 188 189 @Test selectRoute_unselectRoute_returnsTrue()190 public void selectRoute_unselectRoute_returnsTrue() { 191 assertThat(mController.selectRoute(null)).isTrue(); 192 } 193 194 @Test updateVolume_noSelectedRoute_deviceRouteVolumeChanged()195 public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() { 196 when(mResources.getText(R.string.default_audio_route_name_headphones)) 197 .thenReturn(ROUTE_NAME_HEADPHONES); 198 199 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 200 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 201 callAudioRoutesObserver(audioRoutesInfo); 202 203 mController.updateVolume(VOLUME_SAMPLE_1); 204 205 MediaRoute2Info route2Info = mController.getDeviceRoute(); 206 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES); 207 assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); 208 } 209 210 @Test updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged()211 public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() { 212 when(mResources.getText(R.string.default_audio_route_name_headphones)) 213 .thenReturn(ROUTE_NAME_HEADPHONES); 214 when(mResources.getText(R.string.default_audio_route_name_dock_speakers)) 215 .thenReturn(ROUTE_NAME_DOCK); 216 217 AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo(); 218 audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES; 219 callAudioRoutesObserver(audioRoutesInfo); 220 221 mController.updateVolume(VOLUME_SAMPLE_1); 222 223 mController.selectRoute(MediaRoute2Info.TYPE_DOCK); 224 225 MediaRoute2Info route2Info = mController.getDeviceRoute(); 226 assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK); 227 assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1); 228 } 229 230 /** 231 * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)} 232 * from {@link AudioService}. This happens when there is a wired route change, 233 * like a wired headset being connected. 234 * 235 * @param audioRoutesInfo updated state of connected wired device 236 */ callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo)237 private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) { 238 try { 239 // this is a captured observer implementation 240 // from WiredRoutesController's AudioService#startWatchingRoutes call 241 mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo); 242 } catch (RemoteException exception) { 243 // Should not happen since the object is mocked. 244 assertWithMessage("An unexpected RemoteException happened.").fail(); 245 } 246 } 247 } 248