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