1 /*
2  * Copyright (C) 2017 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.bluetooth.a2dpsink;
18 
19 import static org.mockito.Mockito.*;
20 
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.content.res.Resources;
24 import android.media.AudioManager;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 
28 import androidx.test.InstrumentationRegistry;
29 import androidx.test.filters.MediumTest;
30 import androidx.test.runner.AndroidJUnit4;
31 
32 import com.android.bluetooth.R;
33 
34 import org.junit.Assume;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.mockito.Mock;
39 import org.mockito.MockitoAnnotations;
40 
41 @MediumTest
42 @RunWith(AndroidJUnit4.class)
43 public class A2dpSinkStreamHandlerTest {
44     private static final int DUCK_PERCENT = 75;
45     private HandlerThread mHandlerThread;
46     private A2dpSinkStreamHandler mStreamHandler;
47     private Context mTargetContext;
48 
49     @Mock private Context mMockContext;
50 
51     @Mock private A2dpSinkService mMockA2dpSink;
52 
53     @Mock private AudioManager mMockAudioManager;
54 
55     @Mock private Resources mMockResources;
56 
57     @Mock private PackageManager mMockPackageManager;
58 
59     @Before
setUp()60     public void setUp() {
61         mTargetContext = InstrumentationRegistry.getTargetContext();
62         Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
63                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
64         MockitoAnnotations.initMocks(this);
65         // Mock the looper
66         if (Looper.myLooper() == null) {
67             Looper.prepare();
68         }
69 
70         mHandlerThread = new HandlerThread("A2dpSinkStreamHandlerTest");
71         mHandlerThread.start();
72 
73         when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mMockAudioManager);
74         when(mMockContext.getResources()).thenReturn(mMockResources);
75         when(mMockResources.getInteger(anyInt())).thenReturn(DUCK_PERCENT);
76         when(mMockAudioManager.requestAudioFocus(any())).thenReturn(
77                 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
78         when(mMockAudioManager.abandonAudioFocus(any())).thenReturn(AudioManager.AUDIOFOCUS_GAIN);
79         doNothing().when(mMockA2dpSink).informAudioTrackGainNative(anyFloat());
80         when(mMockContext.getMainLooper()).thenReturn(mHandlerThread.getLooper());
81         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
82         when(mMockPackageManager.hasSystemFeature(any())).thenReturn(false);
83 
84         mStreamHandler = spy(new A2dpSinkStreamHandler(mMockA2dpSink, mMockContext));
85     }
86 
87     @Test
testSrcStart()88     public void testSrcStart() {
89         // Stream started without local play, expect no change in streaming.
90         mStreamHandler.handleMessage(
91                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START));
92         verify(mMockAudioManager, times(0)).requestAudioFocus(any());
93         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
94         verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
95     }
96 
97     @Test
testSrcStop()98     public void testSrcStop() {
99         // Stream stopped without local play, expect no change in streaming.
100         mStreamHandler.handleMessage(
101                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP));
102         verify(mMockAudioManager, times(0)).requestAudioFocus(any());
103         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
104         verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
105     }
106 
107     @Test
testSnkPlay()108     public void testSnkPlay() {
109         // Play was pressed locally, expect streaming to start.
110         mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY));
111         verify(mMockAudioManager, times(1)).requestAudioFocus(any());
112         verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(1);
113         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(1.0f);
114     }
115 
116     @Test
testSnkPause()117     public void testSnkPause() {
118         // Pause was pressed locally, expect streaming to stop.
119         mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE));
120         verify(mMockAudioManager, times(0)).requestAudioFocus(any());
121         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
122         verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
123     }
124 
125     @Test
testDisconnect()126     public void testDisconnect() {
127         // Remote device was disconnected, expect streaming to stop.
128         testSnkPlay();
129         mStreamHandler.handleMessage(
130                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DISCONNECT));
131         verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
132         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
133     }
134 
135     @Test
testSrcPlay()136     public void testSrcPlay() {
137         // Play was pressed remotely, expect no streaming due to lack of audio focus.
138         mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
139         verify(mMockAudioManager, times(0)).requestAudioFocus(any());
140         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
141         verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
142     }
143 
144     @Test
testSrcPlayIot()145     public void testSrcPlayIot() {
146         // Play was pressed remotely for an iot device, expect streaming to start.
147         when(mMockPackageManager.hasSystemFeature(any())).thenReturn(true);
148         mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
149         verify(mMockAudioManager, times(1)).requestAudioFocus(any());
150         verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(1);
151         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(1.0f);
152     }
153 
154     @Test
testSrcPause()155     public void testSrcPause() {
156         // Play was pressed locally, expect streaming to start.
157         mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
158         verify(mMockAudioManager, times(0)).requestAudioFocus(any());
159         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
160         verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
161     }
162 
163     @Test
testFocusGain()164     public void testFocusGain() {
165         // Focus was gained, expect streaming to resume.
166         testSnkPlay();
167         mStreamHandler.handleMessage(
168                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
169                         AudioManager.AUDIOFOCUS_GAIN));
170         verify(mMockAudioManager, times(1)).requestAudioFocus(any());
171         verify(mMockA2dpSink, times(2)).informAudioFocusStateNative(1);
172         verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f);
173     }
174 
175     @Test
testFocusTransientMayDuck()176     public void testFocusTransientMayDuck() {
177         // TransientMayDuck focus was gained, expect audio stream to duck.
178         testSnkPlay();
179         mStreamHandler.handleMessage(
180                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
181                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
182         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(DUCK_PERCENT / 100.0f);
183     }
184 
185     @Test
testFocusLostTransient()186     public void testFocusLostTransient() {
187         // Focus was lost transiently, expect streaming to stop.
188         testSnkPlay();
189         mStreamHandler.handleMessage(
190                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
191                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
192         verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
193         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
194         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
195     }
196 
197     @Test
testFocusRerequest()198     public void testFocusRerequest() {
199         // Focus was lost transiently, expect streaming to stop.
200         testSnkPlay();
201         mStreamHandler.handleMessage(
202                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
203                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
204         verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
205         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
206         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
207         mStreamHandler.handleMessage(
208                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true));
209         verify(mMockAudioManager, times(2)).requestAudioFocus(any());
210     }
211 
212     @Test
testFocusGainTransient()213     public void testFocusGainTransient() {
214         // Focus was lost then regained.
215         testSnkPlay();
216         mStreamHandler.handleMessage(
217                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
218                         AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
219         mStreamHandler.handleMessage(
220                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DELAYED_PAUSE));
221         mStreamHandler.handleMessage(
222                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
223                         AudioManager.AUDIOFOCUS_GAIN));
224         verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
225         verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
226         verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
227         verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f);
228     }
229 
230     @Test
testFocusLost()231     public void testFocusLost() {
232         // Focus was lost permanently, expect streaming to stop.
233         testSnkPlay();
234         mStreamHandler.handleMessage(
235                 mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
236                         AudioManager.AUDIOFOCUS_LOSS));
237         verify(mMockAudioManager, times(1)).abandonAudioFocus(any());
238         verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(0);
239     }
240 }
241