blob: 205312ca508ffe907c8079ef977d993a8e704a99 [file] [log] [blame]
Lucas Dupin9e3fa102017-11-08 17:16:55 -08001/*
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
17package com.android.systemui.statusbar.phone;
18
Selim Cinekf97d7f72019-10-25 17:37:00 -070019import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
20import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
21import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
Lucas Dupin82aa1632017-12-13 00:13:57 -080022
23import static org.mockito.ArgumentMatchers.any;
24import static org.mockito.ArgumentMatchers.anyInt;
25import static org.mockito.ArgumentMatchers.anyLong;
Lucas Dupinee4c9b72019-02-18 17:04:58 -080026import static org.mockito.ArgumentMatchers.anyString;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080027import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.never;
Lucas Dupin82aa1632017-12-13 00:13:57 -080029import static org.mockito.Mockito.reset;
Lucas Dupin067136c2018-03-27 18:03:25 -070030import static org.mockito.Mockito.spy;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080031import static org.mockito.Mockito.verify;
Lucas Dupineea53b32017-12-18 13:47:14 -080032import static org.mockito.Mockito.verifyZeroInteractions;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080033import static org.mockito.Mockito.when;
34
35import android.animation.Animator;
Lucas Dupin80a3fcc2018-02-07 10:49:55 -080036import android.animation.ValueAnimator;
Lucas Dupin82aa1632017-12-13 00:13:57 -080037import android.app.AlarmManager;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080038import android.graphics.Color;
39import android.os.Handler;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080040import android.testing.AndroidTestingRunner;
41import android.testing.TestableLooper;
42import android.view.View;
43
Brett Chabot84151d92019-02-27 15:37:59 -080044import androidx.test.filters.SmallTest;
45
shawnlin317db372018-04-09 19:49:48 +080046import com.android.internal.colorextraction.ColorExtractor.GradientColors;
Yohei Yukawa795f0102018-04-13 14:55:30 -070047import com.android.internal.util.function.TriConsumer;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080048import com.android.systemui.SysuiTestCase;
49import com.android.systemui.statusbar.ScrimView;
Selim Cinek72cd9a72019-08-09 17:19:57 -070050import com.android.systemui.statusbar.policy.KeyguardMonitor;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080051import com.android.systemui.util.wakelock.WakeLock;
52import com.android.systemui.utils.os.FakeHandler;
53
Lucas Dupin4dbe5182019-03-07 18:20:11 -080054import org.junit.After;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080055import org.junit.Assert;
56import org.junit.Before;
57import org.junit.Test;
58import org.junit.runner.RunWith;
59
Lucas Dupin1c327432019-01-03 13:37:53 -080060import java.util.Collections;
Lucas Dupin78949b82018-04-03 18:54:39 -070061import java.util.HashSet;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080062import java.util.function.Consumer;
63
64@RunWith(AndroidTestingRunner.class)
65@TestableLooper.RunWithLooper
66@SmallTest
67public class ScrimControllerTest extends SysuiTestCase {
68
69 private SynchronousScrimController mScrimController;
70 private ScrimView mScrimBehind;
71 private ScrimView mScrimInFront;
Yohei Yukawa795f0102018-04-13 14:55:30 -070072 private ScrimState mScrimState;
shawnlin317db372018-04-09 19:49:48 +080073 private float mScrimBehindAlpha;
74 private GradientColors mScrimInFrontColor;
Lucas Dupin82aa1632017-12-13 00:13:57 -080075 private int mScrimVisibility;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080076 private DozeParameters mDozeParamenters;
77 private WakeLock mWakeLock;
78 private boolean mAlwaysOnEnabled;
Lucas Dupin82aa1632017-12-13 00:13:57 -080079 private AlarmManager mAlarmManager;
Lucas Dupin54fbfb32019-03-05 18:08:13 -080080 private TestableLooper mLooper;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080081
Yohei Yukawa795f0102018-04-13 14:55:30 -070082
Lucas Dupin9e3fa102017-11-08 17:16:55 -080083 @Before
84 public void setup() {
Lucas Dupin067136c2018-03-27 18:03:25 -070085 mScrimBehind = spy(new ScrimView(getContext()));
Lucas Dupin9e3fa102017-11-08 17:16:55 -080086 mScrimInFront = new ScrimView(getContext());
Lucas Dupin9e3fa102017-11-08 17:16:55 -080087 mWakeLock = mock(WakeLock.class);
Lucas Dupin82aa1632017-12-13 00:13:57 -080088 mAlarmManager = mock(AlarmManager.class);
Lucas Dupin9e3fa102017-11-08 17:16:55 -080089 mAlwaysOnEnabled = true;
Lucas Dupin9e3fa102017-11-08 17:16:55 -080090 mDozeParamenters = mock(DozeParameters.class);
Lucas Dupin54fbfb32019-03-05 18:08:13 -080091 mLooper = TestableLooper.get(this);
Lucas Dupin9e3fa102017-11-08 17:16:55 -080092 when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
Lucas Dupin43d0d732017-11-16 11:23:49 -080093 when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
shawnlin317db372018-04-09 19:49:48 +080094 mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
Yohei Yukawa795f0102018-04-13 14:55:30 -070095 (scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
96 mScrimState = scrimState;
97 mScrimBehindAlpha = scrimBehindAlpha;
98 mScrimInFrontColor = scrimInFrontColor;
99 },
Selim Cinek72cd9a72019-08-09 17:19:57 -0700100 visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager,
101 mock(KeyguardMonitor.class));
Lucas Dupinf8463ee2018-06-11 16:18:15 -0700102 mScrimController.setHasBackdrop(false);
TYM Tsaia71c8922019-01-07 15:57:53 +0800103 mScrimController.setWallpaperSupportsAmbientMode(false);
Lucas Dupin4dbe5182019-03-07 18:20:11 -0800104 mScrimController.transitionTo(ScrimState.KEYGUARD);
105 mScrimController.finishAnimationsImmediately();
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800106 }
107
Lucas Dupin4dbe5182019-03-07 18:20:11 -0800108 @After
109 public void tearDown() {
110 mScrimController.finishAnimationsImmediately();
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800111 }
112
113 @Test
114 public void transitionToKeyguard() {
115 mScrimController.transitionTo(ScrimState.KEYGUARD);
116 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700117 // Front scrim should be transparent
118 // Back scrim should be visible without tint
119 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
120 assertScrimTint(mScrimBehind, true /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800121 }
122
123 @Test
Lucas Dupin7517b5d2017-08-22 12:51:25 -0700124 public void transitionToAod_withRegularWallpaper() {
125 mScrimController.transitionTo(ScrimState.AOD);
126 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700127 // Front scrim should be transparent
128 // Back scrim should be visible with tint
129 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
130 assertScrimTint(mScrimBehind, true /* tinted */);
131 assertScrimTint(mScrimInFront, true /* tinted */);
Lucas Dupin7517b5d2017-08-22 12:51:25 -0700132 }
133
134 @Test
135 public void transitionToAod_withAodWallpaper() {
136 mScrimController.setWallpaperSupportsAmbientMode(true);
137 mScrimController.transitionTo(ScrimState.AOD);
138 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700139 // Front scrim should be transparent
140 // Back scrim should be transparent
141 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
Lucas Dupin82aa1632017-12-13 00:13:57 -0800142
Lucas Dupin00be88f2019-01-03 17:50:52 -0800143 // Pulsing notification should conserve AOD wallpaper.
Lucas Dupin82aa1632017-12-13 00:13:57 -0800144 mScrimController.transitionTo(ScrimState.PULSING);
145 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700146 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
Lucas Dupin7517b5d2017-08-22 12:51:25 -0700147 }
148
149 @Test
150 public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
Lucas Dupinf8463ee2018-06-11 16:18:15 -0700151 mScrimController.setHasBackdrop(true);
Lucas Dupin7517b5d2017-08-22 12:51:25 -0700152 mScrimController.setWallpaperSupportsAmbientMode(true);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800153 mScrimController.transitionTo(ScrimState.AOD);
154 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700155 // Front scrim should be transparent
156 // Back scrim should be visible with tint
157 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
158 assertScrimTint(mScrimBehind, true /* tinted */);
159 assertScrimTint(mScrimInFront, true /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800160 }
161
162 @Test
Lucas Dupin9bee5822018-07-09 14:32:53 -0700163 public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
164 mScrimController.setWallpaperSupportsAmbientMode(true);
165 mScrimController.transitionTo(ScrimState.AOD);
166 mScrimController.finishAnimationsImmediately();
167 mScrimController.setHasBackdrop(true);
168 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700169 // Front scrim should be transparent
170 // Back scrim should be visible with tint
171 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
172 assertScrimTint(mScrimBehind, true /* tinted */);
173 assertScrimTint(mScrimInFront, true /* tinted */);
Lucas Dupin9bee5822018-07-09 14:32:53 -0700174 }
175
176 @Test
Michael Wrightcae88752018-04-16 23:13:54 +0100177 public void transitionToAod_withFrontAlphaUpdates() {
178 // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
179 mScrimController.transitionTo(ScrimState.KEYGUARD);
180 mScrimController.setAodFrontScrimAlpha(0.5f);
181 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700182 // Front scrim should be transparent
183 // Back scrim should be visible without tint
184 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
Michael Wrightcae88752018-04-16 23:13:54 +0100185
186 // ... but that it does take effect once we enter the AOD state.
187 mScrimController.transitionTo(ScrimState.AOD);
188 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700189 // Front scrim should be semi-transparent
190 // Back scrim should be visible
191 assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
Michael Wrightcae88752018-04-16 23:13:54 +0100192
193 // ... and that if we set it while we're in AOD, it does take immediate effect.
194 mScrimController.setAodFrontScrimAlpha(1f);
Selim Cinekf97d7f72019-10-25 17:37:00 -0700195 assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
Michael Wrightcae88752018-04-16 23:13:54 +0100196
197 // ... and make sure we recall the previous front scrim alpha even if we transition away
198 // for a bit.
199 mScrimController.transitionTo(ScrimState.UNLOCKED);
200 mScrimController.transitionTo(ScrimState.AOD);
201 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700202 assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
Michael Wrightcae88752018-04-16 23:13:54 +0100203
Lucas Dupin69bda602018-05-18 17:24:52 -0700204 // ... and alpha updates should be completely ignored if always_on is off.
205 // Passing it forward would mess up the wake-up transition.
206 mAlwaysOnEnabled = false;
207 mScrimController.transitionTo(ScrimState.UNLOCKED);
208 mScrimController.transitionTo(ScrimState.AOD);
209 mScrimController.finishAnimationsImmediately();
210 mScrimController.setAodFrontScrimAlpha(0.3f);
211 Assert.assertEquals(ScrimState.AOD.getFrontAlpha(), mScrimInFront.getViewAlpha(), 0.001f);
212 Assert.assertNotEquals(0.3f, mScrimInFront.getViewAlpha(), 0.001f);
213
Michael Wrightcae88752018-04-16 23:13:54 +0100214 // Reset value since enums are static.
215 mScrimController.setAodFrontScrimAlpha(0f);
216 }
217
218 @Test
Chris.CC Leed424e202019-09-16 12:17:19 +0800219 public void transitionToPulsing_withFrontAlphaUpdates() {
Lucas Dupin82aa1632017-12-13 00:13:57 -0800220 // Pre-condition
221 // Need to go to AoD first because PULSING doesn't change
222 // the back scrim opacity - otherwise it would hide AoD wallpapers.
223 mScrimController.setWallpaperSupportsAmbientMode(false);
224 mScrimController.transitionTo(ScrimState.AOD);
225 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700226 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
Lucas Dupin82aa1632017-12-13 00:13:57 -0800227
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800228 mScrimController.transitionTo(ScrimState.PULSING);
229 mScrimController.finishAnimationsImmediately();
Lucas Dupina7eacf92019-07-24 12:40:34 -0700230 // Front scrim should be transparent, but tinted
Lucas Dupinde64ee02018-12-21 14:45:12 -0800231 // Back scrim should be semi-transparent so the user can see the wallpaper
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800232 // Pulse callback should have been invoked
Selim Cinekf97d7f72019-10-25 17:37:00 -0700233 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
234 assertScrimTint(mScrimBehind, true /* tinted */);
Lucas Dupin5f00fa52019-03-27 22:46:53 -0700235
Chris.CC Leed424e202019-09-16 12:17:19 +0800236 // ... and when ambient goes dark, front scrim should be semi-transparent
237 mScrimController.setAodFrontScrimAlpha(0.5f);
238 mScrimController.finishAnimationsImmediately();
239 // Front scrim should be semi-transparent
Selim Cinekf97d7f72019-10-25 17:37:00 -0700240 assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT /* front */,
241 VISIBILITY_FULLY_OPAQUE /* back */);
Chris.CC Leed424e202019-09-16 12:17:19 +0800242
Lucas Dupin5f00fa52019-03-27 22:46:53 -0700243 mScrimController.setWakeLockScreenSensorActive(true);
244 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700245 assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT /* front */,
246 VISIBILITY_SEMI_TRANSPARENT /* back */);
Chris.CC Leed424e202019-09-16 12:17:19 +0800247
248 // Reset value since enums are static.
249 mScrimController.setAodFrontScrimAlpha(0f);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800250 }
251
252 @Test
Lucas Dupinbc9aac12018-03-04 20:18:15 -0800253 public void transitionToKeyguardBouncer() {
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800254 mScrimController.transitionTo(ScrimState.BOUNCER);
255 mScrimController.finishAnimationsImmediately();
256 // Front scrim should be transparent
257 // Back scrim should be visible without tint
Selim Cinekf97d7f72019-10-25 17:37:00 -0700258 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
259 assertScrimTint(mScrimBehind, false /* tinted */);
Lucas Dupinbc9aac12018-03-04 20:18:15 -0800260 }
261
262 @Test
263 public void transitionToBouncer() {
Lucas Dupin05726cd2018-03-13 14:00:24 -0700264 mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
Lucas Dupinbc9aac12018-03-04 20:18:15 -0800265 mScrimController.finishAnimationsImmediately();
266 // Front scrim should be transparent
267 // Back scrim should be visible without tint
Selim Cinekf97d7f72019-10-25 17:37:00 -0700268 assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
269 assertScrimTint(mScrimBehind, false /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800270 }
271
272 @Test
273 public void transitionToUnlocked() {
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800274 mScrimController.setPanelExpansion(0f);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800275 mScrimController.transitionTo(ScrimState.UNLOCKED);
276 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700277 // Front scrim should be transparent
278 // Back scrim should be transparent
279 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
280 assertScrimTint(mScrimBehind, false /* tinted */);
281 assertScrimTint(mScrimInFront, false /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800282
283 // Back scrim should be visible after start dragging
284 mScrimController.setPanelExpansion(0.5f);
Selim Cinekf97d7f72019-10-25 17:37:00 -0700285 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800286 }
287
288 @Test
Mady Mellor0c333772018-11-06 18:05:54 -0800289 public void transitionToBubbleExpanded() {
290 mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
291 mScrimController.finishAnimationsImmediately();
292
293 // Front scrim should be transparent
Selim Cinekf97d7f72019-10-25 17:37:00 -0700294 Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
Mady Mellor0c333772018-11-06 18:05:54 -0800295 mScrimInFront.getViewAlpha(), 0.0f);
296 // Back scrim should be visible
297 Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
298 mScrimBehind.getViewAlpha(), 0.0f);
299 }
300
301 @Test
Yohei Yukawa795f0102018-04-13 14:55:30 -0700302 public void scrimStateCallback() {
303 mScrimController.transitionTo(ScrimState.UNLOCKED);
304 mScrimController.finishAnimationsImmediately();
305 Assert.assertEquals(mScrimState, ScrimState.UNLOCKED);
306
307 mScrimController.transitionTo(ScrimState.BOUNCER);
308 mScrimController.finishAnimationsImmediately();
309 Assert.assertEquals(mScrimState, ScrimState.BOUNCER);
310
311 mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED);
312 mScrimController.finishAnimationsImmediately();
313 Assert.assertEquals(mScrimState, ScrimState.BOUNCER_SCRIMMED);
314 }
315
316 @Test
shawnlin317db372018-04-09 19:49:48 +0800317 public void panelExpansion() {
318 mScrimController.setPanelExpansion(0f);
319 mScrimController.setPanelExpansion(0.5f);
320 mScrimController.transitionTo(ScrimState.UNLOCKED);
321 mScrimController.finishAnimationsImmediately();
322
323 reset(mScrimBehind);
324 mScrimController.setPanelExpansion(0f);
325 mScrimController.setPanelExpansion(1.0f);
Lucas Dupin4dbe5182019-03-07 18:20:11 -0800326 mScrimController.finishAnimationsImmediately();
shawnlin317db372018-04-09 19:49:48 +0800327
328 Assert.assertEquals("Scrim alpha should change after setPanelExpansion",
329 mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
330
331 mScrimController.setPanelExpansion(0f);
Lucas Dupin4dbe5182019-03-07 18:20:11 -0800332 mScrimController.finishAnimationsImmediately();
shawnlin317db372018-04-09 19:49:48 +0800333
334 Assert.assertEquals("Scrim alpha should change after setPanelExpansion",
335 mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
336 }
337
338 @Test
Lucas Dupin67f02632018-03-12 11:08:31 -0700339 public void panelExpansionAffectsAlpha() {
340 mScrimController.setPanelExpansion(0f);
341 mScrimController.setPanelExpansion(0.5f);
342 mScrimController.transitionTo(ScrimState.UNLOCKED);
343 mScrimController.finishAnimationsImmediately();
344
345 final float scrimAlpha = mScrimBehind.getViewAlpha();
Lucas Dupin067136c2018-03-27 18:03:25 -0700346 reset(mScrimBehind);
Lucas Dupin67f02632018-03-12 11:08:31 -0700347 mScrimController.setExpansionAffectsAlpha(false);
348 mScrimController.setPanelExpansion(0.8f);
Lucas Dupin067136c2018-03-27 18:03:25 -0700349 verifyZeroInteractions(mScrimBehind);
Lucas Dupin67f02632018-03-12 11:08:31 -0700350 Assert.assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha "
351 + "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
352
353 mScrimController.setExpansionAffectsAlpha(true);
354 mScrimController.setPanelExpansion(0.1f);
Lucas Dupin4dbe5182019-03-07 18:20:11 -0800355 mScrimController.finishAnimationsImmediately();
Lucas Dupin67f02632018-03-12 11:08:31 -0700356 Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha "
357 + "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
358 }
359
360 @Test
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800361 public void transitionToUnlockedFromAod() {
362 // Simulate unlock with fingerprint
363 mScrimController.transitionTo(ScrimState.AOD);
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800364 mScrimController.setPanelExpansion(0f);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800365 mScrimController.finishAnimationsImmediately();
366 mScrimController.transitionTo(ScrimState.UNLOCKED);
Selim Cinekf97d7f72019-10-25 17:37:00 -0700367 // Immediately tinted after the transition starts
368 assertScrimTint(mScrimInFront, true /* tinted */);
369 assertScrimTint(mScrimBehind, true /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800370 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700371 // Front scrim should be transparent
372 // Back scrim should be transparent
373 // Neither scrims should be tinted anymore after the animation.
374 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
375 assertScrimTint(mScrimInFront, false /* tinted */);
376 assertScrimTint(mScrimBehind, false /* tinted */);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800377 }
378
379 @Test
Lucas Dupinea0116e2018-04-05 10:09:29 -0700380 public void scrimBlanksBeforeLeavingAod() {
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800381 // Simulate unlock with fingerprint
382 mScrimController.transitionTo(ScrimState.AOD);
383 mScrimController.finishAnimationsImmediately();
384 mScrimController.transitionTo(ScrimState.UNLOCKED,
385 new ScrimController.Callback() {
386 @Override
387 public void onDisplayBlanked() {
388 // Front scrim should be black in the middle of the transition
389 Assert.assertTrue("Scrim should be visible during transition. Alpha: "
390 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
Selim Cinekf97d7f72019-10-25 17:37:00 -0700391 assertScrimTint(mScrimInFront, true /* tinted */);
Lucas Dupin82aa1632017-12-13 00:13:57 -0800392 Assert.assertSame("Scrim should be visible during transition.",
Selim Cinekf97d7f72019-10-25 17:37:00 -0700393 mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800394 }
395 });
396 mScrimController.finishAnimationsImmediately();
397 }
398
399 @Test
Lucas Dupineb840ea2018-06-01 00:28:58 -0700400 public void scrimBlanksWhenUnlockingFromPulse() {
401 boolean[] blanked = {false};
402 // Simulate unlock with fingerprint
403 mScrimController.transitionTo(ScrimState.PULSING);
404 mScrimController.finishAnimationsImmediately();
405 mScrimController.transitionTo(ScrimState.UNLOCKED,
406 new ScrimController.Callback() {
407 @Override
408 public void onDisplayBlanked() {
409 blanked[0] = true;
410 }
411 });
412 mScrimController.finishAnimationsImmediately();
413 Assert.assertTrue("Scrim should blank when unlocking from pulse.", blanked[0]);
414 }
415
416 @Test
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800417 public void testScrimCallback() {
418 int[] callOrder = {0, 0, 0};
419 int[] currentCall = {0};
420 mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
421 @Override
422 public void onStart() {
423 callOrder[0] = ++currentCall[0];
424 }
425
426 @Override
427 public void onDisplayBlanked() {
428 callOrder[1] = ++currentCall[0];
429 }
430
431 @Override
432 public void onFinished() {
433 callOrder[2] = ++currentCall[0];
434 }
435 });
436 mScrimController.finishAnimationsImmediately();
437 Assert.assertEquals("onStart called in wrong order", 1, callOrder[0]);
438 Assert.assertEquals("onDisplayBlanked called in wrong order", 2, callOrder[1]);
439 Assert.assertEquals("onFinished called in wrong order", 3, callOrder[2]);
440 }
441
442 @Test
443 public void testScrimCallbacksWithoutAmbientDisplay() {
444 mAlwaysOnEnabled = false;
445 testScrimCallback();
446 }
447
448 @Test
449 public void testScrimCallbackCancelled() {
450 boolean[] cancelledCalled = {false};
451 mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
452 @Override
453 public void onCancelled() {
454 cancelledCalled[0] = true;
455 }
456 });
457 mScrimController.transitionTo(ScrimState.PULSING);
458 Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]);
459 }
460
461 @Test
Lucas Dupineea53b32017-12-18 13:47:14 -0800462 public void testHoldsWakeLock_whenAOD() {
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800463 mScrimController.transitionTo(ScrimState.AOD);
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800464 verify(mWakeLock).acquire(anyString());
465 verify(mWakeLock, never()).release(anyString());
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800466 mScrimController.finishAnimationsImmediately();
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800467 verify(mWakeLock).release(anyString());
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800468 }
469
Lucas Dupin19aba8e2017-12-11 12:42:26 -0800470 @Test
Lucas Dupineea53b32017-12-18 13:47:14 -0800471 public void testDoesNotHoldWakeLock_whenUnlocking() {
472 mScrimController.transitionTo(ScrimState.UNLOCKED);
473 mScrimController.finishAnimationsImmediately();
474 verifyZeroInteractions(mWakeLock);
475 }
476
477 @Test
Lucas Dupin19aba8e2017-12-11 12:42:26 -0800478 public void testCallbackInvokedOnSameStateTransition() {
479 mScrimController.transitionTo(ScrimState.UNLOCKED);
480 mScrimController.finishAnimationsImmediately();
481 ScrimController.Callback callback = mock(ScrimController.Callback.class);
482 mScrimController.transitionTo(ScrimState.UNLOCKED, callback);
Lucas Dupin82aa1632017-12-13 00:13:57 -0800483 verify(callback).onFinished();
484 }
485
486 @Test
487 public void testHoldsAodWallpaperAnimationLock() {
488 // Pre-conditions
489 mScrimController.transitionTo(ScrimState.AOD);
490 mScrimController.finishAnimationsImmediately();
491 reset(mWakeLock);
492
493 mScrimController.onHideWallpaperTimeout();
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800494 verify(mWakeLock).acquire(anyString());
495 verify(mWakeLock, never()).release(anyString());
Lucas Dupin82aa1632017-12-13 00:13:57 -0800496 mScrimController.finishAnimationsImmediately();
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800497 verify(mWakeLock).release(anyString());
Lucas Dupin82aa1632017-12-13 00:13:57 -0800498 }
499
500 @Test
TYM Tsaia71c8922019-01-07 15:57:53 +0800501 public void testHoldsPulsingWallpaperAnimationLock() {
502 // Pre-conditions
Lucas Dupin00be88f2019-01-03 17:50:52 -0800503 mScrimController.transitionTo(ScrimState.PULSING);
TYM Tsaia71c8922019-01-07 15:57:53 +0800504 mScrimController.finishAnimationsImmediately();
505 reset(mWakeLock);
506
507 mScrimController.onHideWallpaperTimeout();
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800508 verify(mWakeLock).acquire(anyString());
509 verify(mWakeLock, never()).release(anyString());
TYM Tsaia71c8922019-01-07 15:57:53 +0800510 mScrimController.finishAnimationsImmediately();
Lucas Dupinee4c9b72019-02-18 17:04:58 -0800511 verify(mWakeLock).release(anyString());
TYM Tsaia71c8922019-01-07 15:57:53 +0800512 }
513
514 @Test
Lucas Dupin16cfe452018-02-08 13:14:50 -0800515 public void testWillHideAodWallpaper() {
Lucas Dupin82aa1632017-12-13 00:13:57 -0800516 mScrimController.setWallpaperSupportsAmbientMode(true);
517 mScrimController.transitionTo(ScrimState.AOD);
518 verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
519 mScrimController.transitionTo(ScrimState.KEYGUARD);
520 verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
Lucas Dupin19aba8e2017-12-11 12:42:26 -0800521 }
522
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800523 @Test
Jerry Changbde4c0c2019-06-13 16:58:41 +0800524 public void transitionToPulsing_withTimeoutWallpaperCallback_willHideWallpaper() {
525 mScrimController.setWallpaperSupportsAmbientMode(true);
526
527 mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {
528 @Override
529 public boolean shouldTimeoutWallpaper() {
530 return true;
531 }
532 });
533
534 verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
535 }
536
537 @Test
538 public void transitionToPulsing_withDefaultCallback_wontHideWallpaper() {
539 mScrimController.setWallpaperSupportsAmbientMode(true);
540
541 mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {});
542
543 verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
544 }
545
546 @Test
547 public void transitionToPulsing_withoutCallback_wontHideWallpaper() {
548 mScrimController.setWallpaperSupportsAmbientMode(true);
549
550 mScrimController.transitionTo(ScrimState.PULSING);
551
552 verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any());
553 }
554
555 @Test
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800556 public void testConservesExpansionOpacityAfterTransition() {
557 mScrimController.transitionTo(ScrimState.UNLOCKED);
558 mScrimController.setPanelExpansion(0.5f);
559 mScrimController.finishAnimationsImmediately();
560
561 final float expandedAlpha = mScrimBehind.getViewAlpha();
562
563 mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
564 mScrimController.finishAnimationsImmediately();
565 mScrimController.transitionTo(ScrimState.UNLOCKED);
566 mScrimController.finishAnimationsImmediately();
567
568 Assert.assertEquals("Scrim expansion opacity wasn't conserved when transitioning back",
569 expandedAlpha, mScrimBehind.getViewAlpha(), 0.01f);
570 }
571
572 @Test
573 public void cancelsOldAnimationBeforeBlanking() {
574 mScrimController.transitionTo(ScrimState.AOD);
575 mScrimController.finishAnimationsImmediately();
576 // Consume whatever value we had before
577 mScrimController.wasAnimationJustCancelled();
578
579 mScrimController.transitionTo(ScrimState.KEYGUARD);
580 mScrimController.finishAnimationsImmediately();
581 Assert.assertTrue(mScrimController.wasAnimationJustCancelled());
582 }
583
Lucas Dupin3daf3b02018-02-27 17:23:41 -0800584 @Test
Lucas Dupin38962d72018-03-14 12:41:39 -0700585 public void testScrimFocus() {
586 mScrimController.transitionTo(ScrimState.AOD);
587 Assert.assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable());
588 Assert.assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable());
589
590 mScrimController.transitionTo(ScrimState.KEYGUARD);
591 Assert.assertTrue("Should be focusable on keyguard", mScrimBehind.isFocusable());
592 Assert.assertTrue("Should be focusable on keyguard", mScrimInFront.isFocusable());
593 }
594
Lucas Dupind5107302018-03-19 15:30:29 -0700595 @Test
596 public void testHidesShowWhenLockedActivity() {
597 mScrimController.setWallpaperSupportsAmbientMode(true);
598 mScrimController.setKeyguardOccluded(true);
599 mScrimController.transitionTo(ScrimState.AOD);
600 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700601 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
Lucas Dupind5107302018-03-19 15:30:29 -0700602
603 mScrimController.transitionTo(ScrimState.PULSING);
604 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700605 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
Lucas Dupind5107302018-03-19 15:30:29 -0700606 }
607
Lucas Dupin78949b82018-04-03 18:54:39 -0700608 @Test
Lucas Dupin63d72172018-06-06 11:42:55 -0700609 public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
610 mScrimController.setWallpaperSupportsAmbientMode(true);
611 mScrimController.transitionTo(ScrimState.AOD);
612 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700613 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
Lucas Dupin63d72172018-06-06 11:42:55 -0700614
615 mScrimController.setKeyguardOccluded(true);
616 mScrimController.finishAnimationsImmediately();
Selim Cinekf97d7f72019-10-25 17:37:00 -0700617 assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
Lucas Dupin63d72172018-06-06 11:42:55 -0700618 }
619
620 @Test
Lucas Dupin78949b82018-04-03 18:54:39 -0700621 public void testEatsTouchEvent() {
622 HashSet<ScrimState> eatsTouches =
Lucas Dupin1c327432019-01-03 13:37:53 -0800623 new HashSet<>(Collections.singletonList(ScrimState.AOD));
Lucas Dupin78949b82018-04-03 18:54:39 -0700624 for (ScrimState state : ScrimState.values()) {
625 if (state == ScrimState.UNINITIALIZED) {
626 continue;
627 }
628 mScrimController.transitionTo(state);
629 mScrimController.finishAnimationsImmediately();
630 Assert.assertEquals("Should be clickable unless AOD or PULSING, was: " + state,
631 mScrimBehind.getViewAlpha() != 0 && !eatsTouches.contains(state),
632 mScrimBehind.isClickable());
633 }
634 }
635
Lucas Dupinea0116e2018-04-05 10:09:29 -0700636 @Test
637 public void testAnimatesTransitionToAod() {
638 when(mDozeParamenters.shouldControlScreenOff()).thenReturn(false);
639 ScrimState.AOD.prepare(ScrimState.KEYGUARD);
640 Assert.assertFalse("No animation when ColorFade kicks in",
641 ScrimState.AOD.getAnimateChange());
642
643 reset(mDozeParamenters);
644 when(mDozeParamenters.shouldControlScreenOff()).thenReturn(true);
645 ScrimState.AOD.prepare(ScrimState.KEYGUARD);
646 Assert.assertTrue("Animate scrims when ColorFade won't be triggered",
647 ScrimState.AOD.getAnimateChange());
648 }
649
Lucas Dupin373356b2018-04-07 10:50:25 -0700650 @Test
651 public void testViewsDontHaveFocusHighlight() {
652 Assert.assertFalse("Scrim shouldn't have focus highlight",
653 mScrimInFront.getDefaultFocusHighlightEnabled());
654 Assert.assertFalse("Scrim shouldn't have focus highlight",
655 mScrimBehind.getDefaultFocusHighlightEnabled());
656 }
657
Selim Cinekf97d7f72019-10-25 17:37:00 -0700658 private void assertScrimTint(ScrimView scrimView, boolean tinted) {
659 final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
660 final String name = scrimView == mScrimInFront ? "front" : "back";
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800661 Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
Selim Cinekf97d7f72019-10-25 17:37:00 -0700662 +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
663 tinted, viewIsTinted);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800664 }
665
Selim Cinekf97d7f72019-10-25 17:37:00 -0700666 private void assertScrimVisibility(int inFront, int behind) {
667 boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
668 boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
669 Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
670 + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
671 Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
672 + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
Lyn Hanbde48202019-05-29 19:18:29 -0700673
Lyn Hanbde48202019-05-29 19:18:29 -0700674 final int visibility;
Selim Cinekf97d7f72019-10-25 17:37:00 -0700675 if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
676 visibility = VISIBILITY_FULLY_OPAQUE;
677 } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
678 visibility = VISIBILITY_SEMI_TRANSPARENT;
Lyn Hanbde48202019-05-29 19:18:29 -0700679 } else {
Selim Cinekf97d7f72019-10-25 17:37:00 -0700680 visibility = VISIBILITY_FULLY_TRANSPARENT;
Lyn Hanbde48202019-05-29 19:18:29 -0700681 }
Selim Cinekf97d7f72019-10-25 17:37:00 -0700682 Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800683 }
684
685 /**
686 * Special version of ScrimController where animations have 0 duration for test purposes.
687 */
688 private class SynchronousScrimController extends ScrimController {
689
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800690 private boolean mAnimationCancelled;
Lucas Dupin16cfe452018-02-08 13:14:50 -0800691 boolean mOnPreDrawCalled;
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800692
shawnlin317db372018-04-09 19:49:48 +0800693 SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
Yohei Yukawa795f0102018-04-13 14:55:30 -0700694 TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Lucas Dupin82aa1632017-12-13 00:13:57 -0800695 Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
Selim Cinek72cd9a72019-08-09 17:19:57 -0700696 AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
Selim Cinekf97d7f72019-10-25 17:37:00 -0700697 super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
698 dozeParameters, alarmManager, keyguardMonitor);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800699 }
700
Lucas Dupin16cfe452018-02-08 13:14:50 -0800701 @Override
702 public boolean onPreDraw() {
703 mOnPreDrawCalled = true;
704 return super.onPreDraw();
705 }
706
Lucas Dupin3503c5f2018-03-02 19:04:00 -0800707 void finishAnimationsImmediately() {
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800708 boolean[] animationFinished = {false};
Selim Cinekf97d7f72019-10-25 17:37:00 -0700709 setOnAnimationFinished(()-> animationFinished[0] = true);
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800710 // Execute code that will trigger animations.
711 onPreDraw();
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800712 // Force finish all animations.
Lucas Dupin54fbfb32019-03-05 18:08:13 -0800713 mLooper.processAllMessages();
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800714 endAnimation(mScrimBehind, TAG_KEY_ANIM);
715 endAnimation(mScrimInFront, TAG_KEY_ANIM);
716
717 if (!animationFinished[0]) {
718 throw new IllegalStateException("Animation never finished");
719 }
720 }
721
Lucas Dupin3503c5f2018-03-02 19:04:00 -0800722 boolean wasAnimationJustCancelled() {
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800723 final boolean wasCancelled = mAnimationCancelled;
724 mAnimationCancelled = false;
725 return wasCancelled;
726 }
727
Lucas Dupin3daf3b02018-02-27 17:23:41 -0800728 private void endAnimation(View scrimView, int tag) {
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800729 Animator animator = (Animator) scrimView.getTag(tag);
730 if (animator != null) {
731 animator.end();
732 }
733 }
734
735 @Override
Lucas Dupin80a3fcc2018-02-07 10:49:55 -0800736 protected void cancelAnimator(ValueAnimator previousAnimator) {
737 super.cancelAnimator(previousAnimator);
738 mAnimationCancelled = true;
739 }
740
741 @Override
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800742 protected Handler getHandler() {
Lucas Dupin54fbfb32019-03-05 18:08:13 -0800743 return new FakeHandler(mLooper.getLooper());
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800744 }
745
746 @Override
747 protected WakeLock createWakeLock() {
748 return mWakeLock;
749 }
Lucas Dupin8c7cb022018-02-05 10:49:03 -0800750
751 /**
752 * Do not wait for a frame since we're in a test environment.
753 * @param callback What to execute.
754 */
755 @Override
Lucas Dupin0791d972018-03-26 13:32:16 -0700756 protected void doOnTheNextFrame(Runnable callback) {
757 callback.run();
Lucas Dupin8c7cb022018-02-05 10:49:03 -0800758 }
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800759 }
Lucas Dupin9e3fa102017-11-08 17:16:55 -0800760}