1 /*
2  * Copyright (C) 2019 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.Mockito.never;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24 import static org.robolectric.Shadows.shadowOf;
25 
26 import android.bluetooth.BluetoothAdapter;
27 import android.bluetooth.BluetoothDevice;
28 import android.content.Context;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.Drawable;
31 import android.provider.DeviceConfig;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.widget.ImageView;
35 import android.widget.LinearLayout;
36 import android.widget.TextView;
37 
38 import com.android.settings.R;
39 import com.android.settings.core.BasePreferenceController;
40 import com.android.settings.core.SettingsUIDeviceConfig;
41 import com.android.settings.fuelgauge.BatteryMeterView;
42 import com.android.settings.testutils.shadow.ShadowDeviceConfig;
43 import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
44 import com.android.settingslib.bluetooth.BluetoothUtils;
45 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
46 import com.android.settingslib.utils.StringUtil;
47 import com.android.settingslib.widget.LayoutPreference;
48 
49 import org.junit.Before;
50 import org.junit.Ignore;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.Mock;
54 import org.mockito.MockitoAnnotations;
55 import org.robolectric.RobolectricTestRunner;
56 import org.robolectric.RuntimeEnvironment;
57 import org.robolectric.annotation.Config;
58 
59 import java.util.HashSet;
60 import java.util.Set;
61 
62 @RunWith(RobolectricTestRunner.class)
63 @Config(shadows = {ShadowEntityHeaderController.class, ShadowDeviceConfig.class})
64 public class AdvancedBluetoothDetailsHeaderControllerTest {
65     private static final int BATTERY_LEVEL_MAIN = 30;
66     private static final int BATTERY_LEVEL_LEFT = 25;
67     private static final int BATTERY_LEVEL_RIGHT = 45;
68     private static final int LOW_BATTERY_LEVEL = 15;
69     private static final int CASE_LOW_BATTERY_LEVEL = 19;
70     private static final int LOW_BATTERY_LEVEL_THRESHOLD = 15;
71     private static final int BATTERY_LEVEL_5 = 5;
72     private static final int BATTERY_LEVEL_50 = 50;
73     private static final String ICON_URI = "content://test.provider/icon.png";
74     private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
75     private static final String DEVICE_SUMMARY = "test summary";
76 
77     private Context mContext;
78 
79     @Mock
80     private BluetoothDevice mBluetoothDevice;
81     @Mock
82     private Bitmap mBitmap;
83     @Mock
84     private ImageView mImageView;
85     @Mock
86     private CachedBluetoothDevice mCachedDevice;
87     @Mock
88     private BluetoothAdapter mBluetoothAdapter;
89     private AdvancedBluetoothDetailsHeaderController mController;
90     private LayoutPreference mLayoutPreference;
91 
92     @Before
setUp()93     public void setUp() {
94         MockitoAnnotations.initMocks(this);
95 
96         mContext = RuntimeEnvironment.application;
97         mController = new AdvancedBluetoothDetailsHeaderController(mContext, "pref_Key");
98         when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
99         mController.init(mCachedDevice);
100         mLayoutPreference = new LayoutPreference(mContext,
101                 LayoutInflater.from(mContext).inflate(R.layout.advanced_bt_entity_header, null));
102         mController.mLayoutPreference = mLayoutPreference;
103         mController.mBluetoothAdapter = mBluetoothAdapter;
104         when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
105         when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
106         when(mCachedDevice.getIdentityAddress()).thenReturn(MAC_ADDRESS);
107     }
108 
109     @Test
createBatteryIcon_hasCorrectInfo()110     public void createBatteryIcon_hasCorrectInfo() {
111         final Drawable drawable = mController.createBtBatteryIcon(mContext, BATTERY_LEVEL_MAIN,
112                 true /* charging */);
113         assertThat(drawable).isInstanceOf(BatteryMeterView.BatteryMeterDrawable.class);
114 
115         final BatteryMeterView.BatteryMeterDrawable iconDrawable =
116                 (BatteryMeterView.BatteryMeterDrawable) drawable;
117         assertThat(iconDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL_MAIN);
118         assertThat(iconDrawable.getCharging()).isTrue();
119     }
120 
121     @Test
refresh_connectedWatch_behaveAsExpected()122     public void refresh_connectedWatch_behaveAsExpected() {
123         when(mBluetoothDevice.getMetadata(
124                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
125                 BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
126         when(mBluetoothDevice.getMetadata(
127                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
128                 String.valueOf(false).getBytes());
129         when(mCachedDevice.isConnected()).thenReturn(true);
130 
131         mController.refresh();
132 
133         assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
134                 View.GONE);
135         assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
136                 View.GONE);
137         assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
138                 View.VISIBLE);
139         // TODO (b/188954766) : clarify settings design
140     }
141 
142     @Test
refresh_connectedWatch_unknownBatteryLevel_shouldNotShowBatteryLevel()143     public void refresh_connectedWatch_unknownBatteryLevel_shouldNotShowBatteryLevel() {
144         when(mBluetoothDevice.getMetadata(
145                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
146                 BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
147         when(mBluetoothDevice.getMetadata(
148                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
149                 String.valueOf(false).getBytes());
150         when(mBluetoothDevice.getMetadata(
151                 BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
152                 String.valueOf(BluetoothUtils.META_INT_ERROR).getBytes());
153         when(mBluetoothDevice.getBatteryLevel()).thenReturn(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
154         when(mCachedDevice.isConnected()).thenReturn(true);
155 
156         mController.refresh();
157 
158         assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
159                 View.GONE);
160         assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
161                 View.GONE);
162         assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
163                 View.VISIBLE);
164         assertThat(mLayoutPreference.findViewById(R.id.layout_middle)
165                 .requireViewById(R.id.bt_battery_summary).getVisibility()).isEqualTo(View.GONE);
166     }
167 
168     @Test
refresh_connectedStylus_behaveAsExpected()169     public void refresh_connectedStylus_behaveAsExpected() {
170         when(mBluetoothDevice.getMetadata(
171                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
172                 BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
173         when(mBluetoothDevice.getMetadata(
174                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
175                 String.valueOf(false).getBytes());
176         when(mCachedDevice.isConnected()).thenReturn(true);
177 
178         mController.refresh();
179 
180         assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
181                 View.GONE);
182         assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
183                 View.GONE);
184         assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
185                 View.VISIBLE);
186     }
187 
188     @Test
refresh_connectedUnknownType_behaveAsExpected()189     public void refresh_connectedUnknownType_behaveAsExpected() {
190         when(mBluetoothDevice.getMetadata(
191                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
192                 "UNKNOWN_TYPE".getBytes());
193         when(mBluetoothDevice.getMetadata(
194                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
195                 String.valueOf(false).getBytes());
196         when(mCachedDevice.isConnected()).thenReturn(true);
197 
198         mController.refresh();
199 
200         assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
201                 View.GONE);
202         assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
203                 View.GONE);
204         assertThat(mLayoutPreference.findViewById(R.id.layout_middle).getVisibility()).isEqualTo(
205                 View.VISIBLE);
206     }
207 
208     @Test
refresh_connectedUntetheredHeadset_behaveAsExpected()209     public void refresh_connectedUntetheredHeadset_behaveAsExpected() {
210         when(mBluetoothDevice.getMetadata(
211                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
212                 BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
213         when(mBluetoothDevice.getMetadata(
214                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
215                 String.valueOf(false).getBytes());
216         when(mBluetoothDevice.getMetadata(
217                 BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
218                 String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
219         when(mBluetoothDevice.getMetadata(
220                 BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
221                 String.valueOf(BATTERY_LEVEL_RIGHT).getBytes());
222         when(mBluetoothDevice.getMetadata(
223                 BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
224                 String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
225         when(mCachedDevice.isConnected()).thenReturn(true);
226 
227         mController.refresh();
228 
229         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_left), BATTERY_LEVEL_LEFT);
230         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_right), BATTERY_LEVEL_RIGHT);
231         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
232     }
233 
234     @Test
refresh_connected_updateCorrectInfo()235     public void refresh_connected_updateCorrectInfo() {
236         when(mBluetoothDevice.getMetadata(
237                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
238                 String.valueOf(true).getBytes());
239         when(mBluetoothDevice.getMetadata(
240                 BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
241                 String.valueOf(BATTERY_LEVEL_LEFT).getBytes());
242         when(mBluetoothDevice.getMetadata(
243                 BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
244                 String.valueOf(BATTERY_LEVEL_RIGHT).getBytes());
245         when(mBluetoothDevice.getMetadata(
246                 BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
247                 String.valueOf(BATTERY_LEVEL_MAIN).getBytes());
248 
249         when(mCachedDevice.isConnected()).thenReturn(true);
250         mController.refresh();
251 
252         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_left), BATTERY_LEVEL_LEFT);
253         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_right), BATTERY_LEVEL_RIGHT);
254         assertBatteryLevel(mLayoutPreference.findViewById(R.id.layout_middle), BATTERY_LEVEL_MAIN);
255     }
256 
257     @Test
refresh_disconnected_updateCorrectInfo()258     public void refresh_disconnected_updateCorrectInfo() {
259         when(mCachedDevice.isConnected()).thenReturn(false);
260 
261         mController.refresh();
262 
263         final LinearLayout layout = mLayoutPreference.findViewById(R.id.layout_middle);
264 
265         assertThat(mLayoutPreference.findViewById(R.id.layout_left).getVisibility()).isEqualTo(
266                 View.GONE);
267         assertThat(mLayoutPreference.findViewById(R.id.layout_right).getVisibility()).isEqualTo(
268                 View.GONE);
269         assertThat(layout.getVisibility()).isEqualTo(View.VISIBLE);
270         assertThat(layout.findViewById(R.id.header_title).getVisibility()).isEqualTo(View.GONE);
271         assertThat(layout.findViewById(R.id.bt_battery_summary).getVisibility()).isEqualTo(
272                 View.GONE);
273         assertThat(layout.findViewById(R.id.bt_battery_icon).getVisibility()).isEqualTo(View.GONE);
274         assertThat(layout.findViewById(R.id.header_icon).getVisibility()).isEqualTo(View.VISIBLE);
275     }
276 
277     @Ignore
278     @Test
refresh_connectedWatch_checkSummary()279     public void refresh_connectedWatch_checkSummary() {
280         when(mBluetoothDevice.getMetadata(
281                 BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
282                 BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
283         when(mCachedDevice.isConnected()).thenReturn(true);
284         when(mCachedDevice.getConnectionSummary(/* shortSummary= */ true))
285                 .thenReturn(DEVICE_SUMMARY);
286 
287         mController.refresh();
288 
289         assertThat(((TextView) (mLayoutPreference.findViewById(R.id.entity_header_summary)))
290                 .getText()).isEqualTo(DEVICE_SUMMARY);
291     }
292 
293     @Test
refresh_withLowBatteryAndUncharged_showAlertIcon()294     public void refresh_withLowBatteryAndUncharged_showAlertIcon() {
295         when(mBluetoothDevice.getMetadata(
296                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
297                 String.valueOf(true).getBytes());
298         when(mBluetoothDevice.getMetadata(
299                 BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
300                 String.valueOf(LOW_BATTERY_LEVEL).getBytes());
301         when(mBluetoothDevice.getMetadata(
302                 BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
303                 String.valueOf(LOW_BATTERY_LEVEL).getBytes());
304         when(mBluetoothDevice.getMetadata(
305                 BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
306                 String.valueOf(CASE_LOW_BATTERY_LEVEL).getBytes());
307         when(mBluetoothDevice.getMetadata(
308                 BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING)).thenReturn(
309                 String.valueOf(false).getBytes());
310         when(mBluetoothDevice.getMetadata(
311                 BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING)).thenReturn(
312                 String.valueOf(true).getBytes());
313         when(mBluetoothDevice.getMetadata(
314                 BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING)).thenReturn(
315                 String.valueOf(false).getBytes());
316         when(mCachedDevice.isConnected()).thenReturn(true);
317 
318         mController.refresh();
319 
320         assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_left),
321                 R.drawable.ic_battery_alert_24dp);
322         assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_right), /* resId= */-1);
323         assertBatteryIcon(mLayoutPreference.findViewById(R.id.layout_middle),
324                 R.drawable.ic_battery_alert_24dp);
325     }
326 
327     @Test
getAvailabilityStatus_untetheredHeadsetWithConfigOn_returnAvailable()328     public void getAvailabilityStatus_untetheredHeadsetWithConfigOn_returnAvailable() {
329         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
330                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
331         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
332                 .thenReturn("true".getBytes());
333 
334         assertThat(mController.getAvailabilityStatus()).isEqualTo(
335                 BasePreferenceController.AVAILABLE);
336     }
337 
338     @Test
getAvailabilityStatus_untetheredHeadsetWithConfigOff_returnUnavailable()339     public void getAvailabilityStatus_untetheredHeadsetWithConfigOff_returnUnavailable() {
340         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
341                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "false", true);
342         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
343                 .thenReturn("true".getBytes());
344 
345         assertThat(mController.getAvailabilityStatus()).isEqualTo(
346                 BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
347     }
348 
349     @Test
getAvailabilityStatus_notUntetheredHeadsetWithConfigOn_returnUnavailable()350     public void getAvailabilityStatus_notUntetheredHeadsetWithConfigOn_returnUnavailable() {
351         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
352                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
353         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
354                 .thenReturn("false".getBytes());
355 
356         assertThat(mController.getAvailabilityStatus()).isEqualTo(
357                 BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
358     }
359 
360     @Test
getAvailabilityStatus_notUntetheredHeadsetWithConfigOff_returnUnavailable()361     public void getAvailabilityStatus_notUntetheredHeadsetWithConfigOff_returnUnavailable() {
362         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
363                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "false", true);
364         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
365                 .thenReturn("false".getBytes());
366 
367         assertThat(mController.getAvailabilityStatus()).isEqualTo(
368                 BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
369     }
370 
371     @Test
updateIcon_existInCache_setImageBitmap()372     public void updateIcon_existInCache_setImageBitmap() {
373         mController.mIconCache.put(ICON_URI, mBitmap);
374 
375         mController.updateIcon(mImageView, ICON_URI);
376 
377         verify(mImageView).setImageBitmap(mBitmap);
378     }
379 
380     @Test
onStart_isAvailable_registerCallback()381     public void onStart_isAvailable_registerCallback() {
382         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
383                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
384         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
385                 .thenReturn("true".getBytes());
386         Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
387         when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
388         when(mBluetoothAdapter.addOnMetadataChangedListener(
389                 mBluetoothDevice, mContext.getMainExecutor(), mController.mMetadataListener))
390                 .thenReturn(true);
391 
392         mController.onStart();
393 
394         verify(mCachedDevice).registerCallback(mController);
395         verify(mBluetoothAdapter).addOnMetadataChangedListener(mBluetoothDevice,
396                 mContext.getMainExecutor(), mController.mMetadataListener);
397     }
398 
399     @Test
onStart_notAvailable_notNeedToRegisterCallback()400     public void onStart_notAvailable_notNeedToRegisterCallback() {
401         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
402                 .thenReturn("false".getBytes());
403 
404         mController.onStart();
405 
406         verify(mCachedDevice, never()).registerCallback(mController);
407         verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
408                 mContext.getMainExecutor(), mController.mMetadataListener);
409     }
410 
411     @Test
onStart_isAvailableButNoBluetoothDevice_notNeedToRegisterCallback()412     public void onStart_isAvailableButNoBluetoothDevice_notNeedToRegisterCallback() {
413         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
414                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
415         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
416                 .thenReturn("true".getBytes());
417         when(mCachedDevice.getDevice()).thenReturn(null);
418         Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
419         when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
420 
421         mController.onStart();
422 
423         verify(mCachedDevice, never()).registerCallback(mController);
424         verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
425                 mContext.getMainExecutor(), mController.mMetadataListener);
426     }
427 
428     @Test
onStop_availableAndHasBluetoothDevice_unregisterCallback()429     public void onStop_availableAndHasBluetoothDevice_unregisterCallback() {
430         onStart_isAvailable_registerCallback();
431 
432         mController.onStop();
433 
434         verify(mCachedDevice).unregisterCallback(mController);
435         verify(mBluetoothAdapter).removeOnMetadataChangedListener(mBluetoothDevice,
436                 mController.mMetadataListener);
437     }
438 
439     @Test
onStop_noBluetoothDevice_noNeedToUnregisterCallback()440     public void onStop_noBluetoothDevice_noNeedToUnregisterCallback() {
441         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
442                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
443         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
444                 .thenReturn("true".getBytes());
445         when(mCachedDevice.getDevice()).thenReturn(null);
446 
447         mController.onStart();
448         mController.onStop();
449 
450         verify(mCachedDevice, never()).unregisterCallback(mController);
451         verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(mBluetoothDevice,
452                 mController.mMetadataListener);
453     }
454 
455     @Test
onDestroy_recycleBitmap()456     public void onDestroy_recycleBitmap() {
457         mController.mIconCache.put(ICON_URI, mBitmap);
458 
459         mController.onDestroy();
460 
461         assertThat(mController.mIconCache).isEmpty();
462         verify(mBitmap).recycle();
463     }
464 
465     @Test
estimateReadyIsBothAvailable_showsView()466     public void estimateReadyIsBothAvailable_showsView() {
467         mController.mIsLeftDeviceEstimateReady = true;
468         mController.mIsRightDeviceEstimateReady = true;
469 
470         mController.showBothDevicesBatteryPredictionIfNecessary();
471 
472         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
473                 View.VISIBLE);
474         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
475                 View.VISIBLE);
476     }
477 
478     @Test
leftDeviceEstimateIsReadyRightDeviceIsNotReady_notShowView()479     public void leftDeviceEstimateIsReadyRightDeviceIsNotReady_notShowView() {
480         mController.mIsLeftDeviceEstimateReady = true;
481         mController.mIsRightDeviceEstimateReady = false;
482 
483         mController.showBothDevicesBatteryPredictionIfNecessary();
484 
485         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
486                 View.GONE);
487         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
488                 View.GONE);
489     }
490 
491     @Test
leftDeviceEstimateIsNotReadyRightDeviceIsReady_notShowView()492     public void leftDeviceEstimateIsNotReadyRightDeviceIsReady_notShowView() {
493         mController.mIsLeftDeviceEstimateReady = false;
494         mController.mIsRightDeviceEstimateReady = true;
495 
496         mController.showBothDevicesBatteryPredictionIfNecessary();
497 
498         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
499                 View.GONE);
500         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
501                 View.GONE);
502     }
503 
504     @Test
bothDevicesEstimateIsNotReady_notShowView()505     public void bothDevicesEstimateIsNotReady_notShowView() {
506         mController.mIsLeftDeviceEstimateReady = false;
507         mController.mIsRightDeviceEstimateReady = false;
508 
509         mController.showBothDevicesBatteryPredictionIfNecessary();
510 
511         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
512                 View.GONE);
513         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
514                 View.GONE);
515     }
516 
517     @Test
showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showCorrectValue()518     public void showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showCorrectValue() {
519         final String leftBatteryPrediction =
520                 StringUtil.formatElapsedTime(mContext, 12000000, false, false).toString();
521         final String rightBatteryPrediction =
522                 StringUtil.formatElapsedTime(mContext, 1200000, false, false).toString();
523 
524         mController.showBatteryPredictionIfNecessary(1, 12000000,
525                 mLayoutPreference.findViewById(R.id.layout_left));
526         mController.showBatteryPredictionIfNecessary(1, 1200000,
527                 mLayoutPreference.findViewById(R.id.layout_right));
528 
529         assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_left),
530                 leftBatteryPrediction);
531         assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_right),
532                 rightBatteryPrediction);
533     }
534 
assertBatteryPredictionVisible(LinearLayout linearLayout, int visible)535     private void assertBatteryPredictionVisible(LinearLayout linearLayout, int visible) {
536         final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
537         assertThat(textView.getVisibility()).isEqualTo(visible);
538     }
539 
assertBatteryPrediction(LinearLayout linearLayout, String prediction)540     private void assertBatteryPrediction(LinearLayout linearLayout, String prediction) {
541         final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
542         assertThat(textView.getText().toString()).isEqualTo(prediction);
543     }
544 
assertBatteryLevel(LinearLayout linearLayout, int batteryLevel)545     private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
546         final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
547         assertThat(textView.getText().toString()).isEqualTo(
548                 com.android.settings.Utils.formatPercentage(batteryLevel));
549     }
550 
assertBatteryIcon(LinearLayout linearLayout, int resId)551     private void assertBatteryIcon(LinearLayout linearLayout, int resId) {
552         final ImageView imageView = linearLayout.findViewById(R.id.bt_battery_icon);
553         assertThat(shadowOf(imageView.getDrawable()).getCreatedFromResId())
554                 .isEqualTo(resId);
555     }
556 
557 }
558