1 /*
2  * Copyright (C) 2023 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.systemui.car.statusicon;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.reset;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.when;
30 
31 import android.app.PendingIntent;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.os.UserHandle;
36 import android.testing.AndroidTestingRunner;
37 import android.testing.TestableLooper;
38 import android.widget.ImageView;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.car.qc.QCItem;
43 import com.android.car.ui.FocusParkingView;
44 import com.android.systemui.R;
45 import com.android.systemui.SysuiTestCase;
46 import com.android.systemui.broadcast.BroadcastDispatcher;
47 import com.android.systemui.car.CarDeviceProvisionedController;
48 import com.android.systemui.car.CarServiceProvider;
49 import com.android.systemui.car.CarSystemUiTest;
50 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer;
51 import com.android.systemui.settings.UserTracker;
52 import com.android.systemui.statusbar.policy.ConfigurationController;
53 
54 import org.junit.After;
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.ArgumentCaptor;
59 import org.mockito.Mock;
60 import org.mockito.MockitoAnnotations;
61 
62 @CarSystemUiTest
63 @RunWith(AndroidTestingRunner.class)
64 @TestableLooper.RunWithLooper
65 @SmallTest
66 public class StatusIconPanelViewControllerTest extends SysuiTestCase {
67     private StatusIconPanelViewController mViewController;
68     private ImageView mAnchorView;
69     private UserHandle mUserHandle;
70 
71     @Mock
72     private UserTracker mUserTracker;
73     @Mock
74     private CarServiceProvider mCarServiceProvider;
75     @Mock
76     private BroadcastDispatcher mBroadcastDispatcher;
77     @Mock
78     private ConfigurationController mConfigurationController;
79     @Mock
80     private CarDeviceProvisionedController mDeviceProvisionedController;
81     @Mock
82     private CarSystemBarElementInitializer mCarSystemBarElementInitializer;
83 
84     @Before
setUp()85     public void setUp() {
86         MockitoAnnotations.initMocks(this);
87 
88         mContext = spy(mContext);
89         mUserHandle = UserHandle.of(1000);
90         when(mUserTracker.getUserHandle()).thenReturn(mUserHandle);
91 
92         mAnchorView = spy(new ImageView(mContext));
93         mAnchorView.setImageDrawable(mContext.getDrawable(R.drawable.ic_bluetooth_status_off));
94         mAnchorView.setColorFilter(mContext.getColor(R.color.car_status_icon_color));
95         mViewController = new StatusIconPanelViewController.Builder(mContext, mUserTracker,
96                 mCarServiceProvider, mBroadcastDispatcher, mConfigurationController,
97                 mDeviceProvisionedController, mCarSystemBarElementInitializer).build(mAnchorView,
98                 R.layout.qc_display_panel, R.dimen.car_status_icon_panel_default_width);
99         spyOn(mViewController);
100         reset(mAnchorView);
101         mViewController.init();
102     }
103 
104     @After
tearDown()105     public void tearDown() {
106         // The view controller must be detached before we move on since it creates a real
107         // PopupWindow instance. Otherwise, the window will continue to be present and cause
108         // instability in future tests.
109         mViewController.onViewDetached();
110     }
111 
112     @Test
onViewAttached_registersListeners()113     public void onViewAttached_registersListeners() {
114         mViewController.onViewAttached();
115         verify(mBroadcastDispatcher).registerReceiver(any(), any(), any(), any());
116         verify(mUserTracker).addCallback(any(), any());
117         verify(mConfigurationController).addCallback(any());
118         verify(mCarServiceProvider).addListener(any());
119     }
120 
121     @Test
onViewDetached_unregistersListeners()122     public void onViewDetached_unregistersListeners() {
123         mViewController.onViewDetached();
124         verify(mCarServiceProvider).removeListener(any());
125         verify(mConfigurationController).removeCallback(any());
126         verify(mUserTracker).removeCallback(any());
127         verify(mBroadcastDispatcher).unregisterReceiver(any());
128         assertThat(mViewController.getPanel()).isNull();
129     }
130 
131     @Test
onPanelAnchorViewClicked_panelShowing()132     public void onPanelAnchorViewClicked_panelShowing() {
133         clickAnchorView();
134         waitForIdleSync();
135 
136         assertThat(mViewController.getPanel().isShowing()).isTrue();
137     }
138 
139     @Test
onPanelAnchorViewClicked_panelShowing_panelDismissed()140     public void onPanelAnchorViewClicked_panelShowing_panelDismissed() {
141         clickAnchorView();
142 
143         clickAnchorView();
144         waitForIdleSync();
145 
146         assertThat(mViewController.getPanel().isShowing()).isFalse();
147     }
148 
149     @Test
onPanelAnchorViewClicked_sendsIntentToDismissSystemDialogsWithIdentifier()150     public void onPanelAnchorViewClicked_sendsIntentToDismissSystemDialogsWithIdentifier() {
151         ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
152 
153         clickAnchorView();
154         waitForIdleSync();
155 
156         verify(mContext).sendBroadcastAsUser(argumentCaptor.capture(), eq(mUserHandle));
157         assertThat(argumentCaptor.getValue().getAction()).isEqualTo(
158                 Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
159         assertThat(argumentCaptor.getValue().getIdentifier()).isEqualTo(
160                 mViewController.getIdentifier());
161     }
162 
163     @Test
onDismissSystemDialogReceived_fromSelf_panelOpen_doesNotDismissPanel()164     public void onDismissSystemDialogReceived_fromSelf_panelOpen_doesNotDismissPanel() {
165         Intent intent = new Intent();
166         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
167         intent.setIdentifier(mViewController.getIdentifier());
168         clickAnchorView();
169         waitForIdleSync();
170 
171         mViewController.getBroadcastReceiver().onReceive(mContext, intent);
172 
173         assertThat(mViewController.getPanel().isShowing()).isTrue();
174     }
175 
176     @Test
onDismissSystemDialogReceived_notFromSelf_panelOpen_dismissesPanel()177     public void onDismissSystemDialogReceived_notFromSelf_panelOpen_dismissesPanel() {
178         Intent intent = new Intent();
179         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
180         clickAnchorView();
181         waitForIdleSync();
182 
183         mViewController.getBroadcastReceiver().onReceive(mContext, intent);
184 
185         assertThat(mViewController.getPanel().isShowing()).isFalse();
186     }
187 
188     @Test
onLayoutDirectionChanged_recreatePanel()189     public void onLayoutDirectionChanged_recreatePanel() {
190         mViewController.getConfigurationListener()
191                 .onLayoutDirectionChanged(/* isLayoutRtl= */ true);
192 
193         assertThat(mViewController.getPanel()).isNotNull();
194     }
195 
196     @Test
onUserChanged_unregisterRegisterReceiver()197     public void onUserChanged_unregisterRegisterReceiver() {
198         int newUser = 999;
199         Context userContext = mock(Context.class);
200         reset(mBroadcastDispatcher);
201 
202         mViewController.getUserTrackerCallback()
203                 .onUserChanged(newUser, userContext);
204 
205         verify(mBroadcastDispatcher).unregisterReceiver(
206                 eq(mViewController.getBroadcastReceiver()));
207         verify(mBroadcastDispatcher).registerReceiver(
208                 eq(mViewController.getBroadcastReceiver()),
209                 any(IntentFilter.class), eq(null), eq(mUserHandle));
210     }
211 
212     @Test
onGlobalFocusChanged_panelShowing_panelDismissed()213     public void onGlobalFocusChanged_panelShowing_panelDismissed() {
214         FocusParkingView newFocusView = mock(FocusParkingView.class);
215         clickAnchorView();
216         waitForIdleSync();
217 
218         mViewController.getFocusChangeListener()
219                 .onGlobalFocusChanged(mAnchorView, newFocusView);
220 
221         assertThat(mViewController.getPanel().isShowing()).isFalse();
222     }
223 
224     @Test
onQCAction_pendingIntentAction_panelDismissed()225     public void onQCAction_pendingIntentAction_panelDismissed() {
226         QCItem qcItem = mock(QCItem.class);
227         PendingIntent action = mock(PendingIntent.class);
228         when(action.isActivity()).thenReturn(true);
229         clickAnchorView();
230         waitForIdleSync();
231 
232         mViewController.getQCActionListener().onQCAction(qcItem, action);
233 
234         assertThat(mViewController.getPanel().isShowing()).isFalse();
235     }
236 
237     @Test
onQCAction_actionHandler_panelDismissed()238     public void onQCAction_actionHandler_panelDismissed() {
239         QCItem qcItem = mock(QCItem.class);
240         QCItem.ActionHandler action = mock(QCItem.ActionHandler.class);
241         when(action.isActivity()).thenReturn(true);
242         clickAnchorView();
243         waitForIdleSync();
244 
245         mViewController.getQCActionListener().onQCAction(qcItem, action);
246 
247         assertThat(mViewController.getPanel().isShowing()).isFalse();
248     }
249 
clickAnchorView()250     private void clickAnchorView() {
251         mViewController.getOnClickListener().onClick(mAnchorView);
252     }
253 }
254