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