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