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 
17 package com.android.settings.bluetooth;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.anyString;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.doCallRealMethod;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 
28 import android.bluetooth.BluetoothAdapter;
29 import android.bluetooth.BluetoothDevice;
30 import android.content.Context;
31 
32 import com.android.settings.R;
33 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
34 import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
35 import com.android.settingslib.bluetooth.LocalBluetoothManager;
36 
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.mockito.Answers;
41 import org.mockito.Mock;
42 import org.mockito.MockitoAnnotations;
43 import org.robolectric.RobolectricTestRunner;
44 import org.robolectric.RuntimeEnvironment;
45 import org.robolectric.annotation.Config;
46 import org.robolectric.shadow.api.Shadow;
47 
48 import java.util.HashSet;
49 import java.util.Set;
50 
51 @RunWith(RobolectricTestRunner.class)
52 @Config(shadows = {ShadowBluetoothAdapter.class})
53 public class BluetoothSummaryUpdaterTest {
54 
55     private static final String DEVICE_NAME = "Nightshade";
56     private static final String DEVICE_KEYBOARD_NAME = "Bluetooth Keyboard";
57 
58     private Context mContext;
59     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
60     private LocalBluetoothManager mBluetoothManager;
61     @Mock
62     private BluetoothDevice mConnectedDevice;
63     @Mock
64     private BluetoothDevice mConnectedKeyBoardDevice;
65     @Mock
66     private SummaryListener mListener;
67 
68     // Disabled by default
69     private final boolean[] mAdapterEnabled = {false};
70     // Not connected by default
71     private final int[] mAdapterConnectionState = {BluetoothAdapter.STATE_DISCONNECTED};
72     // Not connected by default
73     private final boolean[] mDeviceConnected = {false, false};
74     private final Set<BluetoothDevice> mBondedDevices = new HashSet<>();
75     private BluetoothSummaryUpdater mSummaryUpdater;
76     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
77 
78     @Before
setUp()79     public void setUp() {
80         MockitoAnnotations.initMocks(this);
81         mContext = RuntimeEnvironment.application.getApplicationContext();
82         doCallRealMethod().when(mListener).onSummaryChanged(anyString());
83         mSummaryUpdater = new BluetoothSummaryUpdater(mContext, mListener, mBluetoothManager);
84         // Setup first device
85         doReturn(DEVICE_NAME).when(mConnectedDevice).getName();
86         doAnswer(invocation -> mDeviceConnected[0]).when(mConnectedDevice).isConnected();
87         // Setup second device
88         doReturn(DEVICE_KEYBOARD_NAME).when(mConnectedKeyBoardDevice).getName();
89         doAnswer(invocation -> mDeviceConnected[1]).when(mConnectedKeyBoardDevice).isConnected();
90         // Setup BluetoothAdapter
91         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
92         mShadowBluetoothAdapter.setEnabled(mAdapterEnabled[0]);
93         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
94         mShadowBluetoothAdapter.setConnectionState(mAdapterConnectionState[0]);
95     }
96 
97     @Test
register_true_shouldRegisterListener()98     public void register_true_shouldRegisterListener() {
99         mSummaryUpdater.register(true);
100 
101         verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryUpdater);
102     }
103 
104     @Test
register_false_shouldUnregisterListener()105     public void register_false_shouldUnregisterListener() {
106         mSummaryUpdater.register(false);
107 
108         verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryUpdater);
109     }
110 
111     @Test
register_true_shouldSendSummaryChange()112     public void register_true_shouldSendSummaryChange() {
113         mShadowBluetoothAdapter.setEnabled(true);
114         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
115         mBondedDevices.add(mConnectedDevice);
116         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
117         mDeviceConnected[0] = true;
118 
119         mSummaryUpdater.register(true);
120 
121         verify(mListener).onSummaryChanged(
122                 mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
123     }
124 
125     @Test
onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary()126     public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
127         // These states should be ignored
128         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
129         mBondedDevices.add(mConnectedDevice);
130         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
131         mDeviceConnected[0] = true;
132 
133         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
134 
135         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
136     }
137 
138     @Test
onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary()139     public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
140         mShadowBluetoothAdapter.setEnabled(true);
141         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
142         mBondedDevices.add(mConnectedDevice);
143         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
144         mDeviceConnected[0] = true;
145 
146         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
147 
148         verify(mListener).onSummaryChanged(
149                 mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
150     }
151 
152     @Test
onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected()153     public void onBluetoothStateChanged_btEnabled_connectedMisMatch_shouldSendNotConnected() {
154         mShadowBluetoothAdapter.setEnabled(true);
155         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
156         mBondedDevices.add(mConnectedDevice);
157         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
158         // State mismatch
159         mDeviceConnected[0] = false;
160 
161         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
162 
163         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
164     }
165 
166     @Test
onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage()167     public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
168         mShadowBluetoothAdapter.setEnabled(true);
169         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
170         mBondedDevices.add(mConnectedDevice);
171         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
172         // This should be ignored
173         mDeviceConnected[0] = true;
174 
175         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
176 
177         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
178     }
179 
180     @Test
onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary()181     public void onBluetoothStateChanged_ConnectedDisabledEnabled_shouldSendDisconnectedSummary() {
182         mShadowBluetoothAdapter.setEnabled(true);
183         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
184         mBondedDevices.add(mConnectedDevice);
185         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
186         mDeviceConnected[0] = false;
187 
188         mSummaryUpdater.register(true);
189         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
190 
191         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
192         mDeviceConnected[0] = true;
193         mSummaryUpdater.onConnectionStateChanged(null /* device */,
194                 BluetoothAdapter.STATE_CONNECTED);
195         verify(mListener).onSummaryChanged(
196                 mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
197 
198         mShadowBluetoothAdapter.setEnabled(false);
199         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
200         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
201 
202         // Turning ON means not enabled
203         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
204         // There should still be only one invocation of disabled message
205         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
206 
207         mShadowBluetoothAdapter.setEnabled(true);
208         mDeviceConnected[0] = false;
209         mSummaryUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
210         verify(mListener, times(2)).onSummaryChanged(mContext.getString(R.string.disconnected));
211         verify(mListener, times(4)).onSummaryChanged(anyString());
212     }
213 
214     @Test
onConnectionStateChanged_connected_shouldSendConnectedMessage()215     public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
216         mShadowBluetoothAdapter.setEnabled(true);
217         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
218         mBondedDevices.add(mConnectedDevice);
219         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
220         mDeviceConnected[0] = true;
221 
222         mSummaryUpdater.onConnectionStateChanged(null /* device */,
223                 BluetoothAdapter.STATE_CONNECTED);
224 
225         verify(mListener).onSummaryChanged(
226                 mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME));
227     }
228 
229     @Test
onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage()230     public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
231         mShadowBluetoothAdapter.setEnabled(true);
232         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
233         mBondedDevices.add(mConnectedDevice);
234         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
235         mDeviceConnected[0] = false;
236 
237         mSummaryUpdater.onConnectionStateChanged(null /* device */,
238                 BluetoothAdapter.STATE_CONNECTED);
239 
240         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
241     }
242 
243     @Test
onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage()244     public void onConnectionStateChanged_noBondedDevice_shouldSendDisconnectedMessage() {
245         mShadowBluetoothAdapter.setEnabled(true);
246         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTED);
247 
248         mSummaryUpdater.onConnectionStateChanged(null /* device */,
249                 BluetoothAdapter.STATE_CONNECTED);
250 
251         verify(mListener).onSummaryChanged(mContext.getString(R.string.disconnected));
252     }
253 
254     @Test
onConnectionStateChanged_connecting_shouldSendConnectingMessage()255     public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
256         // No need for bonded devices
257         mShadowBluetoothAdapter.setEnabled(true);
258         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_CONNECTING);
259 
260         mSummaryUpdater.onConnectionStateChanged(null /* device */,
261                 BluetoothAdapter.STATE_CONNECTING);
262 
263         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connecting));
264     }
265 
266     @Test
onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage()267     public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
268         // No need for bonded devices
269         mShadowBluetoothAdapter.setEnabled(true);
270         mShadowBluetoothAdapter.setConnectionState(BluetoothAdapter.STATE_DISCONNECTING);
271 
272         mSummaryUpdater.onConnectionStateChanged(null /* device */,
273                 BluetoothAdapter.STATE_DISCONNECTING);
274 
275         verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnecting));
276     }
277 
278     @Test
getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary()279     public void getConnectedDeviceSummary_hasConnectedDevice_returnOneDeviceSummary() {
280         mBondedDevices.add(mConnectedDevice);
281         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
282         mDeviceConnected[0] = true;
283         final String expectedSummary =
284             mContext.getString(R.string.bluetooth_connected_summary, DEVICE_NAME);
285 
286         assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
287     }
288 
289     @Test
getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary()290     public void getConnectedDeviceSummary_multipleDevices_returnMultipleDevicesSummary() {
291         mBondedDevices.add(mConnectedDevice);
292         mBondedDevices.add(mConnectedKeyBoardDevice);
293         mShadowBluetoothAdapter.setBondedDevices(mBondedDevices);
294 
295         mDeviceConnected[0] = true;
296         mDeviceConnected[1] = true;
297         final String expectedSummary =
298             mContext.getString(R.string.bluetooth_connected_multiple_devices_summary);
299 
300         assertThat(mSummaryUpdater.getConnectedDeviceSummary()).isEqualTo(expectedSummary);
301     }
302 
303     private class SummaryListener implements OnSummaryChangeListener {
304         String summary;
305 
306         @Override
onSummaryChanged(String summary)307         public void onSummaryChanged(String summary) {
308             this.summary = summary;
309         }
310     }
311 }
312