• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bluetooth.vc;
19 
20 import static org.mockito.Mockito.*;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothProfile;
25 import android.bluetooth.BluetoothUuid;
26 import android.bluetooth.BluetoothVolumeControl;
27 import android.bluetooth.IBluetoothVolumeControlCallback;
28 import android.content.AttributionSource;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.media.AudioManager;
34 import android.os.Binder;
35 import android.os.Looper;
36 import android.os.ParcelUuid;
37 
38 import androidx.test.InstrumentationRegistry;
39 import androidx.test.filters.MediumTest;
40 import androidx.test.rule.ServiceTestRule;
41 import androidx.test.runner.AndroidJUnit4;
42 
43 import com.android.bluetooth.btservice.AdapterService;
44 import com.android.bluetooth.btservice.ServiceFactory;
45 import com.android.bluetooth.btservice.storage.DatabaseManager;
46 import com.android.bluetooth.csip.CsipSetCoordinatorService;
47 import com.android.bluetooth.R;
48 import com.android.bluetooth.TestUtils;
49 import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver;
50 
51 import org.junit.After;
52 import org.junit.Assert;
53 import org.junit.Assume;
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.Mock;
59 import org.mockito.Mockito;
60 import org.mockito.MockitoAnnotations;
61 
62 import java.time.Duration;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.concurrent.LinkedBlockingQueue;
66 import java.util.concurrent.TimeoutException;
67 import java.util.stream.IntStream;
68 
69 @MediumTest
70 @RunWith(AndroidJUnit4.class)
71 public class VolumeControlServiceTest {
72     private BluetoothAdapter mAdapter;
73     private AttributionSource mAttributionSource;
74     private Context mTargetContext;
75     private VolumeControlService mService;
76     private VolumeControlService.BluetoothVolumeControlBinder mServiceBinder;
77     private BluetoothDevice mDevice;
78     private BluetoothDevice mDeviceTwo;
79     private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
80     private static final int TIMEOUT_MS = 1000;
81     private static final int BT_LE_AUDIO_MAX_VOL = 255;
82     private static final int MEDIA_MIN_VOL = 0;
83     private static final int MEDIA_MAX_VOL = 25;
84     private static final int CALL_MIN_VOL = 1;
85     private static final int CALL_MAX_VOL = 8;
86 
87     private BroadcastReceiver mVolumeControlIntentReceiver;
88 
89     @Mock private AdapterService mAdapterService;
90     @Mock private DatabaseManager mDatabaseManager;
91     @Mock private VolumeControlNativeInterface mNativeInterface;
92     @Mock private AudioManager mAudioManager;
93     @Mock private ServiceFactory mServiceFactory;
94     @Mock private CsipSetCoordinatorService mCsipService;
95 
96     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
97 
98     @Before
setUp()99     public void setUp() throws Exception {
100         mTargetContext = InstrumentationRegistry.getTargetContext();
101         // Set up mocks and test assets
102         MockitoAnnotations.initMocks(this);
103 
104         if (Looper.myLooper() == null) {
105             Looper.prepare();
106         }
107 
108         TestUtils.setAdapterService(mAdapterService);
109         doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
110         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
111 
112         mAdapter = BluetoothAdapter.getDefaultAdapter();
113         mAttributionSource = mAdapter.getAttributionSource();
114 
115         doReturn(MEDIA_MIN_VOL).when(mAudioManager)
116                 .getStreamMinVolume(eq(AudioManager.STREAM_MUSIC));
117         doReturn(MEDIA_MAX_VOL).when(mAudioManager)
118                 .getStreamMaxVolume(eq(AudioManager.STREAM_MUSIC));
119         doReturn(CALL_MIN_VOL).when(mAudioManager)
120                 .getStreamMinVolume(eq(AudioManager.STREAM_VOICE_CALL));
121         doReturn(CALL_MAX_VOL).when(mAudioManager)
122                 .getStreamMaxVolume(eq(AudioManager.STREAM_VOICE_CALL));
123 
124         startService();
125         mService.mVolumeControlNativeInterface = mNativeInterface;
126         mService.mAudioManager = mAudioManager;
127         mService.mFactory = mServiceFactory;
128         mServiceBinder = (VolumeControlService.BluetoothVolumeControlBinder) mService.initBinder();
129         mServiceBinder.mIsTesting = true;
130 
131         doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService();
132 
133         // Override the timeout value to speed up the test
134         VolumeControlStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
135 
136         // Set up the Connection State Changed receiver
137         IntentFilter filter = new IntentFilter();
138         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
139         filter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED);
140 
141         mVolumeControlIntentReceiver = new VolumeControlIntentReceiver();
142         mTargetContext.registerReceiver(mVolumeControlIntentReceiver, filter);
143 
144         // Get a device for testing
145         mDevice = TestUtils.getTestDevice(mAdapter, 0);
146         mDeviceTwo = TestUtils.getTestDevice(mAdapter, 1);
147         mDeviceQueueMap = new HashMap<>();
148         mDeviceQueueMap.put(mDevice, new LinkedBlockingQueue<>());
149         mDeviceQueueMap.put(mDeviceTwo, new LinkedBlockingQueue<>());
150         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
151                 .getBondState(any(BluetoothDevice.class));
152         doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService)
153                 .getRemoteUuids(any(BluetoothDevice.class));
154     }
155 
156     @After
tearDown()157     public void tearDown() throws Exception {
158         if (mService == null) {
159             return;
160         }
161 
162         stopService();
163         mTargetContext.unregisterReceiver(mVolumeControlIntentReceiver);
164         mDeviceQueueMap.clear();
165         TestUtils.clearAdapterService(mAdapterService);
166         reset(mAudioManager);
167     }
168 
startService()169     private void startService() throws TimeoutException {
170         TestUtils.startService(mServiceRule, VolumeControlService.class);
171         mService = VolumeControlService.getVolumeControlService();
172         Assert.assertNotNull(mService);
173     }
174 
stopService()175     private void stopService() throws TimeoutException {
176         TestUtils.stopService(mServiceRule, VolumeControlService.class);
177         mService = VolumeControlService.getVolumeControlService();
178         Assert.assertNull(mService);
179     }
180 
181     private class VolumeControlIntentReceiver extends BroadcastReceiver {
182         @Override
onReceive(Context context, Intent intent)183         public void onReceive(Context context, Intent intent) {
184             try {
185                 BluetoothDevice device = intent.getParcelableExtra(
186                         BluetoothDevice.EXTRA_DEVICE);
187                 Assert.assertNotNull(device);
188                 LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device);
189                 Assert.assertNotNull(queue);
190                 queue.put(intent);
191             } catch (InterruptedException e) {
192                 Assert.fail("Cannot add Intent to the Connection State queue: "
193                         + e.getMessage());
194             }
195         }
196     }
197 
verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState, int prevState)198     private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
199             int newState, int prevState) {
200         Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
201         Assert.assertNotNull(intent);
202         Assert.assertEquals(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED,
203                 intent.getAction());
204         Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
205         Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
206         Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
207                 -1));
208     }
209 
verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device)210     private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) {
211         Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device));
212         Assert.assertNull(intent);
213     }
214 
215     /**
216      * Test getting VolumeControl Service: getVolumeControlService()
217      */
218     @Test
testGetVolumeControlService()219     public void testGetVolumeControlService() {
220         Assert.assertEquals(mService, VolumeControlService.getVolumeControlService());
221     }
222 
223     /**
224      * Test stop VolumeControl Service
225      */
226     @Test
testStopVolumeControlService()227     public void testStopVolumeControlService() throws Exception {
228         // Prepare: connect
229         connectDevice(mDevice);
230         // VolumeControl Service is already running: test stop().
231         // Note: must be done on the main thread
232         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
233             public void run() {
234                 Assert.assertTrue(mService.stop());
235             }
236         });
237         // Try to restart the service. Note: must be done on the main thread
238         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
239             public void run() {
240                 Assert.assertTrue(mService.start());
241             }
242         });
243     }
244 
245     /**
246      * Test get/set policy for BluetoothDevice
247      */
248     @Test
testGetSetPolicy()249     public void testGetSetPolicy() {
250         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
251         when(mDatabaseManager
252                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
253                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
254         Assert.assertEquals("Initial device policy",
255                 BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
256                 mService.getConnectionPolicy(mDevice));
257 
258         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
259         when(mDatabaseManager
260                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
261                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
262         Assert.assertEquals("Setting device policy to POLICY_FORBIDDEN",
263                 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
264                 mService.getConnectionPolicy(mDevice));
265 
266         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
267         when(mDatabaseManager
268                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
269                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
270         Assert.assertEquals("Setting device policy to POLICY_ALLOWED",
271                 BluetoothProfile.CONNECTION_POLICY_ALLOWED,
272                 mService.getConnectionPolicy(mDevice));
273     }
274 
275     /**
276      * Test if getProfileConnectionPolicy works after the service is stopped.
277      */
278     @Test
testGetPolicyAfterStopped()279     public void testGetPolicyAfterStopped() throws Exception {
280         mService.stop();
281         when(mDatabaseManager
282                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
283                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
284         final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
285         int defaultRecvValue = -1000;
286         mServiceBinder.getConnectionPolicy(mDevice, mAttributionSource, recv);
287         int policy = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
288                 .getValue(defaultRecvValue);
289         Assert.assertEquals("Initial device policy",
290                 BluetoothProfile.CONNECTION_POLICY_UNKNOWN, policy);
291     }
292 
293     /**
294      *  Test okToConnect method using various test cases
295      */
296     @Test
testOkToConnect()297     public void testOkToConnect() {
298         int badPolicyValue = 1024;
299         int badBondState = 42;
300         testOkToConnectCase(mDevice,
301                 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
302         testOkToConnectCase(mDevice,
303                 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
304         testOkToConnectCase(mDevice,
305                 BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
306         testOkToConnectCase(mDevice,
307                 BluetoothDevice.BOND_NONE, badPolicyValue, false);
308         testOkToConnectCase(mDevice,
309                 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
310         testOkToConnectCase(mDevice,
311                 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
312         testOkToConnectCase(mDevice,
313                 BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
314         testOkToConnectCase(mDevice,
315                 BluetoothDevice.BOND_BONDING, badPolicyValue, false);
316         testOkToConnectCase(mDevice,
317                 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
318         testOkToConnectCase(mDevice,
319                 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
320         testOkToConnectCase(mDevice,
321                 BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
322         testOkToConnectCase(mDevice,
323                 BluetoothDevice.BOND_BONDED, badPolicyValue, false);
324         testOkToConnectCase(mDevice,
325                 badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
326         testOkToConnectCase(mDevice,
327                 badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
328         testOkToConnectCase(mDevice,
329                 badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
330         testOkToConnectCase(mDevice,
331                 badBondState, badPolicyValue, false);
332     }
333 
334     /**
335      * Test that an outgoing connection to device that does not have Volume Control UUID is rejected
336      */
337     @Test
testOutgoingConnectMissingVolumeControlUuid()338     public void testOutgoingConnectMissingVolumeControlUuid() {
339         // Update the device policy so okToConnect() returns true
340         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
341         when(mDatabaseManager
342                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
343                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
344         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
345         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
346 
347         // Return No UUID
348         doReturn(new ParcelUuid[]{}).when(mAdapterService)
349                 .getRemoteUuids(any(BluetoothDevice.class));
350 
351         // Send a connect request
352         Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
353     }
354 
355     /**
356      * Test that an outgoing connection to device that have Volume Control UUID is successful
357      */
358     @Test
testOutgoingConnectDisconnectExistingVolumeControlUuid()359     public void testOutgoingConnectDisconnectExistingVolumeControlUuid() throws Exception {
360         // Update the device policy so okToConnect() returns true
361         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
362         when(mDatabaseManager
363                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
364                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
365         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
366         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
367 
368         // Return Volume Control UUID
369         doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService)
370                 .getRemoteUuids(any(BluetoothDevice.class));
371 
372         // Send a connect request via binder
373         SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
374         mServiceBinder.connect(mDevice, mAttributionSource, recv);
375         Assert.assertTrue("Connect expected to succeed",
376                 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(false));
377 
378         // Verify the connection state broadcast, and that we are in Connecting state
379         verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING,
380                 BluetoothProfile.STATE_DISCONNECTED);
381 
382         // Send a disconnect request via binder
383         recv = SynchronousResultReceiver.get();
384         mServiceBinder.disconnect(mDevice, mAttributionSource, recv);
385         Assert.assertTrue("Disconnect expected to succeed",
386                 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(false));
387 
388         // Verify the connection state broadcast, and that we are in Connecting state
389         verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_DISCONNECTED,
390                 BluetoothProfile.STATE_CONNECTING);
391     }
392 
393     /**
394      * Test that an outgoing connection to device with POLICY_FORBIDDEN is rejected
395      */
396     @Test
testOutgoingConnectPolicyForbidden()397     public void testOutgoingConnectPolicyForbidden() {
398         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
399         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
400 
401         // Set the device policy to POLICY_FORBIDDEN so connect() should fail
402         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
403         when(mDatabaseManager
404                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
405                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
406 
407         // Send a connect request
408         Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
409     }
410 
411     /**
412      * Test that an outgoing connection times out
413      */
414     @Test
testOutgoingConnectTimeout()415     public void testOutgoingConnectTimeout() throws Exception {
416         // Update the device policy so okToConnect() returns true
417         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
418         when(mDatabaseManager
419                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
420                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
421         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
422         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
423 
424         // Send a connect request
425         Assert.assertTrue("Connect failed", mService.connect(mDevice));
426 
427         // Verify the connection state broadcast, and that we are in Connecting state
428         verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING,
429                 BluetoothProfile.STATE_DISCONNECTED);
430         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
431                 mService.getConnectionState(mDevice));
432 
433         // Verify the connection state broadcast, and that we are in Disconnected state
434         verifyConnectionStateIntent(VolumeControlStateMachine.sConnectTimeoutMs * 2,
435                 mDevice, BluetoothProfile.STATE_DISCONNECTED,
436                 BluetoothProfile.STATE_CONNECTING);
437 
438         final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
439         int defaultRecvValue = -1000;
440         mServiceBinder.getConnectionState(mDevice, mAttributionSource, recv);
441         int state = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
442                 .getValue(defaultRecvValue);
443         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, state);
444     }
445 
446     /**
447      * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Volume Control stack
448      * events will create a state machine.
449      */
450     @Test
testCreateStateMachineStackEvents()451     public void testCreateStateMachineStackEvents() {
452         // Update the device policy so okToConnect() returns true
453         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
454         when(mDatabaseManager
455                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
456                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
457         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
458         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
459 
460         // stack event: CONNECTION_STATE_CONNECTING - state machine should be created
461         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING,
462                 BluetoothProfile.STATE_DISCONNECTED);
463         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
464                 mService.getConnectionState(mDevice));
465         Assert.assertTrue(mService.getDevices().contains(mDevice));
466 
467         // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
468         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED,
469                 BluetoothProfile.STATE_CONNECTING);
470         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
471                 mService.getConnectionState(mDevice));
472         Assert.assertTrue(mService.getDevices().contains(mDevice));
473         mService.bondStateChanged(mDevice, BluetoothDevice.BOND_NONE);
474         Assert.assertFalse(mService.getDevices().contains(mDevice));
475 
476         // stack event: CONNECTION_STATE_CONNECTED - state machine should be created
477         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
478                 BluetoothProfile.STATE_DISCONNECTED);
479         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
480                 mService.getConnectionState(mDevice));
481         Assert.assertTrue(mService.getDevices().contains(mDevice));
482 
483         // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
484         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED,
485                 BluetoothProfile.STATE_CONNECTED);
486         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
487                 mService.getConnectionState(mDevice));
488         Assert.assertTrue(mService.getDevices().contains(mDevice));
489         mService.bondStateChanged(mDevice, BluetoothDevice.BOND_NONE);
490         Assert.assertFalse(mService.getDevices().contains(mDevice));
491 
492         // stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
493         generateUnexpectedConnectionMessageFromNative(mDevice,
494                 BluetoothProfile.STATE_DISCONNECTING,
495                 BluetoothProfile.STATE_DISCONNECTED);
496         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
497                 mService.getConnectionState(mDevice));
498         Assert.assertFalse(mService.getDevices().contains(mDevice));
499 
500         // stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created
501         generateUnexpectedConnectionMessageFromNative(mDevice,
502                 BluetoothProfile.STATE_DISCONNECTED,
503                 BluetoothProfile.STATE_DISCONNECTED);
504         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
505                 mService.getConnectionState(mDevice));
506         Assert.assertFalse(mService.getDevices().contains(mDevice));
507     }
508 
509     /**
510      * Test that a CONNECTION_STATE_DISCONNECTED Volume Control stack event will remove the state
511      * machine only if the device is unbond.
512      */
513     @Test
testDeleteStateMachineDisconnectEvents()514     public void testDeleteStateMachineDisconnectEvents() {
515         // Update the device policy so okToConnect() returns true
516         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
517         when(mDatabaseManager
518                 .getProfileConnectionPolicy(mDevice, BluetoothProfile.VOLUME_CONTROL))
519                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
520         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
521         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
522 
523         // stack event: CONNECTION_STATE_CONNECTING - state machine should be created
524         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING,
525                 BluetoothProfile.STATE_DISCONNECTED);
526         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
527                 mService.getConnectionState(mDevice));
528         Assert.assertTrue(mService.getDevices().contains(mDevice));
529 
530         // stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
531         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED,
532                 BluetoothProfile.STATE_CONNECTING);
533         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
534                 mService.getConnectionState(mDevice));
535         Assert.assertTrue(mService.getDevices().contains(mDevice));
536 
537         // stack event: CONNECTION_STATE_CONNECTING - state machine remains
538         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTING,
539                 BluetoothProfile.STATE_DISCONNECTED);
540         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
541                 mService.getConnectionState(mDevice));
542         Assert.assertTrue(mService.getDevices().contains(mDevice));
543 
544         // device bond state marked as unbond - state machine is not removed
545         doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService)
546                 .getBondState(any(BluetoothDevice.class));
547         Assert.assertTrue(mService.getDevices().contains(mDevice));
548 
549         // stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed
550         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_DISCONNECTED,
551                 BluetoothProfile.STATE_CONNECTING);
552         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
553                 mService.getConnectionState(mDevice));
554         Assert.assertFalse(mService.getDevices().contains(mDevice));
555     }
556 
557     /**
558      * Test that various Volume Control stack events will broadcast related states.
559      */
560     @Test
testVolumeControlStackEvents()561     public void testVolumeControlStackEvents() {
562         int group_id = -1;
563         int volume = 6;
564         boolean mute = false;
565 
566         // Send a message to trigger volume state changed broadcast
567         VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
568                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
569         stackEvent.device = mDevice;
570         stackEvent.valueInt1 = group_id;
571         stackEvent.valueInt2 = volume;
572         stackEvent.valueBool1 = mute;
573         mService.messageFromNative(stackEvent);
574     }
575 
getLeAudioVolume(int index, int minIndex, int maxIndex, int streamType)576     int getLeAudioVolume(int index, int minIndex, int maxIndex, int streamType) {
577         // Note: This has to be the same as mBtHelper.setLeAudioVolume()
578         return (int) Math.round((double) index * BT_LE_AUDIO_MAX_VOL / maxIndex);
579     }
580 
testVolumeCalculations(int streamType, int minIdx, int maxIdx)581     void testVolumeCalculations(int streamType, int minIdx, int maxIdx) {
582         // Send a message to trigger volume state changed broadcast
583         final VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
584                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
585         stackEvent.device = null;
586         stackEvent.valueInt1 = 1;       // groupId
587         stackEvent.valueBool1 = false;  // isMuted
588         stackEvent.valueBool2 = true;   // isAutonomous
589 
590         IntStream.range(minIdx, maxIdx).forEach(idx -> {
591             // Given the reference volume index, set the LeAudio Volume
592             stackEvent.valueInt2 = getLeAudioVolume(idx,
593                             mAudioManager.getStreamMinVolume(streamType),
594                             mAudioManager.getStreamMaxVolume(streamType), streamType);
595             mService.messageFromNative(stackEvent);
596 
597             // Verify that setting LeAudio Volume, sets the original volume index to Audio FW
598             verify(mAudioManager, times(1)).setStreamVolume(eq(streamType), eq(idx), anyInt());
599         });
600     }
601 
602     @Test
testAutonomousVolumeStateChange()603     public void testAutonomousVolumeStateChange() {
604         doReturn(AudioManager.MODE_IN_CALL).when(mAudioManager).getMode();
605         testVolumeCalculations(AudioManager.STREAM_VOICE_CALL, CALL_MIN_VOL, CALL_MAX_VOL);
606 
607         doReturn(AudioManager.MODE_NORMAL).when(mAudioManager).getMode();
608         testVolumeCalculations(AudioManager.STREAM_MUSIC, MEDIA_MIN_VOL, MEDIA_MAX_VOL);
609     }
610 
611     /**
612      * Test if autonomous Mute/Unmute propagates the event to audio manager.
613      */
614     @Test
testAutonomousMuteUnmute()615     public void testAutonomousMuteUnmute() {
616         int streamType = AudioManager.STREAM_MUSIC;
617         int streamVol = getLeAudioVolume(19, MEDIA_MIN_VOL, MEDIA_MAX_VOL, streamType);
618 
619         // Send a message to trigger volume state changed broadcast
620         final VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
621                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
622         stackEvent.device = null;
623         stackEvent.valueInt1 = 1;       // groupId
624         stackEvent.valueInt2 = streamVol;
625         stackEvent.valueBool1 = false;  // isMuted
626         stackEvent.valueBool2 = true;   // isAutonomous
627 
628         doReturn(false).when(mAudioManager)
629                 .isStreamMute(eq(AudioManager.STREAM_MUSIC));
630 
631         // Verify that muting LeAudio device, sets the mute state on the audio device
632         stackEvent.valueBool1 = true;
633         mService.messageFromNative(stackEvent);
634         verify(mAudioManager, times(1)).adjustStreamVolume(eq(streamType),
635                 eq(AudioManager.ADJUST_MUTE), anyInt());
636 
637         doReturn(true).when(mAudioManager)
638                 .isStreamMute(eq(AudioManager.STREAM_MUSIC));
639 
640         // Verify that unmuting LeAudio device, unsets the mute state on the audio device
641         stackEvent.valueBool1 = false;
642         mService.messageFromNative(stackEvent);
643         verify(mAudioManager, times(1)).adjustStreamVolume(eq(streamType),
644                 eq(AudioManager.ADJUST_UNMUTE), anyInt());
645     }
646 
647     /**
648      * Test Volume Control cache.
649      */
650     @Test
testVolumeCache()651     public void testVolumeCache() throws Exception {
652         int groupId = 1;
653         int volume = 6;
654 
655         Assert.assertEquals(-1, mService.getGroupVolume(groupId));
656         final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
657         mServiceBinder.setGroupVolume(groupId, volume, mAttributionSource, voidRecv);
658         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
659 
660         final SynchronousResultReceiver<Integer> intRecv = SynchronousResultReceiver.get();
661         int defaultRecvValue = -100;
662         mServiceBinder.getGroupVolume(groupId, mAttributionSource, intRecv);
663         int groupVolume = intRecv.awaitResultNoInterrupt(
664                 Duration.ofMillis(TIMEOUT_MS)).getValue(defaultRecvValue);
665         Assert.assertEquals(volume, groupVolume);
666 
667         volume = 10;
668         // Send autonomous volume change.
669         VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
670                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
671         stackEvent.device = null;
672         stackEvent.valueInt1 = groupId;
673         stackEvent.valueInt2 = volume;
674         stackEvent.valueBool1 = false;
675         stackEvent.valueBool2 = true; /* autonomous */
676         mService.messageFromNative(stackEvent);
677 
678         Assert.assertEquals(volume, mService.getGroupVolume(groupId));
679     }
680 
681     /**
682      * Test Volume Control Mute cache.
683      */
684     @Test
testMuteCache()685     public void testMuteCache() throws Exception {
686         int groupId = 1;
687         int volume = 6;
688 
689         Assert.assertEquals(false, mService.getGroupMute(groupId));
690 
691         // Send autonomous volume change
692         VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
693                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
694         stackEvent.device = null;
695         stackEvent.valueInt1 = groupId;
696         stackEvent.valueInt2 = volume;
697         stackEvent.valueBool1 = false; /* unmuted */
698         stackEvent.valueBool2 = true; /* autonomous */
699         mService.messageFromNative(stackEvent);
700 
701         // Mute
702         final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
703         mServiceBinder.muteGroup(groupId, mAttributionSource, voidRecv);
704         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
705         Assert.assertEquals(true, mService.getGroupMute(groupId));
706 
707         // Make sure the volume is kept even when muted
708         Assert.assertEquals(volume, mService.getGroupVolume(groupId));
709 
710         // Send autonomous unmute
711         stackEvent.valueBool1 = false; /* unmuted */
712         mService.messageFromNative(stackEvent);
713 
714         Assert.assertEquals(false, mService.getGroupMute(groupId));
715     }
716 
717     /**
718      * Test Volume Control with muted stream.
719      */
720     @Test
testVolumeChangeWhileMuted()721     public void testVolumeChangeWhileMuted() throws Exception {
722         int groupId = 1;
723         int volume = 6;
724 
725         Assert.assertEquals(false, mService.getGroupMute(groupId));
726 
727         // Set the initial volume state
728         VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
729                 VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
730         stackEvent.device = null;
731         stackEvent.valueInt1 = groupId;
732         stackEvent.valueInt2 = volume;
733         stackEvent.valueBool1 = false; /* unmuted */
734         stackEvent.valueBool2 = true; /* autonomous */
735         mService.messageFromNative(stackEvent);
736 
737         // Mute
738         mService.muteGroup(groupId);
739         Assert.assertEquals(true, mService.getGroupMute(groupId));
740         verify(mNativeInterface, times(1)).muteGroup(eq(groupId));
741 
742         // Make sure the volume is kept even when muted
743         doReturn(true).when(mAudioManager)
744                 .isStreamMute(eq(AudioManager.STREAM_MUSIC));
745         Assert.assertEquals(volume, mService.getGroupVolume(groupId));
746 
747         // Lower the volume and keep it mute
748         mService.setGroupVolume(groupId, --volume);
749         Assert.assertEquals(true, mService.getGroupMute(groupId));
750         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume));
751         verify(mNativeInterface, times(0)).unmuteGroup(eq(groupId));
752 
753         // Don't unmute on consecutive calls either
754         mService.setGroupVolume(groupId, --volume);
755         Assert.assertEquals(true, mService.getGroupMute(groupId));
756         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume));
757         verify(mNativeInterface, times(0)).unmuteGroup(eq(groupId));
758 
759         // Raise the volume and unmute
760         volume += 10; // avoid previous volume levels and simplify mock verification
761         doReturn(false).when(mAudioManager)
762                 .isStreamMute(eq(AudioManager.STREAM_MUSIC));
763         mService.setGroupVolume(groupId, ++volume);
764         Assert.assertEquals(false, mService.getGroupMute(groupId));
765         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume));
766         // Verify the number of unmute calls after the second volume change
767         mService.setGroupVolume(groupId, ++volume);
768         Assert.assertEquals(false, mService.getGroupMute(groupId));
769         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume));
770         // Make sure we unmuted only once
771         verify(mNativeInterface, times(1)).unmuteGroup(eq(groupId));
772     }
773 
774     /**
775      * Test setting volume for a group member who connects after the volume level
776      * for a group was already changed and cached.
777      */
778     @Test
testLateConnectingDevice()779     public void testLateConnectingDevice() throws Exception {
780         int groupId = 1;
781         int groupVolume = 56;
782 
783         // Both devices are in the same group
784         when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
785         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
786 
787         // Update the device policy so okToConnect() returns true
788         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
789         when(mDatabaseManager
790                 .getProfileConnectionPolicy(any(BluetoothDevice.class),
791                         eq(BluetoothProfile.VOLUME_CONTROL)))
792                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
793         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
794         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
795 
796         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
797                 BluetoothProfile.STATE_DISCONNECTED);
798         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
799                 mService.getConnectionState(mDevice));
800         Assert.assertTrue(mService.getDevices().contains(mDevice));
801 
802         mService.setGroupVolume(groupId, groupVolume);
803         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(groupVolume));
804         verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume));
805 
806         // Verify that second device gets the proper group volume level when connected
807         generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
808                 BluetoothProfile.STATE_DISCONNECTED);
809         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
810                 mService.getConnectionState(mDeviceTwo));
811         Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
812         verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume));
813     }
814 
815     /**
816      * Test setting volume for a new group member who is discovered after the volume level
817      * for a group was already changed and cached.
818      */
819     @Test
testLateDiscoveredGroupMember()820     public void testLateDiscoveredGroupMember() throws Exception {
821         int groupId = 1;
822         int groupVolume = 56;
823 
824         // For now only one device is in the group
825         when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
826         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1);
827 
828         // Update the device policy so okToConnect() returns true
829         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
830         when(mDatabaseManager
831                 .getProfileConnectionPolicy(any(BluetoothDevice.class),
832                         eq(BluetoothProfile.VOLUME_CONTROL)))
833                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
834         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
835         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
836 
837         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
838                 BluetoothProfile.STATE_DISCONNECTED);
839         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
840                 mService.getConnectionState(mDevice));
841         Assert.assertTrue(mService.getDevices().contains(mDevice));
842 
843         // Set the group volume
844         mService.setGroupVolume(groupId, groupVolume);
845 
846         // Verify that second device will not get the group volume level if it is not a group member
847         generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
848                 BluetoothProfile.STATE_DISCONNECTED);
849         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
850                 mService.getConnectionState(mDeviceTwo));
851         Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
852         verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume));
853 
854         // But gets the volume when it becomes the group member
855         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
856         mService.handleGroupNodeAdded(groupId, mDeviceTwo);
857         verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume));
858     }
859 
860     /**
861      * Test setting volume to 0 for a group member who connects after the volume level
862      * for a group was already changed and cached. LeAudio has no knowledge of mute
863      * for anything else than telephony, thus setting volume level to 0 is considered
864      * as muting.
865      */
866     @Test
testMuteLateConnectingDevice()867     public void testMuteLateConnectingDevice() throws Exception {
868         int groupId = 1;
869         int volume = 100;
870 
871         // Both devices are in the same group
872         when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
873         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
874 
875         // Update the device policy so okToConnect() returns true
876         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
877         when(mDatabaseManager
878                 .getProfileConnectionPolicy(any(BluetoothDevice.class),
879                         eq(BluetoothProfile.VOLUME_CONTROL)))
880                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
881         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
882         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
883 
884         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
885                 BluetoothProfile.STATE_DISCONNECTED);
886         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
887                 mService.getConnectionState(mDevice));
888         Assert.assertTrue(mService.getDevices().contains(mDevice));
889 
890         // Set the initial volume and mute conditions
891         doReturn(true).when(mAudioManager).isStreamMute(anyInt());
892         mService.setGroupVolume(groupId, volume);
893 
894         verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(volume));
895         verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(volume));
896         // Check if it was muted
897         verify(mNativeInterface, times(1)).muteGroup(eq(groupId));
898 
899         Assert.assertEquals(true, mService.getGroupMute(groupId));
900 
901         // Verify that second device gets the proper group volume level when connected
902         generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
903                 BluetoothProfile.STATE_DISCONNECTED);
904         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
905                 mService.getConnectionState(mDeviceTwo));
906         Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
907         verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(volume));
908         // Check if new device was muted
909         verify(mNativeInterface, times(1)).mute(eq(mDeviceTwo));
910     }
911 
912     /**
913      * Test setting volume to 0 for a new group member who is discovered after the volume level
914      * for a group was already changed and cached. LeAudio has no knowledge of mute
915      * for anything else than telephony, thus setting volume level to 0 is considered
916      * as muting.
917      */
918     @Test
testMuteLateDiscoveredGroupMember()919     public void testMuteLateDiscoveredGroupMember() throws Exception {
920         int groupId = 1;
921         int volume = 100;
922 
923         // For now only one device is in the group
924         when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
925         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1);
926 
927         // Update the device policy so okToConnect() returns true
928         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
929         when(mDatabaseManager
930                 .getProfileConnectionPolicy(any(BluetoothDevice.class),
931                         eq(BluetoothProfile.VOLUME_CONTROL)))
932                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
933         doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
934         doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
935 
936         generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
937                 BluetoothProfile.STATE_DISCONNECTED);
938         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
939                 mService.getConnectionState(mDevice));
940         Assert.assertTrue(mService.getDevices().contains(mDevice));
941 
942         // Set the initial volume and mute conditions
943         doReturn(true).when(mAudioManager).isStreamMute(anyInt());
944         mService.setGroupVolume(groupId, volume);
945 
946         // Verify that second device will not get the group volume level if it is not a group member
947         generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
948                 BluetoothProfile.STATE_DISCONNECTED);
949         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
950                 mService.getConnectionState(mDeviceTwo));
951         Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
952         verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(volume));
953         // Check if it was not muted
954         verify(mNativeInterface, times(0)).mute(eq(mDeviceTwo));
955 
956         // But gets the volume when it becomes the group member
957         when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
958         mService.handleGroupNodeAdded(groupId, mDeviceTwo);
959         verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(volume));
960         verify(mNativeInterface, times(1)).mute(eq(mDeviceTwo));
961     }
962 
963     @Test
testServiceBinderGetDevicesMatchingConnectionStates()964     public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception {
965         final SynchronousResultReceiver<List<BluetoothDevice>> recv =
966                 SynchronousResultReceiver.get();
967         mServiceBinder.getDevicesMatchingConnectionStates(null, mAttributionSource, recv);
968         List<BluetoothDevice> devices = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
969                 .getValue(null);
970         Assert.assertEquals(0, devices.size());
971     }
972 
973     @Test
testServiceBinderSetConnectionPolicy()974     public void testServiceBinderSetConnectionPolicy() throws Exception {
975         final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
976         boolean defaultRecvValue = false;
977         mServiceBinder.setConnectionPolicy(
978                 mDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, mAttributionSource, recv);
979         Assert.assertTrue(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
980                 .getValue(defaultRecvValue));
981         verify(mDatabaseManager).setProfileConnectionPolicy(
982                 mDevice, BluetoothProfile.VOLUME_CONTROL, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
983     }
984 
985     @Test
testServiceBinderVolumeOffsetMethods()986     public void testServiceBinderVolumeOffsetMethods() throws Exception {
987         // Send a message to trigger connection completed
988         VolumeControlStackEvent event = new VolumeControlStackEvent(
989                 VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
990         event.device = mDevice;
991         event.valueInt1 = 2; // number of external outputs
992         mService.messageFromNative(event);
993 
994         final SynchronousResultReceiver<Boolean> boolRecv = SynchronousResultReceiver.get();
995         boolean defaultRecvValue = false;
996         mServiceBinder.isVolumeOffsetAvailable(mDevice, mAttributionSource, boolRecv);
997         Assert.assertTrue(boolRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
998                 .getValue(defaultRecvValue));
999 
1000         int volumeOffset = 100;
1001         final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
1002         mServiceBinder.setVolumeOffset(mDevice, volumeOffset, mAttributionSource, voidRecv);
1003         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
1004         verify(mNativeInterface).setExtAudioOutVolumeOffset(mDevice, 1, volumeOffset);
1005     }
1006 
1007     @Test
testServiceBinderRegisterUnregisterCallback()1008     public void testServiceBinderRegisterUnregisterCallback() throws Exception {
1009         IBluetoothVolumeControlCallback callback =
1010                 Mockito.mock(IBluetoothVolumeControlCallback.class);
1011         Binder binder = Mockito.mock(Binder.class);
1012         when(callback.asBinder()).thenReturn(binder);
1013 
1014         int size = mService.mCallbacks.getRegisteredCallbackCount();
1015         SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get();
1016         mServiceBinder.registerCallback(callback, mAttributionSource, recv);
1017         recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
1018         Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount());
1019 
1020         recv = SynchronousResultReceiver.get();
1021         mServiceBinder.unregisterCallback(callback, mAttributionSource, recv);
1022         recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
1023         Assert.assertEquals(size, mService.mCallbacks.getRegisteredCallbackCount());
1024     }
1025 
1026     @Test
testServiceBinderMuteMethods()1027     public void testServiceBinderMuteMethods() throws Exception {
1028         SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
1029         mServiceBinder.mute(mDevice, mAttributionSource, voidRecv);
1030         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
1031         verify(mNativeInterface).mute(mDevice);
1032 
1033         voidRecv = SynchronousResultReceiver.get();
1034         mServiceBinder.unmute(mDevice, mAttributionSource, voidRecv);
1035         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
1036         verify(mNativeInterface).unmute(mDevice);
1037 
1038         int groupId = 1;
1039         voidRecv = SynchronousResultReceiver.get();
1040         mServiceBinder.muteGroup(groupId, mAttributionSource, voidRecv);
1041         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
1042         verify(mNativeInterface).muteGroup(groupId);
1043 
1044         voidRecv = SynchronousResultReceiver.get();
1045         mServiceBinder.unmuteGroup(groupId, mAttributionSource, voidRecv);
1046         voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
1047         verify(mNativeInterface).unmuteGroup(groupId);
1048     }
1049 
1050     @Test
testVolumeControlOffsetDescriptor()1051     public void testVolumeControlOffsetDescriptor() {
1052         VolumeControlService.VolumeControlOffsetDescriptor descriptor =
1053                 new VolumeControlService.VolumeControlOffsetDescriptor();
1054         int invalidId = -1;
1055         int validId = 10;
1056         int testValue = 100;
1057         String testDesc = "testDescription";
1058         int testLocation = 10000;
1059 
1060         Assert.assertEquals(0, descriptor.size());
1061         descriptor.add(validId);
1062         Assert.assertEquals(1, descriptor.size());
1063 
1064         Assert.assertFalse(descriptor.setValue(invalidId, testValue));
1065         Assert.assertTrue(descriptor.setValue(validId, testValue));
1066         Assert.assertEquals(0, descriptor.getValue(invalidId));
1067         Assert.assertEquals(testValue, descriptor.getValue(validId));
1068 
1069         Assert.assertFalse(descriptor.setDescription(invalidId, testDesc));
1070         Assert.assertTrue(descriptor.setDescription(validId, testDesc));
1071         Assert.assertEquals(null, descriptor.getDescription(invalidId));
1072         Assert.assertEquals(testDesc, descriptor.getDescription(validId));
1073 
1074         Assert.assertFalse(descriptor.setLocation(invalidId, testLocation));
1075         Assert.assertTrue(descriptor.setLocation(validId, testLocation));
1076         Assert.assertEquals(0, descriptor.getLocation(invalidId));
1077         Assert.assertEquals(testLocation, descriptor.getLocation(validId));
1078 
1079         StringBuilder sb = new StringBuilder();
1080         descriptor.dump(sb);
1081         Assert.assertTrue(sb.toString().contains(testDesc));
1082 
1083         descriptor.add(validId + 1);
1084         Assert.assertEquals(2, descriptor.size());
1085         descriptor.remove(validId);
1086         Assert.assertEquals(1, descriptor.size());
1087         descriptor.clear();
1088         Assert.assertEquals(0, descriptor.size());
1089     }
1090 
1091     @Test
testDump_doesNotCrash()1092     public void testDump_doesNotCrash() throws Exception {
1093         connectDevice(mDevice);
1094 
1095         StringBuilder sb = new StringBuilder();
1096         mService.dump(sb);
1097     }
1098 
connectDevice(BluetoothDevice device)1099     private void connectDevice(BluetoothDevice device) throws Exception {
1100         VolumeControlStackEvent connCompletedEvent;
1101 
1102         List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
1103 
1104         // Update the device policy so okToConnect() returns true
1105         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
1106         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL))
1107                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
1108         doReturn(true).when(mNativeInterface).connectVolumeControl(device);
1109         doReturn(true).when(mNativeInterface).disconnectVolumeControl(device);
1110 
1111         // Send a connect request
1112         Assert.assertTrue("Connect failed", mService.connect(device));
1113 
1114         // Verify the connection state broadcast, and that we are in Connecting state
1115         verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING,
1116                 BluetoothProfile.STATE_DISCONNECTED);
1117         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
1118                 mService.getConnectionState(device));
1119 
1120         // Send a message to trigger connection completed
1121         connCompletedEvent = new VolumeControlStackEvent(
1122                 VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1123         connCompletedEvent.device = device;
1124         connCompletedEvent.valueInt1 = VolumeControlStackEvent.CONNECTION_STATE_CONNECTED;
1125         mService.messageFromNative(connCompletedEvent);
1126 
1127         // Verify the connection state broadcast, and that we are in Connected state
1128         verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED,
1129                 BluetoothProfile.STATE_CONNECTING);
1130         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
1131                 mService.getConnectionState(device));
1132 
1133         // Verify that the device is in the list of connected devices
1134         final SynchronousResultReceiver<List<BluetoothDevice>> recv =
1135                 SynchronousResultReceiver.get();
1136         mServiceBinder.getConnectedDevices(mAttributionSource, recv);
1137         List<BluetoothDevice> connectedDevices =
1138                 recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
1139         Assert.assertTrue(connectedDevices.contains(device));
1140         // Verify the list of previously connected devices
1141         for (BluetoothDevice prevDevice : prevConnectedDevices) {
1142             Assert.assertTrue(connectedDevices.contains(prevDevice));
1143         }
1144     }
1145 
generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, int oldConnectionState)1146     private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState,
1147             int oldConnectionState) {
1148         VolumeControlStackEvent stackEvent =
1149                 new VolumeControlStackEvent(
1150                         VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1151         stackEvent.device = device;
1152         stackEvent.valueInt1 = newConnectionState;
1153         mService.messageFromNative(stackEvent);
1154         // Verify the connection state broadcast
1155         verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState);
1156     }
1157 
generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, int oldConnectionState)1158     private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device,
1159             int newConnectionState, int oldConnectionState) {
1160         VolumeControlStackEvent stackEvent =
1161                 new VolumeControlStackEvent(
1162                         VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
1163         stackEvent.device = device;
1164         stackEvent.valueInt1 = newConnectionState;
1165         mService.messageFromNative(stackEvent);
1166         // Verify the connection state broadcast
1167         verifyNoConnectionStateIntent(TIMEOUT_MS, device);
1168     }
1169 
1170     /**
1171      *  Helper function to test okToConnect() method
1172      *
1173      *  @param device test device
1174      *  @param bondState bond state value, could be invalid
1175      *  @param policy value, could be invalid
1176      *  @param expected expected result from okToConnect()
1177      */
testOkToConnectCase(BluetoothDevice device, int bondState, int policy, boolean expected)1178     private void testOkToConnectCase(BluetoothDevice device, int bondState, int policy,
1179             boolean expected) {
1180         doReturn(bondState).when(mAdapterService).getBondState(device);
1181         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
1182         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL))
1183                 .thenReturn(policy);
1184         Assert.assertEquals(expected, mService.okToConnect(device));
1185     }
1186 }
1187