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 junit.framework.Assert.assertEquals;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.anyLong;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.animation.AnimatorTestRule;
33 import android.platform.test.annotations.DisableFlags;
34 import android.testing.AndroidTestingRunner;
35 import android.testing.TestableLooper;
36 import android.view.View;
37 
38 import androidx.test.filters.SmallTest;
39 
40 import com.android.app.animation.Interpolators;
41 import com.android.systemui.Flags;
42 import com.android.systemui.animation.ViewHierarchyAnimator;
43 import com.android.systemui.plugins.clocks.ClockConfig;
44 import com.android.systemui.plugins.clocks.ClockController;
45 import com.android.systemui.res.R;
46 import com.android.systemui.statusbar.notification.AnimatableProperty;
47 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
48 
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.ArgumentCaptor;
53 
54 import java.lang.reflect.Field;
55 
56 @SmallTest
57 @TestableLooper.RunWithLooper(setAsMainLooper = true)
58 @RunWith(AndroidTestingRunner.class)
59 public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
60 
61     @Rule
62     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
63 
64     @Test
dozeTimeTick_updatesSlice()65     public void dozeTimeTick_updatesSlice() {
66         mController.dozeTimeTick();
67         verify(mKeyguardSliceViewController).refresh();
68     }
69 
70     @Test
dozeTimeTick_updatesClock()71     public void dozeTimeTick_updatesClock() {
72         mController.dozeTimeTick();
73         verify(mKeyguardClockSwitchController).refresh();
74     }
75 
76     @Test
setTranslationYExcludingMedia_forwardsCallToView()77     public void setTranslationYExcludingMedia_forwardsCallToView() {
78         float translationY = 123f;
79 
80         mController.setTranslationY(translationY, /* excludeMedia= */true);
81 
82         verify(mKeyguardStatusView).setChildrenTranslationY(translationY, /* excludeMedia= */true);
83     }
84 
85     @Test
86     @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
onLocaleListChangedNotifiesClockSwitchController()87     public void onLocaleListChangedNotifiesClockSwitchController() {
88         ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
89                 ArgumentCaptor.forClass(ConfigurationListener.class);
90 
91         mController.onViewAttached();
92         verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
93 
94         configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
95         verify(mKeyguardClockSwitchController).onLocaleListChanged();
96     }
97 
98     @Test
updatePosition_primaryClockAnimation()99     public void updatePosition_primaryClockAnimation() {
100         ClockController mockClock = mock(ClockController.class);
101         when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
102         when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true, false));
103 
104         mController.updatePosition(10, 15, 20f, true);
105 
106         verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
107         verify(mKeyguardClockSwitchController).updatePosition(
108                 10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
109         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true);
110         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true);
111     }
112 
113     @Test
updatePosition_alternateClockAnimation()114     public void updatePosition_alternateClockAnimation() {
115         ClockController mockClock = mock(ClockController.class);
116         when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
117         when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true, false));
118 
119         mController.updatePosition(10, 15, 20f, true);
120 
121         verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
122         verify(mKeyguardClockSwitchController).updatePosition(
123                 10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
124         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
125         verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
126     }
127 
128     @Test
splitShadeEnabledPassedToClockSwitchController()129     public void splitShadeEnabledPassedToClockSwitchController() {
130         mController.setSplitShadeEnabled(true);
131         verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true);
132         verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false);
133     }
134 
135     @Test
splitShadeDisabledPassedToClockSwitchController()136     public void splitShadeDisabledPassedToClockSwitchController() {
137         mController.setSplitShadeEnabled(false);
138         verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
139         verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
140     }
141 
142     @Test
correctlyDump()143     public void correctlyDump() {
144         mController.onInit();
145         verify(mDumpManager).registerDumpable(eq(mController.getInstanceName()), eq(mController));
146         mController.onDestroy();
147         verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
148     }
149 
150     @Test
onInit_addsOnLayoutChangeListenerToClockSwitch()151     public void onInit_addsOnLayoutChangeListenerToClockSwitch() {
152         when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
153                 mMediaHostContainer);
154 
155         mController.onInit();
156 
157         ArgumentCaptor<View.OnLayoutChangeListener> captor =
158                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
159         verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
160     }
161 
162     @Test
clockSwitchHeightChanged_animatesMediaHostContainer()163     public void clockSwitchHeightChanged_animatesMediaHostContainer() {
164         when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
165                 mMediaHostContainer);
166 
167         mController.onInit();
168 
169         ArgumentCaptor<View.OnLayoutChangeListener> captor =
170                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
171         verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
172 
173         // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
174         // Below here is the actual test.
175 
176         ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
177         ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
178         setCompanion(spiedAnimator);
179 
180         View.OnLayoutChangeListener listener = captor.getValue();
181 
182         mController.setSplitShadeEnabled(true);
183         when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
184         when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
185         when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
186         when(mMediaHostContainer.getHeight()).thenReturn(200);
187 
188         when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
189         listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
190                 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
191                 0, /* oldBottom = */ 200);
192         verify(spiedAnimator).animateNextUpdate(mMediaHostContainer,
193                 Interpolators.STANDARD, /* duration= */ 500L, /* animateChildren= */ false);
194 
195         // Resets ViewHierarchyAnimator.Companion to its original value
196         setCompanion(animator);
197     }
198 
199     @Test
clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer()200     public void clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer() {
201         when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
202                 mMediaHostContainer);
203 
204         mController.onInit();
205 
206         ArgumentCaptor<View.OnLayoutChangeListener> captor =
207                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
208         verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture());
209 
210         // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
211         // Below here is the actual test.
212 
213         ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
214         ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
215         setCompanion(spiedAnimator);
216 
217         View.OnLayoutChangeListener listener = captor.getValue();
218 
219         mController.setSplitShadeEnabled(true);
220         when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false);
221         when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
222         when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
223         when(mMediaHostContainer.getHeight()).thenReturn(200);
224 
225         when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
226         listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
227                 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
228                 0, /* oldBottom = */ 200);
229         verify(spiedAnimator, never()).animateNextUpdate(any(), any(), anyLong(), anyBoolean());
230 
231         // Resets ViewHierarchyAnimator.Companion to its original value
232         setCompanion(animator);
233     }
234 
setCompanion(ViewHierarchyAnimator.Companion companion)235     private void setCompanion(ViewHierarchyAnimator.Companion companion) {
236         try {
237             Field field = ViewHierarchyAnimator.class.getDeclaredField("Companion");
238             field.setAccessible(true);
239             field.set(null, companion);
240         } catch (Exception e) {
241             throw new RuntimeException(e);
242         }
243     }
244 
245     @Test
246     @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
statusAreaHeightChange_animatesHeightOutputChange()247     public void statusAreaHeightChange_animatesHeightOutputChange() {
248         // Init & Capture Layout Listener
249         mController.onInit();
250         mController.onViewAttached();
251 
252         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
253         ArgumentCaptor<View.OnLayoutChangeListener> captor =
254                 ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
255         verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture());
256         View.OnLayoutChangeListener listener = captor.getValue();
257 
258         // Setup and validate initial height
259         when(mKeyguardStatusView.getHeight()).thenReturn(200);
260         when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10);
261         assertEquals(190, mController.getLockscreenHeight());
262 
263         // Trigger Change and validate value unchanged immediately
264         when(mKeyguardStatusAreaView.getHeight()).thenReturn(100);
265         when(mKeyguardStatusView.getHeight()).thenReturn(300);      // Include child height
266         listener.onLayoutChange(mKeyguardStatusAreaView,
267             /* new layout */ 100, 300, 200, 400,
268             /* old layout */ 100, 300, 200, 300);
269         assertEquals(190, mController.getLockscreenHeight());
270 
271         // Complete animation, validate height increased
272         mAnimatorTestRule.advanceTimeBy(200);
273         assertEquals(290, mController.getLockscreenHeight());
274     }
275 }
276