1 /*
2  * Copyright (C) 2020 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.keyguard;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.anyBoolean;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.reset;
26 import static org.mockito.Mockito.times;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.content.res.Resources;
31 import android.database.ContentObserver;
32 import android.net.Uri;
33 import android.provider.Settings;
34 import android.testing.AndroidTestingRunner;
35 import android.view.View;
36 import android.widget.FrameLayout;
37 import android.widget.LinearLayout;
38 import android.widget.RelativeLayout;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.internal.colorextraction.ColorExtractor;
43 import com.android.keyguard.clock.ClockManager;
44 import com.android.systemui.R;
45 import com.android.systemui.SysuiTestCase;
46 import com.android.systemui.broadcast.BroadcastDispatcher;
47 import com.android.systemui.colorextraction.SysuiColorExtractor;
48 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
49 import com.android.systemui.plugins.ClockPlugin;
50 import com.android.systemui.plugins.statusbar.StatusBarStateController;
51 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
52 import com.android.systemui.statusbar.StatusBarState;
53 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
54 import com.android.systemui.statusbar.phone.KeyguardBypassController;
55 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
56 import com.android.systemui.statusbar.phone.NotificationIconContainer;
57 import com.android.systemui.statusbar.policy.BatteryController;
58 import com.android.systemui.util.concurrency.FakeExecutor;
59 import com.android.systemui.util.settings.SecureSettings;
60 import com.android.systemui.util.time.FakeSystemClock;
61 
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.ArgumentCaptor;
66 import org.mockito.Mock;
67 import org.mockito.MockitoAnnotations;
68 import org.mockito.verification.VerificationMode;
69 
70 @SmallTest
71 @RunWith(AndroidTestingRunner.class)
72 public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
73 
74     @Mock
75     private KeyguardClockSwitch mView;
76     @Mock
77     private StatusBarStateController mStatusBarStateController;
78     @Mock
79     private SysuiColorExtractor mColorExtractor;
80     @Mock
81     private ClockManager mClockManager;
82     @Mock
83     KeyguardSliceViewController mKeyguardSliceViewController;
84     @Mock
85     NotificationIconAreaController mNotificationIconAreaController;
86     @Mock
87     BroadcastDispatcher mBroadcastDispatcher;
88     @Mock
89     BatteryController mBatteryController;
90     @Mock
91     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
92     @Mock
93     KeyguardBypassController mBypassController;
94     @Mock
95     LockscreenSmartspaceController mSmartspaceController;
96 
97     @Mock
98     Resources mResources;
99     KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
100     @Mock
101     SmartspaceTransitionController mSmartSpaceTransitionController;
102     @Mock
103     private ClockPlugin mClockPlugin;
104     @Mock
105     ColorExtractor.GradientColors mGradientColors;
106 
107     @Mock
108     private NotificationIconContainer mNotificationIcons;
109     @Mock
110     private AnimatableClockView mClockView;
111     @Mock
112     private AnimatableClockView mLargeClockView;
113     @Mock
114     private FrameLayout mLargeClockFrame;
115     @Mock
116     private SecureSettings mSecureSettings;
117 
118     private final View mFakeSmartspaceView = new View(mContext);
119 
120     private KeyguardClockSwitchController mController;
121     private View mSliceView;
122     private FakeExecutor mExecutor;
123 
124     @Before
setup()125     public void setup() {
126         MockitoAnnotations.initMocks(this);
127 
128         when(mView.findViewById(R.id.left_aligned_notification_icon_container))
129                 .thenReturn(mNotificationIcons);
130         when(mNotificationIcons.getLayoutParams()).thenReturn(
131                 mock(RelativeLayout.LayoutParams.class));
132         when(mView.getContext()).thenReturn(getContext());
133         when(mView.getResources()).thenReturn(mResources);
134 
135         when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
136         when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
137         when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
138         when(mClockView.getContext()).thenReturn(getContext());
139         when(mLargeClockView.getContext()).thenReturn(getContext());
140 
141         when(mView.isAttachedToWindow()).thenReturn(true);
142         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
143         mExecutor = new FakeExecutor(new FakeSystemClock());
144         mController = new KeyguardClockSwitchController(
145                 mView,
146                 mStatusBarStateController,
147                 mColorExtractor,
148                 mClockManager,
149                 mKeyguardSliceViewController,
150                 mNotificationIconAreaController,
151                 mBroadcastDispatcher,
152                 mBatteryController,
153                 mKeyguardUpdateMonitor,
154                 mBypassController,
155                 mSmartspaceController,
156                 mKeyguardUnlockAnimationController,
157                 mSmartSpaceTransitionController,
158                 mSecureSettings,
159                 mExecutor,
160                 mResources
161         );
162 
163         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
164         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
165 
166         mSliceView = new View(getContext());
167         when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
168         when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
169                 new LinearLayout(getContext()));
170     }
171 
172     @Test
testInit_viewAlreadyAttached()173     public void testInit_viewAlreadyAttached() {
174         mController.init();
175 
176         verifyAttachment(times(1));
177     }
178 
179     @Test
testInit_viewNotYetAttached()180     public void testInit_viewNotYetAttached() {
181         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
182                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
183 
184         when(mView.isAttachedToWindow()).thenReturn(false);
185         mController.init();
186         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
187 
188         verifyAttachment(never());
189 
190         listenerArgumentCaptor.getValue().onViewAttachedToWindow(mView);
191 
192         verifyAttachment(times(1));
193     }
194 
195     @Test
testInitSubControllers()196     public void testInitSubControllers() {
197         mController.init();
198         verify(mKeyguardSliceViewController).init();
199     }
200 
201     @Test
testInit_viewDetached()202     public void testInit_viewDetached() {
203         ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
204                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
205         mController.init();
206         verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
207 
208         verifyAttachment(times(1));
209 
210         listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
211         verify(mColorExtractor).removeOnColorsChangedListener(
212                 any(ColorExtractor.OnColorsChangedListener.class));
213     }
214 
215     @Test
testPluginPassesStatusBarState()216     public void testPluginPassesStatusBarState() {
217         ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
218                 ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
219 
220         mController.init();
221         verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
222 
223         listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
224         verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
225     }
226 
227     @Test
testSmartspaceEnabledRemovesKeyguardStatusArea()228     public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
229         when(mSmartspaceController.isEnabled()).thenReturn(true);
230         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
231         mController.init();
232 
233         assertEquals(View.GONE, mSliceView.getVisibility());
234     }
235 
236     @Test
testSmartspaceDisabledShowsKeyguardStatusArea()237     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
238         when(mSmartspaceController.isEnabled()).thenReturn(false);
239         mController.init();
240 
241         assertEquals(View.VISIBLE, mSliceView.getVisibility());
242     }
243 
244     @Test
testRefresh()245     public void testRefresh() {
246         mController.refresh();
247 
248         verify(mSmartspaceController).requestSmartspaceUpdate();
249     }
250 
251     @Test
testChangeToDoubleLineClockSetsSmallClock()252     public void testChangeToDoubleLineClockSetsSmallClock() {
253         when(mSecureSettings.getInt(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1))
254                 .thenReturn(0);
255         ArgumentCaptor<ContentObserver> observerCaptor =
256                 ArgumentCaptor.forClass(ContentObserver.class);
257         mController.init();
258         verify(mSecureSettings).registerContentObserver(any(Uri.class),
259                 anyBoolean(), observerCaptor.capture());
260         ContentObserver observer = observerCaptor.getValue();
261         mExecutor.runAllReady();
262 
263         // When a settings change has occurred to the small clock, make sure the view is adjusted
264         reset(mView);
265         observer.onChange(true);
266         mExecutor.runAllReady();
267         verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
268     }
269 
verifyAttachment(VerificationMode times)270     private void verifyAttachment(VerificationMode times) {
271         verify(mClockManager, times).addOnClockChangedListener(
272                 any(ClockManager.ClockChangedListener.class));
273         verify(mColorExtractor, times).addOnColorsChangedListener(
274                 any(ColorExtractor.OnColorsChangedListener.class));
275         verify(mView, times).updateColors(mGradientColors);
276     }
277 }
278