1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.bluetooth;
18
19import static com.google.common.truth.Truth.assertThat;
20import static org.mockito.Matchers.anyString;
21import static org.mockito.Mockito.doAnswer;
22import static org.mockito.Mockito.doCallRealMethod;
23import static org.mockito.Mockito.doReturn;
24import static org.mockito.Mockito.times;
25import static org.mockito.Mockito.verify;
26import static org.mockito.Mockito.when;
27
28import android.bluetooth.BluetoothAdapter;
29import android.bluetooth.BluetoothDevice;
30import android.content.Context;
31
32import com.android.settings.R;
33import com.android.settings.testutils.SettingsRobolectricTestRunner;
34import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
35import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
36import com.android.settingslib.bluetooth.LocalBluetoothManager;
37
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41import org.mockito.Answers;
42import org.mockito.Mock;
43import org.mockito.MockitoAnnotations;
44import org.robolectric.RuntimeEnvironment;
45
46import java.util.HashSet;
47import java.util.Set;
48
49@RunWith(SettingsRobolectricTestRunner.class)
50public class BluetoothSummaryUpdaterTest {
51
52    private static final String DEVICE_NAME = "Nightshade";
53    private static final String DEVICE_KEYBOARD_NAME = "Bluetooth Keyboard";
54
55    private Context mContext;
56    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
57    private LocalBluetoothManager mBluetoothManager;
58    @Mock
59    private LocalBluetoothAdapter mBtAdapter;
60    @Mock
61    private BluetoothDevice mConnectedDevice;
62    @Mock
63    private BluetoothDevice mConnectedKeyBoardDevice;
64    @Mock
65    private SummaryListener mListener;
66
67    // Disabled by default
68    private final boolean[] mAdapterEnabled = {false};
69    // Not connected by default
70    private final int[] mAdapterConnectionState = {BluetoothAdapter.STATE_DISCONNECTED};
71    // Not connected by default
72    private final boolean[] mDeviceConnected = {false, false};
73    private final Set<BluetoothDevice> mBondedDevices = new HashSet<>();
74    private BluetoothSummaryUpdater mSummaryUpdater;
75
76    @Before
77    public void setUp() {
78        MockitoAnnotations.initMocks(this);
79        mContext = RuntimeEnvironment.application.getApplicationContext();
80        doCallRealMethod().when(mListener).onSummaryChanged(anyString());
81        // Setup mock adapter
82        when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter);
83        doAnswer(invocation -> mAdapterEnabled[0]).when(mBtAdapter).isEnabled();
84        doAnswer(invocation -> mAdapterConnectionState[0]).when(mBtAdapter).getConnectionState();
85        mSummaryUpdater = new BluetoothSummaryUpdater(mContext, mListener, mBluetoothManager);
86        // Setup first device
87        doReturn(DEVICE_NAME).when(mConnectedDevice).getName();
88        doAnswer(invocation -> mDeviceConnected[0]).when(mConnectedDevice).isConnected();
89        // Setup second device
90        doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName();
91        doAnswer(invocation -> mDeviceConnected[1]).when(mConnectedKeyBoardDevice).isConnected();
92        doReturn(mBondedDevices).when(mBtAdapter).getBondedDevices();
93    }
94
95    @Test
96    public void register_true_shouldRegisterListener() {
97        mSummaryUpdater.register(true);
98
99        verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryUpdater);
100    }
101
102    @Test
103    public void register_false_shouldUnregisterListener() {
104        mSummaryUpdater.register(false);
105
106        verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryUpdater);
107    }
108
109    @Test
110    public void register_true_shouldSendSummaryChange() {
111        mAdapterEnabled[0] = true;
112        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
113        mBondedDevices.add(mConnectedDevice);
114        mDeviceConnected[0] = true;
115
116        mSummaryUpdater.register(true);
117
118        verify(mListener).onSummaryChanged(
119                mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
120    }
121
122    @Test
123    public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
124        // These states should be ignored
125        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
126        mBondedDevices.add(mConnectedDevice);
127        mDeviceConnected[0] = true;
128
129        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
130
131        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
132    }
133
134    @Test
135    public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
136        mAdapterEnabled[0] = true;
137        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
138        mBondedDevices.add(mConnectedDevice);
139        mDeviceConnected[0] = true;
140
141        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
142
143        verify(mListener).onSummaryChanged(
144                mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
145    }
146
147    @Test
148    public void onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected() {
149        mAdapterEnabled[0] = true;
150        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
151        mBondedDevices.add(mConnectedDevice);
152        // State mismatch
153        mDeviceConnected[0] = false;
154
155        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
156
157        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
158    }
159
160    @Test
161    public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
162        mAdapterEnabled[0] = true;
163        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
164        mBondedDevices.add(mConnectedDevice);
165        // This should be ignored
166        mDeviceConnected[0] = true;
167
168        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
169
170        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
171    }
172
173    @Test
174    public void onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary() {
175        mAdapterEnabled[0] = true;
176        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
177        mBondedDevices.add(mConnectedDevice);
178        mDeviceConnected[0] = false;
179
180        mSummaryUpdater.register(true);
181        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
182
183        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
184        mDeviceConnected[0] = true;
185        mSummaryUpdater.onConnectionStateChanged(null /* device */,
186                BluetoothAdapter.STATE_CONNECTED);
187        verify(mListener).onSummaryChanged(
188                mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
189
190        mAdapterEnabled[0] = false;
191        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
192        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
193
194        // Turning ON means not enabled
195        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
196        // There should still be only one invocation of disabled message
197        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
198
199        mAdapterEnabled[0] = true;
200        mDeviceConnected[0] = false;
201        mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
202        verify(mListener, times(2)).onSummaryChanged(mContext.getString(R.string.disconnected));
203        verify(mListener, times(4)).onSummaryChanged(anyString());
204    }
205
206    @Test
207    public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
208        mAdapterEnabled[0] = true;
209        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
210        mBondedDevices.add(mConnectedDevice);
211        mDeviceConnected[0] = true;
212
213        mSummaryUpdater.onConnectionStateChanged(null /* device */,
214                BluetoothAdapter.STATE_CONNECTED);
215
216        verify(mListener).onSummaryChanged(
217                mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
218    }
219
220    @Test
221    public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
222        mAdapterEnabled[0] = true;
223        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTED;
224        mBondedDevices.add(mConnectedDevice);
225        mDeviceConnected[0] = false;
226
227        mSummaryUpdater.onConnectionStateChanged(null /* device */,
228                BluetoothAdapter.STATE_CONNECTED);
229
230        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
231    }
232
233    @Test
234    public void onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage() {
235        mAdapterEnabled[0] = true;
236        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTED;
237
238        mSummaryUpdater.onConnectionStateChanged(null /* device */,
239                BluetoothAdapter.STATE_CONNECTED);
240
241        verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
242    }
243
244    @Test
245    public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
246        // No need for bonded devices
247        mAdapterEnabled[0] = true;
248        mAdapterConnectionState[0] = BluetoothAdapter.STATE_CONNECTING;
249
250        mSummaryUpdater.onConnectionStateChanged(null /* device */,
251                BluetoothAdapter.STATE_CONNECTING);
252
253        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connecting));
254    }
255
256    @Test
257    public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
258        // No need for bonded devices
259        mAdapterEnabled[0] = true;
260        mAdapterConnectionState[0] = BluetoothAdapter.STATE_DISCONNECTING;
261
262        mSummaryUpdater.onConnectionStateChanged(null /* device */,
263                BluetoothAdapter.STATE_DISCONNECTING);
264
265        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnecting));
266    }
267
268    @Test
269    public void getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary() {
270        mBondedDevices.add(mConnectedDevice);
271        mDeviceConnected[0] = true;
272        final String expectedSummary =
273            mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME);
274
275        assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
276    }
277
278    @Test
279    public void getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary() {
280        mBondedDevices.add(mConnectedDevice);
281        mBondedDevices.add(mConnectedKeyBoardDevice);
282        mDeviceConnected[0] = true;
283        mDeviceConnected[1] = true;
284        final String expectedSummary =
285            mContext.getString(R.string.bluetooth_connected_multiple_devices_summary);
286
287        assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
288    }
289
290    private class SummaryListener implements OnSummaryChangeListener {
291        String summary;
292
293        @Override
294        public void onSummaryChanged(String summary) {
295            this.summary = summary;
296        }
297    }
298}
299