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