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 package com.android.settings.bluetooth;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.Mockito.doReturn;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.bluetooth.BluetoothAdapter;
30 import android.bluetooth.BluetoothDevice;
31 import android.content.Context;
32 import android.graphics.drawable.Drawable;
33 import android.os.UserManager;
34 import android.util.Pair;
35 import android.view.ContextThemeWrapper;
36 
37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
38 import com.android.settings.R;
39 import com.android.settings.testutils.FakeFeatureFactory;
40 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
41 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
42 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
43 
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.mockito.Mock;
48 import org.mockito.MockitoAnnotations;
49 import org.robolectric.RobolectricTestRunner;
50 import org.robolectric.RuntimeEnvironment;
51 import org.robolectric.annotation.Config;
52 import org.robolectric.util.ReflectionHelpers;
53 
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.List;
58 
59 @RunWith(RobolectricTestRunner.class)
60 @Config(shadows = {ShadowAlertDialogCompat.class})
61 public class BluetoothDevicePreferenceTest {
62     private static final boolean SHOW_DEVICES_WITHOUT_NAMES = true;
63     private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
64     private static final String MAC_ADDRESS_2 = "05:52:C7:0B:D8:3C";
65     private static final String MAC_ADDRESS_3 = "06:52:C7:0B:D8:3C";
66     private static final String MAC_ADDRESS_4 = "07:52:C7:0B:D8:3C";
67     private static final Comparator<BluetoothDevicePreference> COMPARATOR =
68             Comparator.naturalOrder();
69     private static final String FAKE_DESCRIPTION = "fake_description";
70 
71     private Context mContext;
72     @Mock
73     private CachedBluetoothDevice mCachedBluetoothDevice;
74     @Mock
75     private CachedBluetoothDevice mCachedDevice1;
76     @Mock
77     private CachedBluetoothDevice mCachedDevice2;
78     @Mock
79     private CachedBluetoothDevice mCachedDevice3;
80     @Mock
81     private BluetoothDevice mBluetoothDevice;
82     @Mock
83     private BluetoothDevice mBluetoothDevice1;
84     @Mock
85     private BluetoothDevice mBluetoothDevice2;
86     @Mock
87     private BluetoothDevice mBluetoothDevice3;
88     @Mock
89     private Drawable mDrawable;
90     @Mock
91     private BluetoothAdapter mBluetoothAdapter;
92 
93     private FakeFeatureFactory mFakeFeatureFactory;
94     private MetricsFeatureProvider mMetricsFeatureProvider;
95     private BluetoothDevicePreference mPreference;
96     private List<BluetoothDevicePreference> mPreferenceList = new ArrayList<>();
97 
98     @Before
setUp()99     public void setUp() {
100         MockitoAnnotations.initMocks(this);
101         Context context = spy(RuntimeEnvironment.application.getApplicationContext());
102         mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
103         mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
104         mMetricsFeatureProvider = mFakeFeatureFactory.getMetricsFeatureProvider();
105         when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
106         when(mCachedBluetoothDevice.getDrawableWithDescription())
107                 .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
108         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
109         when(mCachedDevice1.getAddress()).thenReturn(MAC_ADDRESS_2);
110         when(mCachedDevice1.getDrawableWithDescription())
111                 .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
112         when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1);
113         when(mCachedDevice2.getAddress()).thenReturn(MAC_ADDRESS_3);
114         when(mCachedDevice2.getDrawableWithDescription())
115                 .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
116         when(mCachedDevice2.getDevice()).thenReturn(mBluetoothDevice2);
117         when(mCachedDevice3.getAddress()).thenReturn(MAC_ADDRESS_4);
118         when(mCachedDevice3.getDrawableWithDescription())
119                 .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION));
120         when(mCachedDevice3.getDevice()).thenReturn(mBluetoothDevice3);
121         mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
122                 SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
123         mPreference.mBluetoothAdapter = mBluetoothAdapter;
124     }
125 
126     @Test
onClicked_deviceConnected_shouldLogBluetoothDisconnectEvent()127     public void onClicked_deviceConnected_shouldLogBluetoothDisconnectEvent() {
128         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
129 
130         mPreference.onClicked();
131 
132         verify(mMetricsFeatureProvider)
133                 .action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
134     }
135 
136     @Test
onClicked_deviceBonded_shouldLogBluetoothConnectEvent()137     public void onClicked_deviceBonded_shouldLogBluetoothConnectEvent() {
138         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
139         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
140 
141         mPreference.onClicked();
142 
143         verify(mMetricsFeatureProvider)
144                 .action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT);
145     }
146 
147     @Test
onClicked_deviceNotBonded_shouldLogBluetoothPairEvent()148     public void onClicked_deviceNotBonded_shouldLogBluetoothPairEvent() {
149         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
150         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
151         when(mCachedBluetoothDevice.startPairing()).thenReturn(true);
152         when(mCachedBluetoothDevice.hasHumanReadableName()).thenReturn(true);
153 
154         mPreference.onClicked();
155 
156         verify(mMetricsFeatureProvider)
157                 .action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR);
158         verify(mMetricsFeatureProvider, never())
159                 .action(mContext,
160                         MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
161     }
162 
163     @Test
onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent()164     public void onClicked_deviceNotBonded_shouldLogBluetoothPairEventAndPairWithoutNameEvent() {
165         when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
166         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
167         when(mCachedBluetoothDevice.startPairing()).thenReturn(true);
168         when(mCachedBluetoothDevice.hasHumanReadableName()).thenReturn(false);
169 
170         mPreference.onClicked();
171 
172         verify(mMetricsFeatureProvider)
173                 .action(mContext, MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR);
174         verify(mMetricsFeatureProvider)
175                 .action(mContext,
176                         MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES);
177     }
178 
179     @Test
getSecondTargetResource_shouldBeGearIconLayout()180     public void getSecondTargetResource_shouldBeGearIconLayout() {
181         assertThat(mPreference.getSecondTargetResId()).isEqualTo(R.layout.preference_widget_gear);
182     }
183 
184     @Test
shouldHideSecondTarget_noDevice_shouldReturnTrue()185     public void shouldHideSecondTarget_noDevice_shouldReturnTrue() {
186         ReflectionHelpers.setField(mPreference, "mCachedDevice", null);
187 
188         assertThat(mPreference.shouldHideSecondTarget()).isTrue();
189     }
190 
191     @Test
shouldHideSecondTarget_notBond_shouldReturnTrue()192     public void shouldHideSecondTarget_notBond_shouldReturnTrue() {
193         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
194 
195         assertThat(mPreference.shouldHideSecondTarget()).isTrue();
196     }
197 
198     @Test
shouldHideSecondTarget_hasUserRestriction_shouldReturnTrue()199     public void shouldHideSecondTarget_hasUserRestriction_shouldReturnTrue() {
200         final UserManager um = mock(UserManager.class);
201         ReflectionHelpers.setField(mPreference, "mUserManager", um);
202         when(um.hasUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(true);
203 
204         assertThat(mPreference.shouldHideSecondTarget()).isTrue();
205     }
206 
207     @Test
shouldHideSecondTarget_hasBoundDeviceAndNoRestriction_shouldReturnFalse()208     public void shouldHideSecondTarget_hasBoundDeviceAndNoRestriction_shouldReturnFalse() {
209         when(mCachedBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
210         final UserManager um = mock(UserManager.class);
211         ReflectionHelpers.setField(mPreference, "mUserManager", um);
212         when(um.hasUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH)).thenReturn(false);
213 
214         assertThat(mPreference.shouldHideSecondTarget()).isFalse();
215     }
216 
217     @Test
isVisible_showDeviceWithoutNames_visible()218     public void isVisible_showDeviceWithoutNames_visible() {
219         doReturn(false).when(mCachedBluetoothDevice).hasHumanReadableName();
220         BluetoothDevicePreference preference =
221                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
222                         SHOW_DEVICES_WITHOUT_NAMES,
223                         BluetoothDevicePreference.SortType.TYPE_DEFAULT);
224 
225         assertThat(preference.isVisible()).isTrue();
226     }
227 
228     @Test
isVisible_hideDeviceWithoutNames_invisible()229     public void isVisible_hideDeviceWithoutNames_invisible() {
230         doReturn(false).when(mCachedBluetoothDevice).hasHumanReadableName();
231         BluetoothDevicePreference preference =
232                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
233                         false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
234 
235         assertThat(preference.isVisible()).isFalse();
236     }
237 
238     @Test
setNeedNotifyHierarchyChanged_updateValue()239     public void setNeedNotifyHierarchyChanged_updateValue() {
240         mPreference.setNeedNotifyHierarchyChanged(true);
241 
242         assertThat(mPreference.mNeedNotifyHierarchyChanged).isTrue();
243     }
244 
245     @Test
compareTo_sortTypeFIFO()246     public void compareTo_sortTypeFIFO() {
247         final BluetoothDevicePreference preference3 = new BluetoothDevicePreference(mContext,
248                 mCachedDevice3, SHOW_DEVICES_WITHOUT_NAMES,
249                 BluetoothDevicePreference.SortType.TYPE_FIFO);
250         final BluetoothDevicePreference preference2 = new BluetoothDevicePreference(mContext,
251                 mCachedDevice2, SHOW_DEVICES_WITHOUT_NAMES,
252                 BluetoothDevicePreference.SortType.TYPE_FIFO);
253         final BluetoothDevicePreference preference1 = new BluetoothDevicePreference(mContext,
254                 mCachedDevice1, SHOW_DEVICES_WITHOUT_NAMES,
255                 BluetoothDevicePreference.SortType.TYPE_FIFO);
256 
257         mPreferenceList.add(preference1);
258         mPreferenceList.add(preference2);
259         mPreferenceList.add(preference3);
260         Collections.sort(mPreferenceList, COMPARATOR);
261 
262         assertThat(mPreferenceList.get(0).getCachedDevice().getAddress())
263                 .isEqualTo(preference3.getCachedDevice().getAddress());
264         assertThat(mPreferenceList.get(1).getCachedDevice().getAddress())
265                 .isEqualTo(preference2.getCachedDevice().getAddress());
266         assertThat(mPreferenceList.get(2).getCachedDevice().getAddress())
267                 .isEqualTo(preference1.getCachedDevice().getAddress());
268     }
269 
270     @Test
compareTo_sortTypeDefault()271     public void compareTo_sortTypeDefault() {
272         final BluetoothDevicePreference preference3 = new BluetoothDevicePreference(mContext,
273                 mCachedDevice3, SHOW_DEVICES_WITHOUT_NAMES,
274                 BluetoothDevicePreference.SortType.TYPE_DEFAULT);
275         final BluetoothDevicePreference preference2 = new BluetoothDevicePreference(mContext,
276                 mCachedDevice2, SHOW_DEVICES_WITHOUT_NAMES,
277                 BluetoothDevicePreference.SortType.TYPE_DEFAULT);
278         final BluetoothDevicePreference preference1 = new BluetoothDevicePreference(mContext,
279                 mCachedDevice1, SHOW_DEVICES_WITHOUT_NAMES,
280                 BluetoothDevicePreference.SortType.TYPE_DEFAULT);
281 
282         mPreferenceList.add(preference1);
283         mPreferenceList.add(preference2);
284         mPreferenceList.add(preference3);
285         Collections.sort(mPreferenceList, COMPARATOR);
286 
287         assertThat(mPreferenceList.get(0).getCachedDevice().getAddress())
288                 .isEqualTo(preference1.getCachedDevice().getAddress());
289         assertThat(mPreferenceList.get(1).getCachedDevice().getAddress())
290                 .isEqualTo(preference2.getCachedDevice().getAddress());
291         assertThat(mPreferenceList.get(2).getCachedDevice().getAddress())
292                 .isEqualTo(preference3.getCachedDevice().getAddress());
293     }
294 
295     @Test
onAttached_callbackNotRemoved_doNotRegisterCallback()296     public void onAttached_callbackNotRemoved_doNotRegisterCallback() {
297         mPreference.onAttached();
298         // After the onAttached(), the callback is registered.
299 
300         // If it goes to the onAttached() again, then it do not register again, since the
301         // callback is not removed.
302         mPreference.onAttached();
303 
304         verify(mCachedBluetoothDevice, times(1)).registerCallback(any());
305         verify(mBluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any());
306     }
307 
308     @Test
onAttached_callbackRemoved_registerCallback()309     public void onAttached_callbackRemoved_registerCallback() {
310         mPreference.onAttached();
311 
312         mPreference.onPrepareForRemoval();
313         mPreference.onAttached();
314 
315         verify(mCachedBluetoothDevice, times(1)).unregisterCallback(any());
316         verify(mCachedBluetoothDevice, times(2)).registerCallback(any());
317         verify(mBluetoothAdapter, times(2)).addOnMetadataChangedListener(any(), any(), any());
318     }
319 }
320