• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.telecom.tests;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothHearingAid;
22 import android.content.ContentResolver;
23 import android.os.Parcel;
24 import android.telecom.Log;
25 import android.test.suitebuilder.annotation.SmallTest;
26 
27 import com.android.internal.os.SomeArgs;
28 import com.android.server.telecom.BluetoothHeadsetProxy;
29 import com.android.server.telecom.TelecomSystem;
30 import com.android.server.telecom.Timeouts;
31 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
32 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
33 
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.junit.runners.JUnit4;
38 import org.mockito.Mock;
39 
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 
45 import static org.junit.Assert.assertEquals;
46 import static org.mockito.ArgumentMatchers.any;
47 import static org.mockito.ArgumentMatchers.nullable;
48 import static org.mockito.Matchers.eq;
49 import static org.mockito.Mockito.reset;
50 import static org.mockito.Mockito.times;
51 import static org.mockito.Mockito.verify;
52 import static org.mockito.Mockito.when;
53 
54 @RunWith(JUnit4.class)
55 public class BluetoothRouteManagerTest extends TelecomTestCase {
56     private static final int TEST_TIMEOUT = 1000;
57     static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
58     static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
59     static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
60     static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04");
61 
62     @Mock private BluetoothDeviceManager mDeviceManager;
63     @Mock private BluetoothHeadsetProxy mHeadsetProxy;
64     @Mock private BluetoothHearingAid mBluetoothHearingAid;
65     @Mock private Timeouts.Adapter mTimeoutsAdapter;
66     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
67 
68     @Override
69     @Before
setUp()70     public void setUp() throws Exception {
71         super.setUp();
72     }
73 
74     @SmallTest
75     @Test
testConnectHfpRetryWhileNotConnected()76     public void testConnectHfpRetryWhileNotConnected() {
77         BluetoothRouteManager sm = setupStateMachine(
78                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
79         setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null);
80         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
81                 nullable(ContentResolver.class))).thenReturn(0L);
82         when(mHeadsetProxy.connectAudio()).thenReturn(false);
83         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress());
84         // Wait 3 times: for the first connection attempt, the retry attempt,
85         // the second retry, and once more to make sure there are only three attempts.
86         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
87         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
88         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
89         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
90         verifyConnectionAttempt(DEVICE1, 3);
91         assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
92         sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
93         sm.quitNow();
94     }
95 
96     @SmallTest
97     @Test
testAmbiguousActiveDevice()98     public void testAmbiguousActiveDevice() {
99         BluetoothRouteManager sm = setupStateMachine(
100                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
101         setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
102                 new BluetoothDevice[]{HEARING_AID_DEVICE}, DEVICE1, HEARING_AID_DEVICE);
103         sm.onActiveDeviceChanged(DEVICE1, false);
104         sm.onActiveDeviceChanged(HEARING_AID_DEVICE, true);
105         executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
106 
107         verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
108         verifyConnectionAttempt(DEVICE1, 0);
109         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
110                         + ":" + HEARING_AID_DEVICE.getAddress(),
111                 sm.getCurrentState().getName());
112         sm.quitNow();
113     }
114 
115     @SmallTest
116     @Test
testAudioOnDeviceWithScoOffActiveDevice()117     public void testAudioOnDeviceWithScoOffActiveDevice() {
118         BluetoothRouteManager sm = setupStateMachine(
119                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
120         setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, DEVICE1, null);
121         when(mHeadsetProxy.getAudioState(DEVICE1))
122                 .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
123         executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
124 
125         verifyConnectionAttempt(DEVICE1, 0);
126         assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME,
127                 sm.getCurrentState().getName());
128         sm.quitNow();
129     }
130 
131     @SmallTest
132     @Test
testConnectHfpRetryWhileConnectedToAnotherDevice()133     public void testConnectHfpRetryWhileConnectedToAnotherDevice() {
134         BluetoothRouteManager sm = setupStateMachine(
135                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
136         setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null);
137         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
138                 nullable(ContentResolver.class))).thenReturn(0L);
139         when(mHeadsetProxy.connectAudio()).thenReturn(false);
140         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress());
141         // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
142         // so wait twice for the retry attempt, again to make sure there are only three attempts,
143         // and once more for good luck.
144         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
145         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
146         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
147         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
148         verifyConnectionAttempt(DEVICE2, 3);
149         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
150                         + ":" + DEVICE1.getAddress(),
151                 sm.getCurrentState().getName());
152         sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
153         sm.quitNow();
154     }
155 
setupStateMachine(String initialState, BluetoothDevice initialDevice)156     private BluetoothRouteManager setupStateMachine(String initialState,
157             BluetoothDevice initialDevice) {
158         resetMocks();
159         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
160                 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
161         sm.setListener(mListener);
162         sm.setInitialStateForTesting(initialState, initialDevice);
163         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
164         resetMocks();
165         return sm;
166     }
167 
setupConnectedDevices(BluetoothDevice[] hfpDevices, BluetoothDevice[] hearingAidDevices, BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice)168     private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
169             BluetoothDevice[] hearingAidDevices,
170             BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice) {
171         if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
172         if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
173 
174         when(mDeviceManager.getNumConnectedDevices()).thenReturn(
175                 hfpDevices.length + hearingAidDevices.length);
176         List<BluetoothDevice> allDevices = Stream.concat(
177                 Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices))
178                 .collect(Collectors.toList());
179 
180         when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices);
181         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices));
182         when(mHeadsetProxy.getActiveDevice()).thenReturn(hfpActiveDevice);
183         when(mHeadsetProxy.getAudioState(hfpActiveDevice))
184                 .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
185 
186         when(mBluetoothHearingAid.getConnectedDevices())
187                 .thenReturn(Arrays.asList(hearingAidDevices));
188         when(mBluetoothHearingAid.getActiveDevices())
189                 .thenReturn(Arrays.asList(hearingAidActiveDevice, null));
190     }
191 
executeRoutingAction(BluetoothRouteManager brm, int message, String device)192     static void executeRoutingAction(BluetoothRouteManager brm, int message, String
193             device) {
194         SomeArgs args = SomeArgs.obtain();
195         args.arg1 = Log.createSubsession();
196         args.arg2 = device;
197         brm.sendMessage(message, args);
198         waitForHandlerAction(brm.getHandler(), TEST_TIMEOUT);
199     }
200 
makeBluetoothDevice(String address)201     public static BluetoothDevice makeBluetoothDevice(String address) {
202         Parcel p1 = Parcel.obtain();
203         p1.writeString(address);
204         p1.setDataPosition(0);
205         BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1);
206         p1.recycle();
207         return device;
208     }
209 
resetMocks()210     private void resetMocks() {
211         reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
212         when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
213         when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid);
214         when(mHeadsetProxy.connectAudio()).thenReturn(true);
215         when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
216         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
217                 nullable(ContentResolver.class))).thenReturn(100000L);
218         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
219                 nullable(ContentResolver.class))).thenReturn(100000L);
220     }
221 
verifyConnectionAttempt(BluetoothDevice device, int numTimes)222     private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) {
223         verify(mDeviceManager, times(numTimes)).connectAudio(device.getAddress());
224     }
225 }
226