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.settingslib.bluetooth;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.anyString;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.never;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.bluetooth.BluetoothAdapter;
31 import android.bluetooth.BluetoothDevice;
32 import android.bluetooth.BluetoothLeAudio;
33 import android.bluetooth.BluetoothProfile;
34 import android.bluetooth.BluetoothStatusCodes;
35 import android.content.Context;
36 import android.graphics.drawable.BitmapDrawable;
37 import android.media.AudioManager;
38 import android.util.LruCache;
39 
40 import com.android.settingslib.R;
41 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
42 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
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.shadow.api.Shadow;
53 
54 @RunWith(RobolectricTestRunner.class)
55 @Config(shadows = {ShadowBluetoothAdapter.class})
56 public class CachedBluetoothDeviceTest {
57     private static final String DEVICE_NAME = "TestName";
58     private static final String DEVICE_ALIAS = "TestAlias";
59     private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF";
60     private static final String DEVICE_ALIAS_NEW = "TestAliasNew";
61     private static final String TWS_BATTERY_LEFT = "15";
62     private static final String TWS_BATTERY_RIGHT = "25";
63     private static final short RSSI_1 = 10;
64     private static final short RSSI_2 = 11;
65     private static final boolean JUSTDISCOVERED_1 = true;
66     private static final boolean JUSTDISCOVERED_2 = false;
67     @Mock
68     private LocalBluetoothProfileManager mProfileManager;
69     @Mock
70     private HeadsetProfile mHfpProfile;
71     @Mock
72     private A2dpProfile mA2dpProfile;
73     @Mock
74     private PanProfile mPanProfile;
75     @Mock
76     private HearingAidProfile mHearingAidProfile;
77     @Mock
78     private HapClientProfile mHapClientProfile;
79     @Mock
80     private LeAudioProfile mLeAudioProfile;
81     @Mock
82     private BluetoothDevice mDevice;
83     @Mock
84     private BluetoothDevice mSubDevice;
85     private CachedBluetoothDevice mCachedDevice;
86     private CachedBluetoothDevice mSubCachedDevice;
87     private AudioManager mAudioManager;
88     private Context mContext;
89     private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
90     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
91 
92     @Before
setUp()93     public void setUp() {
94         MockitoAnnotations.initMocks(this);
95         mContext = RuntimeEnvironment.application;
96         mAudioManager = mContext.getSystemService(AudioManager.class);
97         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
98         when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
99         when(mHfpProfile.isProfileReady()).thenReturn(true);
100         when(mHfpProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET);
101         when(mA2dpProfile.isProfileReady()).thenReturn(true);
102         when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
103         when(mPanProfile.isProfileReady()).thenReturn(true);
104         when(mPanProfile.getProfileId()).thenReturn(BluetoothProfile.PAN);
105         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
106         when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
107         when(mLeAudioProfile.isProfileReady()).thenReturn(true);
108         when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
109         mCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mDevice));
110         mSubCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mSubDevice));
111         doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
112         doAnswer((invocation) -> mBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
113     }
114 
testTransitionFromConnectingToDisconnected( LocalBluetoothProfile connectingProfile, LocalBluetoothProfile connectedProfile, int connectionPolicy, String expectedSummary)115     private void testTransitionFromConnectingToDisconnected(
116         LocalBluetoothProfile connectingProfile, LocalBluetoothProfile connectedProfile,
117         int connectionPolicy, String expectedSummary) {
118         // Arrange:
119         // At least one profile has to be connected
120         updateProfileStatus(connectedProfile, BluetoothProfile.STATE_CONNECTED);
121         // Set profile under test to CONNECTING
122         updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING);
123         // Set connection policy
124         when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy);
125 
126         // Act & Assert:
127         //   Get the expected connection summary.
128         updateProfileStatus(connectingProfile, BluetoothProfile.STATE_DISCONNECTED);
129         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(expectedSummary);
130     }
131 
132     @Test
onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem()133     public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() {
134         String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext);
135 
136         testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
137         BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
138         testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
139         BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
140         testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
141         BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
142         testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
143         BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
144     }
145 
146     @Test
onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem()147     public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() {
148         testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
149         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
150         testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
151         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
152         testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
153         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
154         testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
155         BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
156     }
157 
158     @Test
onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem()159     public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() {
160         testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
161         BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
162         testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
163         BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
164         testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
165         BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
166         testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
167         BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
168     }
169 
170     @Test
getConnectionSummary_testProfilesInactive_returnPairing()171     public void getConnectionSummary_testProfilesInactive_returnPairing() {
172         // Arrange:
173         //   Bond State: Bonding
174         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
175 
176         // Act & Assert:
177         //   Get "Pairing…" result without Battery Level.
178         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Pairing…");
179     }
180 
181     @Test
getConnectionSummary_testSingleProfileConnectDisconnect()182     public void getConnectionSummary_testSingleProfileConnectDisconnect() {
183         // Test without battery level
184         // Set PAN profile to be connected and test connection state summary
185         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
186         assertThat(mCachedDevice.getConnectionSummary()).isNull();
187 
188         // Set PAN profile to be disconnected and test connection state summary
189         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
190         assertThat(mCachedDevice.getConnectionSummary()).isNull();
191 
192         // Test with battery level
193         mBatteryLevel = 10;
194         // Set PAN profile to be connected and test connection state summary
195         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
196         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
197 
198         // Set PAN profile to be disconnected and test connection state summary
199         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
200         assertThat(mCachedDevice.getConnectionSummary()).isNull();
201 
202         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
203         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
204 
205         // Set PAN profile to be connected and test connection state summary
206         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
207         assertThat(mCachedDevice.getConnectionSummary()).isNull();
208 
209         // Set PAN profile to be disconnected and test connection state summary
210         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
211         assertThat(mCachedDevice.getConnectionSummary()).isNull();
212     }
213 
214     @Test
getConnectionSummary_testMultipleProfileConnectDisconnect()215     public void getConnectionSummary_testMultipleProfileConnectDisconnect() {
216         mBatteryLevel = 10;
217 
218         // Set HFP, A2DP and PAN profile to be connected and test connection state summary
219         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
220         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
221         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
222         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
223 
224         // Disconnect HFP only and test connection state summary
225         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
226         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
227                 "10% battery");
228 
229         // Disconnect A2DP only and test connection state summary
230         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
231         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
232         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
233                 "10% battery");
234 
235         // Disconnect both HFP and A2DP and test connection state summary
236         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
237         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
238                 "10% battery");
239 
240         // Disconnect all profiles and test connection state summary
241         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
242         assertThat(mCachedDevice.getConnectionSummary()).isNull();
243     }
244 
245     @Test
getConnectionSummary_testSingleProfileActiveDeviceA2dp()246     public void getConnectionSummary_testSingleProfileActiveDeviceA2dp() {
247         // Test without battery level
248         // Set A2DP profile to be connected and test connection state summary
249         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
250         assertThat(mCachedDevice.getConnectionSummary()).isNull();
251 
252         // Set device as Active for A2DP and test connection state summary
253         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
254         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
255 
256         // Test with battery level
257         mBatteryLevel = 10;
258         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
259                 "Active, 10% battery");
260 
261         // Set A2DP profile to be disconnected and test connection state summary
262         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
263         assertThat(mCachedDevice.getConnectionSummary()).isNull();
264 
265         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
266         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
267         // Set A2DP profile to be connected, Active and test connection state summary
268         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
269         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
270         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
271 
272         // Set A2DP profile to be disconnected and test connection state summary
273         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
274         assertThat(mCachedDevice.getConnectionSummary()).isNull();
275     }
276 
277     @Test
getConnectionSummary_shortSummary_returnShortSummary()278     public void getConnectionSummary_shortSummary_returnShortSummary() {
279         // Test without battery level
280         // Set A2DP profile to be connected and test connection state summary
281         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
282         assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isNull();
283 
284         // Set device as Active for A2DP and test connection state summary
285         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
286         assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isEqualTo("Active");
287 
288         // Test with battery level
289         mBatteryLevel = 10;
290         assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isEqualTo(
291                 "Active");
292 
293         // Set A2DP profile to be disconnected and test connection state summary
294         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
295         assertThat(mCachedDevice.getConnectionSummary()).isNull();
296     }
297 
298     @Test
getConnectionSummary_testA2dpBatteryInactive_returnBattery()299     public void getConnectionSummary_testA2dpBatteryInactive_returnBattery() {
300         // Arrange:
301         //   1. Profile:       {A2DP, CONNECTED, Inactive}
302         //   2. Battery Level: 10
303         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
304         mBatteryLevel = 10;
305 
306         // Act & Assert:
307         //   Get "10% battery" result without Battery Level.
308         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
309     }
310 
311     @Test
getConnectionSummary_testA2dpInCall_returnNull()312     public void getConnectionSummary_testA2dpInCall_returnNull() {
313         // Arrange:
314         //   1. Profile:       {A2DP, Connected, Active}
315         //   2. Audio Manager: In Call
316         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
317         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
318         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
319 
320         // Act & Assert:
321         //   Get null result without Battery Level.
322         assertThat(mCachedDevice.getConnectionSummary()).isNull();
323     }
324 
325     @Test
getConnectionSummary_testA2dpBatteryInCall_returnBattery()326     public void getConnectionSummary_testA2dpBatteryInCall_returnBattery() {
327         // Arrange:
328         //   1. Profile:       {A2DP, Connected, Active}
329         //   3. Battery Level: 10
330         //   2. Audio Manager: In Call
331         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
332         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
333         mBatteryLevel = 10;
334         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
335 
336         // Act & Assert:
337         //   Get "10% battery" result with Battery Level 10.
338         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
339     }
340 
341     @Test
getConnectionSummary_testSingleProfileActiveDeviceHfp()342     public void getConnectionSummary_testSingleProfileActiveDeviceHfp() {
343         // Test without battery level
344         // Set HFP profile to be connected and test connection state summary
345         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
346         assertThat(mCachedDevice.getConnectionSummary()).isNull();
347 
348         // Set device as Active for HFP and test connection state summary
349         mCachedDevice.onAudioModeChanged();
350         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
351         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
352         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
353 
354         // Test with battery level
355         mBatteryLevel = 10;
356         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
357 
358         // Set HFP profile to be disconnected and test connection state summary
359         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
360         assertThat(mCachedDevice.getConnectionSummary()).isNull();
361 
362         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
363         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
364         // Set HFP profile to be connected, Active and test connection state summary
365         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
366         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
367         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
368 
369         // Set HFP profile to be disconnected and test connection state summary
370         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
371         assertThat(mCachedDevice.getConnectionSummary()).isNull();
372     }
373 
374     @Test
getConnectionSummary_testHeadsetBatteryInactive_returnBattery()375     public void getConnectionSummary_testHeadsetBatteryInactive_returnBattery() {
376         // Arrange:
377         //   1. Profile:       {HEADSET, CONNECTED, Inactive}
378         //   2. Battery Level: 10
379         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
380         mBatteryLevel = 10;
381 
382         // Act & Assert:
383         //   Get "10% battery" result without Battery Level.
384         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
385     }
386 
387     @Test
getConnectionSummary_testHeadsetWithoutInCall_returnNull()388     public void getConnectionSummary_testHeadsetWithoutInCall_returnNull() {
389         // Arrange:
390         //   1. Profile:       {HEADSET, Connected, Active}
391         //   2. Audio Manager: Normal (Without In Call)
392         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
393         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
394 
395         // Act & Assert:
396         //   Get null result without Battery Level.
397         assertThat(mCachedDevice.getConnectionSummary()).isNull();
398     }
399 
400     @Test
getConnectionSummary_testHeadsetBatteryWithoutInCall_returnBattery()401     public void getConnectionSummary_testHeadsetBatteryWithoutInCall_returnBattery() {
402         // Arrange:
403         //   1. Profile:       {HEADSET, Connected, Active}
404         //   2. Battery Level: 10
405         //   3. Audio Manager: Normal (Without In Call)
406         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
407         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
408         mBatteryLevel = 10;
409 
410         // Act & Assert:
411         //   Get "10% battery" result with Battery Level 10.
412         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
413     }
414 
415     @Test
getConnectionSummary_testSingleProfileActiveDeviceHearingAid()416     public void getConnectionSummary_testSingleProfileActiveDeviceHearingAid() {
417         // Test without battery level
418         // Set Hearing Aid profile to be connected and test connection state summary
419         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
420         assertThat(mCachedDevice.getConnectionSummary()).isNull();
421 
422         // Set device as Active for Hearing Aid and test connection state summary
423         mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
424         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
425         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
426 
427         // Set Hearing Aid profile to be disconnected and test connection state summary
428         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEARING_AID);
429         mCachedDevice.
430                 onProfileStateChanged(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
431         assertThat(mCachedDevice.getConnectionSummary()).isNull();
432     }
433 
434     @Test
getConnectionSummary_testHearingAidBatteryInactive_returnBattery()435     public void getConnectionSummary_testHearingAidBatteryInactive_returnBattery() {
436         // Arrange:
437         //   1. Profile:       {HEARING_AID, CONNECTED, Inactive}
438         //   2. Battery Level: 10
439         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
440         mBatteryLevel = 10;
441 
442         // Act & Assert:
443         //   Get "10% battery" result without Battery Level.
444         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("10% battery");
445     }
446 
447     @Test
getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery()448     public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() {
449         // Arrange:
450         //   1. Profile:       {HEARING_AID, Connected, Active}
451         //   2. Battery Level: 10
452         //   3. Audio Manager: Normal (Without In Call)
453         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
454         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
455         mBatteryLevel = 10;
456 
457         // Act & Assert:
458         //   Get "Active, 10% battery" result with Battery Level 10.
459         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
460     }
461 
462     @Test
getConnectionSummary_testHearingAidRightEarInCall_returnActiveRightEar()463     public void getConnectionSummary_testHearingAidRightEarInCall_returnActiveRightEar() {
464         // Arrange:
465         //   1. Profile:       {HEARING_AID, Connected, Active, Right ear}
466         //   2. Audio Manager: In Call
467         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
468         mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
469         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
470         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
471 
472         // Act & Assert:
473         //   Get "Active" result without Battery Level.
474         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, right only");
475     }
476 
477     @Test
getConnectionSummary_testHearingAidBothEarInCall_returnActiveBothEar()478     public void getConnectionSummary_testHearingAidBothEarInCall_returnActiveBothEar() {
479         // Arrange:
480         //   1. Profile:       {HEARING_AID, Connected, Active, Both ear}
481         //   2. Audio Manager: In Call
482         mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
483         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
484         mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
485         updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
486         mCachedDevice.setSubDevice(mSubCachedDevice);
487         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
488         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
489 
490         // Act & Assert:
491         //   Get "Active" result without Battery Level.
492         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left and right");
493     }
494 
495     @Test
getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery()496     public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() {
497         // Arrange:
498         //   1. Profile:       {HEARING_AID, Connected, Active}
499         //   2. Battery Level: 10
500         //   3. Audio Manager: In Call
501         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
502         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
503         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
504         mBatteryLevel = 10;
505 
506         // Act & Assert:
507         //   Get "Active, 10% battery" result with Battery Level 10.
508         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery");
509     }
510 
511     @Test
getConnectionSummary_testActiveDeviceLeAudioHearingAid()512     public void getConnectionSummary_testActiveDeviceLeAudioHearingAid() {
513         // Test without battery level
514         // Set HAP Client and LE Audio profile to be connected and test connection state summary
515         when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
516         updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
517         updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
518         assertThat(mCachedDevice.getConnectionSummary()).isNull();
519 
520         // Set device as Active for LE Audio and test connection state summary
521         mCachedDevice.setHearingAidInfo(getLeftLeAudioHearingAidInfo());
522         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.LE_AUDIO);
523         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only");
524 
525         // Set LE Audio profile to be disconnected and test connection state summary
526         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.LE_AUDIO);
527         mCachedDevice.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
528         assertThat(mCachedDevice.getConnectionSummary()).isNull();
529     }
530 
531     @Test
getConnectionSummary_testMemberDevicesExist_returnMinBattery()532     public void getConnectionSummary_testMemberDevicesExist_returnMinBattery() {
533         // One device is active with battery level 70.
534         mBatteryLevel = 70;
535         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
536         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
537 
538 
539         // Add a member device with battery level 30.
540         int lowerBatteryLevel = 30;
541         mCachedDevice.addMemberDevice(mSubCachedDevice);
542         doAnswer((invocation) -> lowerBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
543 
544         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 30% battery");
545     }
546 
547     @Test
getConnectionSummary_testMemberDevicesBatteryUnknown_returnMinBattery()548     public void getConnectionSummary_testMemberDevicesBatteryUnknown_returnMinBattery() {
549         // One device is active with battery level 70.
550         mBatteryLevel = 70;
551         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
552         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
553 
554         // Add a member device with battery level unknown.
555         mCachedDevice.addMemberDevice(mSubCachedDevice);
556         doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
557                 mSubCachedDevice).getBatteryLevel();
558 
559         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 70% battery");
560     }
561 
562     @Test
getConnectionSummary_testAllDevicesBatteryUnknown_returnNoBattery()563     public void getConnectionSummary_testAllDevicesBatteryUnknown_returnNoBattery() {
564         // One device is active with battery level unknown.
565         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
566         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
567 
568         // Add a member device with battery level unknown.
569         mCachedDevice.addMemberDevice(mSubCachedDevice);
570         doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
571                 mSubCachedDevice).getBatteryLevel();
572 
573         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
574     }
575 
576     @Test
getConnectionSummary_testMultipleProfilesActiveDevice()577     public void getConnectionSummary_testMultipleProfilesActiveDevice() {
578         // Test without battery level
579         // Set A2DP and HFP profiles to be connected and test connection state summary
580         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
581         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
582         assertThat(mCachedDevice.getConnectionSummary()).isNull();
583 
584         // Set device as Active for A2DP and HFP and test connection state summary
585         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
586         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
587         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
588 
589         // Test with battery level
590         mBatteryLevel = 10;
591         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
592                 "Active, 10% battery");
593 
594         // Disconnect A2DP only and test connection state summary
595         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.A2DP);
596         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
597         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
598                 "10% battery");
599 
600         // Disconnect HFP only and test connection state summary
601         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEADSET);
602         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
603         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
604         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
605         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
606                 "Active, 10% battery");
607 
608         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
609         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
610         // Set A2DP and HFP profiles to be connected, Active and test connection state summary
611         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
612         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
613         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
614         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
615         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
616 
617         // Set A2DP and HFP profiles to be disconnected and test connection state summary
618         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
619         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
620         assertThat(mCachedDevice.getConnectionSummary()).isNull();
621     }
622 
623     @Test
getConnectionSummary_testMultipleProfilesInactive_returnPairing()624     public void getConnectionSummary_testMultipleProfilesInactive_returnPairing() {
625         // Arrange:
626         //   1. Profile 1:  {A2DP, CONNECTED, Inactive}
627         //   2. Profile 2:  {HEADSET, CONNECTED, Inactive}
628         //   3. Profile 3:  {HEARING_AID, CONNECTED, Inactive}
629         //   4. Bond State: Bonding
630         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
631         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
632         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
633         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
634 
635         // Act & Assert:
636         //    Get "Pairing…" result without Battery Level.
637         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Pairing…");
638     }
639 
640     @Test
getConnectionSummary_trueWirelessActiveDeviceWithBattery_returnActiveWithBattery()641     public void getConnectionSummary_trueWirelessActiveDeviceWithBattery_returnActiveWithBattery() {
642         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
643         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
644         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
645         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
646         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
647         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
648                 "true".getBytes());
649         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
650                 TWS_BATTERY_LEFT.getBytes());
651         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
652                 TWS_BATTERY_RIGHT.getBytes());
653 
654         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
655                 "Active, L: 15% battery, R: 25% battery");
656     }
657 
658     @Test
getConnectionSummary_trueWirelessDeviceWithBattery_returnActiveWithBattery()659     public void getConnectionSummary_trueWirelessDeviceWithBattery_returnActiveWithBattery() {
660         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
661         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
662         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
663         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
664         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
665                 "true".getBytes());
666         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
667                 TWS_BATTERY_LEFT.getBytes());
668         when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
669                 TWS_BATTERY_RIGHT.getBytes());
670 
671         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
672                 "L: 15% battery, R: 25% battery");
673     }
674 
675     @Test
getCarConnectionSummary_singleProfileConnectDisconnect()676     public void getCarConnectionSummary_singleProfileConnectDisconnect() {
677         // Test without battery level
678         // Set PAN profile to be connected and test connection state summary
679         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
680         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
681 
682         // Set PAN profile to be disconnected and test connection state summary
683         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
684         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
685 
686         // Test with battery level
687         mBatteryLevel = 10;
688         // Set PAN profile to be connected and test connection state summary
689         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
690         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, battery 10%");
691 
692         // Set PAN profile to be disconnected and test connection state summary
693         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
694         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
695 
696         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
697         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
698 
699         // Set PAN profile to be connected and test connection state summary
700         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
701         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
702 
703         // Set PAN profile to be disconnected and test connection state summary
704         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
705         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
706     }
707 
708     @Test
getCarConnectionSummary_multipleProfileConnectDisconnect()709     public void getCarConnectionSummary_multipleProfileConnectDisconnect() {
710         mBatteryLevel = 10;
711 
712         // Set HFP, A2DP and PAN profile to be connected and test connection state summary
713         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
714         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
715         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_CONNECTED);
716         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, battery 10%");
717 
718         // Disconnect HFP only and test connection state summary
719         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
720         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
721                 "Connected (no phone), battery 10%");
722 
723         // Disconnect A2DP only and test connection state summary
724         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
725         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
726         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
727                 "Connected (no media), battery 10%");
728 
729         // Disconnect both HFP and A2DP and test connection state summary
730         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
731         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
732                 "Connected (no phone or media), battery 10%");
733 
734         // Disconnect all profiles and test connection state summary
735         updateProfileStatus(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
736         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
737     }
738 
739     @Test
getCarConnectionSummary_singleProfileActiveDeviceA2dp()740     public void getCarConnectionSummary_singleProfileActiveDeviceA2dp() {
741         // Test without battery level
742         // Set A2DP profile to be connected and test connection state summary
743         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
744         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
745 
746         // Set device as Active for A2DP and test connection state summary
747         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
748         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active (media)");
749 
750         // Test with battery level
751         mBatteryLevel = 10;
752         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
753                 "Connected, battery 10%, active (media)");
754 
755         // Set A2DP profile to be disconnected and test connection state summary
756         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
757         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
758 
759         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
760         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
761         // Set A2DP profile to be connected, Active and test connection state summary
762         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
763         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
764         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active (media)");
765 
766         // Set A2DP profile to be disconnected and test connection state summary
767         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
768         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
769     }
770 
771     @Test
getCarConnectionSummary_singleProfileActiveDeviceHfp()772     public void getCarConnectionSummary_singleProfileActiveDeviceHfp() {
773         // Test without battery level
774         // Set HFP profile to be connected and test connection state summary
775         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
776         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
777 
778         // Set device as Active for HFP and test connection state summary
779         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
780         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active (phone)");
781 
782         // Test with battery level
783         mBatteryLevel = 10;
784         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
785                 "Connected, battery 10%, active (phone)");
786 
787         // Set HFP profile to be disconnected and test connection state summary
788         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
789         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
790 
791         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
792         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
793         // Set HFP profile to be connected, Active and test connection state summary
794         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
795         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
796         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active (phone)");
797 
798         // Set HFP profile to be disconnected and test connection state summary
799         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
800         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
801     }
802 
803     @Test
getCarConnectionSummary_singleProfileActiveDeviceHearingAid()804     public void getCarConnectionSummary_singleProfileActiveDeviceHearingAid() {
805         // Test without battery level
806         // Set Hearing Aid profile to be connected and test connection state summary
807         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
808         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
809 
810         // Set device as Active for Hearing Aid and test connection state summary
811         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID);
812         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active");
813 
814         // Set Hearing Aid profile to be disconnected and test connection state summary
815         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEARING_AID);
816         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
817         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
818     }
819 
820     @Test
getCarConnectionSummary_multipleProfilesActiveDevice()821     public void getCarConnectionSummary_multipleProfilesActiveDevice() {
822         // Test without battery level
823         // Set A2DP and HFP profiles to be connected and test connection state summary
824         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
825         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
826         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected");
827 
828         // Set device as Active for A2DP and HFP and test connection state summary
829         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
830         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
831         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active");
832 
833         // Test with battery level
834         mBatteryLevel = 10;
835         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
836                 "Connected, battery 10%, active");
837 
838         // Disconnect A2DP only and test connection state summary
839         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.A2DP);
840         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
841         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
842                 "Connected (no media), battery 10%, active (phone)");
843 
844         // Disconnect HFP only and test connection state summary
845         mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEADSET);
846         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
847         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
848         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
849         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo(
850                 "Connected (no phone), battery 10%, active (media)");
851 
852         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
853         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
854         // Set A2DP and HFP profiles to be connected, Active and test connection state summary
855         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
856         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
857         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
858         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
859         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Connected, active");
860 
861         // Set A2DP and HFP profiles to be disconnected and test connection state summary
862         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
863         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
864         assertThat(mCachedDevice.getCarConnectionSummary()).isEqualTo("Disconnected");
865     }
866 
867     @Test
getCarConnectionSummary_shortSummary_returnShortSummary()868     public void getCarConnectionSummary_shortSummary_returnShortSummary() {
869         // Test without battery level
870         // Set A2DP profile to be connected and test connection state summary
871         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
872         assertThat(mCachedDevice.getCarConnectionSummary(true /* shortSummary */))
873                 .isEqualTo("Connected");
874 
875         // Set device as Active for A2DP and test connection state summary
876         mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
877         assertThat(mCachedDevice.getCarConnectionSummary(true /* shortSummary */))
878                 .isEqualTo("Connected");
879 
880         // Test with battery level
881         mBatteryLevel = 10;
882         assertThat(mCachedDevice.getCarConnectionSummary(true /* shortSummary */))
883                 .isEqualTo("Connected");
884 
885         // Set A2DP profile to be disconnected and test connection state summary
886         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
887         assertThat(mCachedDevice.getCarConnectionSummary(true /* shortSummary */))
888                 .isEqualTo("Disconnected");
889     }
890 
891     @Test
deviceName_testAliasNameAvailable()892     public void deviceName_testAliasNameAvailable() {
893         when(mDevice.getAlias()).thenReturn(DEVICE_ALIAS);
894         when(mDevice.getName()).thenReturn(DEVICE_NAME);
895         CachedBluetoothDevice cachedBluetoothDevice =
896                 new CachedBluetoothDevice(mContext, mProfileManager, mDevice);
897         // Verify alias is returned on getName
898         assertThat(cachedBluetoothDevice.getName()).isEqualTo(DEVICE_ALIAS);
899         // Verify device is visible
900         assertThat(cachedBluetoothDevice.hasHumanReadableName()).isTrue();
901     }
902 
903     @Test
deviceName_testNameNotAvailable()904     public void deviceName_testNameNotAvailable() {
905         CachedBluetoothDevice cachedBluetoothDevice =
906                 new CachedBluetoothDevice(mContext, mProfileManager, mDevice);
907         // Verify device address is returned on getName
908         assertThat(cachedBluetoothDevice.getName()).isEqualTo(DEVICE_ADDRESS);
909         // Verify device is not visible
910         assertThat(cachedBluetoothDevice.hasHumanReadableName()).isFalse();
911     }
912 
913     @Test
deviceName_testRenameDevice()914     public void deviceName_testRenameDevice() {
915         final String[] alias = {DEVICE_ALIAS};
916         doAnswer(invocation -> alias[0]).when(mDevice).getAlias();
917         doAnswer(invocation -> {
918             alias[0] = (String) invocation.getArguments()[0];
919             return BluetoothStatusCodes.SUCCESS;
920         }).when(mDevice).setAlias(anyString());
921         when(mDevice.getName()).thenReturn(DEVICE_NAME);
922         CachedBluetoothDevice cachedBluetoothDevice =
923                 new CachedBluetoothDevice(mContext, mProfileManager, mDevice);
924         // Verify alias is returned on getName
925         assertThat(cachedBluetoothDevice.getName()).isEqualTo(DEVICE_ALIAS);
926         // Verify null name does not get set
927         cachedBluetoothDevice.setName(null);
928         verify(mDevice, never()).setAlias(any());
929         // Verify new name is set properly
930         cachedBluetoothDevice.setName(DEVICE_ALIAS_NEW);
931         verify(mDevice).setAlias(DEVICE_ALIAS_NEW);
932         // Verify new alias is returned on getName
933         assertThat(cachedBluetoothDevice.getName()).isEqualTo(DEVICE_ALIAS_NEW);
934     }
935 
936     @Test
setActive()937     public void setActive() {
938         when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
939         when(mProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
940         when(mA2dpProfile.setActiveDevice(any(BluetoothDevice.class))).thenReturn(true);
941         when(mHfpProfile.setActiveDevice(any(BluetoothDevice.class))).thenReturn(true);
942 
943         assertThat(mCachedDevice.setActive()).isFalse();
944 
945         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
946         assertThat(mCachedDevice.setActive()).isTrue();
947 
948         updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
949         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
950         assertThat(mCachedDevice.setActive()).isTrue();
951 
952         updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
953         assertThat(mCachedDevice.setActive()).isFalse();
954     }
955 
956     @Test
isA2dpDevice_isA2dpDevice()957     public void isA2dpDevice_isA2dpDevice() {
958         when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
959         when(mA2dpProfile.getConnectionStatus(mDevice)).
960                 thenReturn(BluetoothProfile.STATE_CONNECTED);
961 
962         assertThat(mCachedDevice.isConnectedA2dpDevice()).isTrue();
963     }
964 
965     @Test
isA2dpDevice_isNotA2dpDevice()966     public void isA2dpDevice_isNotA2dpDevice() {
967         when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
968         when(mA2dpProfile.getConnectionStatus(mDevice)).
969                 thenReturn(BluetoothProfile.STATE_DISCONNECTING);
970 
971         assertThat(mCachedDevice.isConnectedA2dpDevice()).isFalse();
972     }
973 
974     @Test
isHfpDevice_isHfpDevice()975     public void isHfpDevice_isHfpDevice() {
976         when(mProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
977         when(mHfpProfile.getConnectionStatus(mDevice)).
978                 thenReturn(BluetoothProfile.STATE_CONNECTED);
979 
980         assertThat(mCachedDevice.isConnectedHfpDevice()).isTrue();
981     }
982 
983     @Test
testIsHfpDevice_isNotHfpDevice()984     public void testIsHfpDevice_isNotHfpDevice() {
985         when(mProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
986         when(mHfpProfile.getConnectionStatus(mDevice)).
987                 thenReturn(BluetoothProfile.STATE_DISCONNECTING);
988 
989         assertThat(mCachedDevice.isConnectedHfpDevice()).isFalse();
990     }
991 
992     @Test
isConnectedAshaHearingAidDevice_connected_returnTrue()993     public void isConnectedAshaHearingAidDevice_connected_returnTrue() {
994         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
995         when(mHearingAidProfile.getConnectionStatus(mDevice)).
996                 thenReturn(BluetoothProfile.STATE_CONNECTED);
997 
998         assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isTrue();
999     }
1000 
1001     @Test
isConnectedAshaHearingAidDevice_disconnected_returnFalse()1002     public void isConnectedAshaHearingAidDevice_disconnected_returnFalse() {
1003         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
1004         when(mHearingAidProfile.getConnectionStatus(mDevice)).
1005                 thenReturn(BluetoothProfile.STATE_DISCONNECTED);
1006 
1007         assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
1008     }
1009 
1010     @Test
isConnectedHfpDevice_profileIsNull_returnFalse()1011     public void isConnectedHfpDevice_profileIsNull_returnFalse() {
1012         when(mProfileManager.getHeadsetProfile()).thenReturn(null);
1013 
1014         assertThat(mCachedDevice.isConnectedHfpDevice()).isFalse();
1015     }
1016 
1017     @Test
isConnectedA2dpDevice_profileIsNull_returnFalse()1018     public void isConnectedA2dpDevice_profileIsNull_returnFalse() {
1019         when(mProfileManager.getA2dpProfile()).thenReturn(null);
1020 
1021         assertThat(mCachedDevice.isConnectedA2dpDevice()).isFalse();
1022     }
1023 
1024     @Test
isConnectedAshaHearingAidDevice_profileIsNull_returnFalse()1025     public void isConnectedAshaHearingAidDevice_profileIsNull_returnFalse() {
1026         when(mProfileManager.getHearingAidProfile()).thenReturn(null);
1027 
1028         assertThat(mCachedDevice.isConnectedAshaHearingAidDevice()).isFalse();
1029     }
1030 
1031     @Test
getName_aliasNameNotNull_returnAliasName()1032     public void getName_aliasNameNotNull_returnAliasName() {
1033         when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
1034 
1035         assertThat(mCachedDevice.getName()).isEqualTo(DEVICE_NAME);
1036     }
1037 
1038     @Test
getName_aliasNameIsNull_returnAddress()1039     public void getName_aliasNameIsNull_returnAddress() {
1040         when(mDevice.getAlias()).thenReturn(null);
1041 
1042         assertThat(mCachedDevice.getName()).isEqualTo(DEVICE_ADDRESS);
1043     }
1044 
1045     @Test
setName_setDeviceNameIsNotNull()1046     public void setName_setDeviceNameIsNotNull() {
1047         final String name = "test name";
1048         when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
1049 
1050         mCachedDevice.setName(name);
1051 
1052         verify(mDevice).setAlias(name);
1053     }
1054 
1055     @Test
setName_setDeviceNameIsNull()1056     public void setName_setDeviceNameIsNull() {
1057         mCachedDevice.setName(null);
1058 
1059         verify(mDevice, never()).setAlias(any());
1060     }
1061 
1062     @Test
setName_setDeviceNameIsEmpty()1063     public void setName_setDeviceNameIsEmpty() {
1064         mCachedDevice.setName("");
1065 
1066         verify(mDevice, never()).setAlias(any());
1067     }
1068 
1069     @Test
getProfileConnectionState_nullProfile_returnDisconnected()1070     public void getProfileConnectionState_nullProfile_returnDisconnected() {
1071         assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
1072                 BluetoothProfile.STATE_DISCONNECTED);
1073     }
1074 
1075     @Test
getProfileConnectionState_profileConnected_returnConnected()1076     public void getProfileConnectionState_profileConnected_returnConnected() {
1077         doReturn(BluetoothProfile.STATE_CONNECTED).when(mA2dpProfile).getConnectionStatus(
1078                 any(BluetoothDevice.class));
1079 
1080         assertThat(mCachedDevice.getProfileConnectionState(mA2dpProfile)).isEqualTo(
1081                 BluetoothProfile.STATE_CONNECTED);
1082     }
1083 
updateProfileStatus(LocalBluetoothProfile profile, int status)1084     private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
1085         doReturn(status).when(profile).getConnectionStatus(mDevice);
1086         mCachedDevice.onProfileStateChanged(profile, status);
1087     }
1088 
updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status)1089     private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) {
1090         doReturn(status).when(profile).getConnectionStatus(mSubDevice);
1091         mSubCachedDevice.onProfileStateChanged(profile, status);
1092     }
1093 
1094     @Test
getSubDevice_setSubDevice()1095     public void getSubDevice_setSubDevice() {
1096         mCachedDevice.setSubDevice(mSubCachedDevice);
1097 
1098         assertThat(mCachedDevice.getSubDevice()).isEqualTo(mSubCachedDevice);
1099     }
1100 
1101     @Test
switchSubDeviceContent()1102     public void switchSubDeviceContent() {
1103 
1104         mCachedDevice.mRssi = RSSI_1;
1105         mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
1106         mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
1107         mSubCachedDevice.mRssi = RSSI_2;
1108         mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
1109         mSubCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
1110         mCachedDevice.setSubDevice(mSubCachedDevice);
1111 
1112         mCachedDevice.switchSubDeviceContent();
1113 
1114         verify(mCachedDevice).release();
1115         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
1116         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
1117         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
1118         assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
1119         verify(mSubCachedDevice).release();
1120         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
1121         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
1122         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
1123         assertThat(mSubCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
1124     }
1125 
1126     @Test
getConnectionSummary_profileConnectedFail_showErrorMessage()1127     public void getConnectionSummary_profileConnectedFail_showErrorMessage() {
1128         final A2dpProfile profile = mock(A2dpProfile.class);
1129         mCachedDevice.onProfileStateChanged(profile, BluetoothProfile.STATE_CONNECTED);
1130         mCachedDevice.setProfileConnectedStatus(BluetoothProfile.A2DP, true);
1131 
1132         when(profile.getConnectionStatus(mDevice)).thenReturn(BluetoothProfile.STATE_CONNECTED);
1133 
1134         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
1135                 mContext.getString(R.string.profile_connect_timeout_subtext));
1136     }
1137 
1138     @Test
onUuidChanged_bluetoothClassIsNull_shouldNotCrash()1139     public void onUuidChanged_bluetoothClassIsNull_shouldNotCrash() {
1140         mShadowBluetoothAdapter.setUuids(PbapServerProfile.PBAB_CLIENT_UUIDS);
1141         when(mDevice.getUuids()).thenReturn(PbapServerProfile.PBAB_CLIENT_UUIDS);
1142         when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
1143         when(mDevice.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_UNKNOWN);
1144         when(mDevice.getBluetoothClass()).thenReturn(null);
1145 
1146         mCachedDevice.onUuidChanged();
1147 
1148         // Should not crash
1149     }
1150 
1151     @Test
getDrawableWithDescription_isAdvancedDevice_returnAdvancedIcon()1152     public void getDrawableWithDescription_isAdvancedDevice_returnAdvancedIcon() {
1153         LruCache lruCache = mock(LruCache.class);
1154         mCachedDevice.mDrawableCache = lruCache;
1155         BitmapDrawable drawable = mock(BitmapDrawable.class);
1156         when(lruCache.get("fake_uri")).thenReturn(drawable);
1157         when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
1158                 .thenReturn("fake_uri".getBytes());
1159         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
1160                 .thenReturn("true".getBytes());
1161 
1162         mCachedDevice.refresh();
1163 
1164         assertThat(mCachedDevice.getDrawableWithDescription().first).isInstanceOf(
1165                 AdaptiveOutlineDrawable.class);
1166     }
1167 
1168     @Test
getDrawableWithDescription_isNotAdvancedDevice_returnBluetoothIcon()1169     public void getDrawableWithDescription_isNotAdvancedDevice_returnBluetoothIcon() {
1170         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
1171                 .thenReturn("false".getBytes());
1172 
1173         mCachedDevice.refresh();
1174 
1175         assertThat(mCachedDevice.getDrawableWithDescription().first).isNotInstanceOf(
1176                 AdaptiveOutlineDrawable.class);
1177     }
1178 
1179     @Test
releaseLruCache_lruCacheShouldBeRelease()1180     public void releaseLruCache_lruCacheShouldBeRelease() {
1181         when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
1182                 .thenReturn("fake_uri".getBytes());
1183         when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
1184                 .thenReturn("true".getBytes());
1185 
1186         mCachedDevice.refresh();
1187         mCachedDevice.releaseLruCache();
1188 
1189         assertThat(mCachedDevice.mDrawableCache.size()).isEqualTo(0);
1190     }
1191 
1192     @Test
switchMemberDeviceContent_switchMainDevice_switchesSuccessful()1193     public void switchMemberDeviceContent_switchMainDevice_switchesSuccessful() {
1194         mCachedDevice.mRssi = RSSI_1;
1195         mCachedDevice.mJustDiscovered = JUSTDISCOVERED_1;
1196         mSubCachedDevice.mRssi = RSSI_2;
1197         mSubCachedDevice.mJustDiscovered = JUSTDISCOVERED_2;
1198         mCachedDevice.addMemberDevice(mSubCachedDevice);
1199 
1200         mCachedDevice.switchMemberDeviceContent(mSubCachedDevice);
1201 
1202         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
1203         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
1204         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
1205         verify(mCachedDevice).fillData();
1206         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
1207         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
1208         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
1209         verify(mSubCachedDevice).fillData();
1210         assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
1211     }
1212 
1213     @Test
isConnectedHearingAidDevice_isConnectedAshaHearingAidDevice_returnTrue()1214     public void isConnectedHearingAidDevice_isConnectedAshaHearingAidDevice_returnTrue() {
1215         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
1216 
1217         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
1218 
1219         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
1220     }
1221 
1222     @Test
isConnectedHearingAidDevice_isConnectedLeAudioHearingAidDevice_returnTrue()1223     public void isConnectedHearingAidDevice_isConnectedLeAudioHearingAidDevice_returnTrue() {
1224         when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
1225         when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
1226 
1227         updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED);
1228         updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED);
1229 
1230         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isTrue();
1231     }
1232 
1233     @Test
isConnectedHearingAidDevice_isNotAnyConnectedHearingAidDevice_returnFalse()1234     public void isConnectedHearingAidDevice_isNotAnyConnectedHearingAidDevice_returnFalse() {
1235         when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
1236         when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
1237         when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
1238 
1239         updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_DISCONNECTED);
1240         updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_DISCONNECTED);
1241         updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED);
1242 
1243         assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse();
1244     }
1245 
getLeftAshaHearingAidInfo()1246     private HearingAidInfo getLeftAshaHearingAidInfo() {
1247         return new HearingAidInfo.Builder()
1248                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT)
1249                 .build();
1250     }
1251 
getRightAshaHearingAidInfo()1252     private HearingAidInfo getRightAshaHearingAidInfo() {
1253         return new HearingAidInfo.Builder()
1254                 .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT)
1255                 .build();
1256     }
1257 
getLeftLeAudioHearingAidInfo()1258     private HearingAidInfo getLeftLeAudioHearingAidInfo() {
1259         return new HearingAidInfo.Builder()
1260                 .setLeAudioLocation(BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT)
1261                 .build();
1262     }
1263 }
1264