• 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.content.ContentResolver;
22 import android.os.Parcel;
23 import android.telecom.Log;
24 import android.test.suitebuilder.annotation.SmallTest;
25 
26 import com.android.internal.os.SomeArgs;
27 import com.android.server.telecom.BluetoothHeadsetProxy;
28 import com.android.server.telecom.TelecomSystem;
29 import com.android.server.telecom.Timeouts;
30 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
31 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.JUnit4;
37 import org.mockito.Mock;
38 
39 import java.util.Arrays;
40 import java.util.Objects;
41 
42 import static org.junit.Assert.assertEquals;
43 import static org.mockito.ArgumentMatchers.nullable;
44 import static org.mockito.Matchers.eq;
45 import static org.mockito.Mockito.atLeast;
46 import static org.mockito.Mockito.doAnswer;
47 import static org.mockito.Mockito.reset;
48 import static org.mockito.Mockito.times;
49 import static org.mockito.Mockito.verify;
50 import static org.mockito.Mockito.when;
51 
52 @RunWith(JUnit4.class)
53 public class BluetoothRouteManagerTest extends TelecomTestCase {
54     private static final int TEST_TIMEOUT = 1000;
55     static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
56     static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
57     static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
58 
59     @Mock private BluetoothDeviceManager mDeviceManager;
60     @Mock private BluetoothHeadsetProxy mHeadsetProxy;
61     @Mock private Timeouts.Adapter mTimeoutsAdapter;
62     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
63 
64     @Override
65     @Before
setUp()66     public void setUp() throws Exception {
67         super.setUp();
68     }
69 
70     @SmallTest
71     @Test
testConnectHfpRetryWhileNotConnected()72     public void testConnectHfpRetryWhileNotConnected() {
73         BluetoothRouteManager sm = setupStateMachine(
74                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
75         setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null);
76         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
77                 nullable(ContentResolver.class))).thenReturn(0L);
78         when(mHeadsetProxy.connectAudio()).thenReturn(false);
79         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress());
80         // Wait 3 times: for the first connection attempt, the retry attempt,
81         // the second retry, and once more to make sure there are only three attempts.
82         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
83         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
84         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
85         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
86         verifyConnectionAttempt(DEVICE1, 3);
87         assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
88         sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
89         sm.quitNow();
90     }
91 
92     @SmallTest
93     @Test
testConnectHfpRetryWhileConnectedToAnotherDevice()94     public void testConnectHfpRetryWhileConnectedToAnotherDevice() {
95         BluetoothRouteManager sm = setupStateMachine(
96                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
97         setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null);
98         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
99                 nullable(ContentResolver.class))).thenReturn(0L);
100         when(mHeadsetProxy.connectAudio()).thenReturn(false);
101         executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress());
102         // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
103         // so wait twice for the retry attempt, again to make sure there are only three attempts,
104         // and once more for good luck.
105         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
106         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
107         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
108         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
109         verifyConnectionAttempt(DEVICE2, 3);
110         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
111                         + ":" + DEVICE1.getAddress(),
112                 sm.getCurrentState().getName());
113         sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
114         sm.quitNow();
115     }
116 
setupStateMachine(String initialState, BluetoothDevice initialDevice)117     private BluetoothRouteManager setupStateMachine(String initialState,
118             BluetoothDevice initialDevice) {
119         resetMocks();
120         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
121                 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
122         sm.setListener(mListener);
123         sm.setInitialStateForTesting(initialState, initialDevice);
124         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
125         resetMocks();
126         return sm;
127     }
128 
setupConnectedDevices(BluetoothDevice[] devices, BluetoothDevice activeDevice)129     private void setupConnectedDevices(BluetoothDevice[] devices, BluetoothDevice activeDevice) {
130         when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
131         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
132         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
133         if (activeDevice != null) {
134             when(mHeadsetProxy.getAudioState(eq(activeDevice)))
135                     .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
136         }
137         doAnswer(invocation -> {
138             BluetoothDevice first = getFirstExcluding(devices,
139                     (String) invocation.getArguments()[0]);
140             return first == null ? null : first.getAddress();
141         }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class));
142         for (BluetoothDevice device : devices) {
143             when(mDeviceManager.getDeviceFromAddress(device.getAddress())).thenReturn(device);
144         }
145     }
146 
executeRoutingAction(BluetoothRouteManager brm, int message, String device)147     static void executeRoutingAction(BluetoothRouteManager brm, int message, String
148             device) {
149         SomeArgs args = SomeArgs.obtain();
150         args.arg1 = Log.createSubsession();
151         args.arg2 = device;
152         brm.sendMessage(message, args);
153         waitForHandlerAction(brm.getHandler(), TEST_TIMEOUT);
154     }
155 
makeBluetoothDevice(String address)156     public static BluetoothDevice makeBluetoothDevice(String address) {
157         Parcel p1 = Parcel.obtain();
158         p1.writeString(address);
159         p1.setDataPosition(0);
160         BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1);
161         p1.recycle();
162         return device;
163     }
164 
resetMocks()165     private void resetMocks() {
166         reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
167         when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
168         when(mHeadsetProxy.connectAudio()).thenReturn(true);
169         when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
170         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
171                 nullable(ContentResolver.class))).thenReturn(100000L);
172         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
173                 nullable(ContentResolver.class))).thenReturn(100000L);
174     }
175 
verifyConnectionAttempt(BluetoothDevice device, int numTimes)176     private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) {
177         verify(mHeadsetProxy, times(numTimes)).setActiveDevice(device);
178         verify(mHeadsetProxy, atLeast(numTimes)).connectAudio();
179     }
180 
getFirstExcluding( BluetoothDevice[] devices, String excludeAddress)181     private static BluetoothDevice getFirstExcluding(
182             BluetoothDevice[] devices, String excludeAddress) {
183         for (BluetoothDevice x : devices) {
184             if (!Objects.equals(excludeAddress, x.getAddress())) {
185                 return x;
186             }
187         }
188         return null;
189     }
190 }
191