1 /* 2 * Copyright (C) 2021 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.dreams; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyBoolean; 21 import static org.mockito.ArgumentMatchers.anyFloat; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.Mockito.never; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.when; 27 28 import android.content.res.Resources; 29 import android.graphics.Region; 30 import android.os.Handler; 31 import android.testing.AndroidTestingRunner; 32 import android.view.AttachedSurfaceControl; 33 import android.view.ViewGroup; 34 import android.view.ViewRootImpl; 35 import android.view.ViewTreeObserver; 36 37 import androidx.test.filters.SmallTest; 38 39 import com.android.dream.lowlight.LowLightTransitionCoordinator; 40 import com.android.keyguard.BouncerPanelExpansionCalculator; 41 import com.android.systemui.SysuiTestCase; 42 import com.android.systemui.complication.ComplicationHostViewController; 43 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController; 44 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; 45 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; 46 import com.android.systemui.statusbar.BlurUtils; 47 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.mockito.ArgumentCaptor; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 55 @SmallTest 56 @RunWith(AndroidTestingRunner.class) 57 public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { 58 private static final int MAX_BURN_IN_OFFSET = 20; 59 private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10; 60 private static final long MILLIS_UNTIL_FULL_JITTER = 240 * 1000; 61 62 @Mock 63 Resources mResources; 64 65 @Mock 66 ViewTreeObserver mViewTreeObserver; 67 68 @Mock 69 DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController; 70 71 @Mock 72 LowLightTransitionCoordinator mLowLightTransitionCoordinator; 73 74 @Mock 75 DreamOverlayContainerView mDreamOverlayContainerView; 76 77 @Mock 78 ComplicationHostViewController mComplicationHostViewController; 79 80 @Mock 81 AttachedSurfaceControl mAttachedSurfaceControl; 82 83 @Mock 84 ViewGroup mDreamOverlayContentView; 85 86 @Mock 87 Handler mHandler; 88 89 @Mock 90 BlurUtils mBlurUtils; 91 92 @Mock 93 ViewRootImpl mViewRoot; 94 95 @Mock 96 PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; 97 98 @Mock 99 DreamOverlayAnimationsController mAnimationsController; 100 101 @Mock 102 BouncerlessScrimController mBouncerlessScrimController; 103 104 @Mock 105 DreamOverlayStateController mStateController; 106 107 DreamOverlayContainerViewController mController; 108 109 @Before setup()110 public void setup() { 111 MockitoAnnotations.initMocks(this); 112 113 when(mDreamOverlayContainerView.getResources()).thenReturn(mResources); 114 when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver); 115 when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot); 116 when(mDreamOverlayContainerView.getRootSurfaceControl()) 117 .thenReturn(mAttachedSurfaceControl); 118 119 mController = new DreamOverlayContainerViewController( 120 mDreamOverlayContainerView, 121 mComplicationHostViewController, 122 mDreamOverlayContentView, 123 mDreamOverlayStatusBarViewController, 124 mLowLightTransitionCoordinator, 125 mBlurUtils, 126 mHandler, 127 mResources, 128 MAX_BURN_IN_OFFSET, 129 BURN_IN_PROTECTION_UPDATE_INTERVAL, 130 MILLIS_UNTIL_FULL_JITTER, 131 mPrimaryBouncerCallbackInteractor, 132 mAnimationsController, 133 mStateController, 134 mBouncerlessScrimController); 135 } 136 137 @Test testRootSurfaceControlInsetSetOnAttach()138 public void testRootSurfaceControlInsetSetOnAttach() { 139 mController.onViewAttached(); 140 verify(mAttachedSurfaceControl).setTouchableRegion(eq(Region.obtain())); 141 } 142 143 @Test testDreamOverlayStatusBarViewControllerInitialized()144 public void testDreamOverlayStatusBarViewControllerInitialized() { 145 mController.init(); 146 verify(mDreamOverlayStatusBarViewController).init(); 147 } 148 149 @Test testBurnInProtectionStartsWhenContentViewAttached()150 public void testBurnInProtectionStartsWhenContentViewAttached() { 151 mController.onViewAttached(); 152 verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); 153 } 154 155 @Test testBurnInProtectionStopsWhenContentViewDetached()156 public void testBurnInProtectionStopsWhenContentViewDetached() { 157 mController.onViewDetached(); 158 verify(mHandler).removeCallbacks(any(Runnable.class)); 159 } 160 161 @Test testBurnInProtectionOffsetsStartAtZero()162 public void testBurnInProtectionOffsetsStartAtZero() { 163 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 164 mController.onViewAttached(); 165 verify(mHandler).postDelayed( 166 runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); 167 runnableCaptor.getValue().run(); 168 verify(mDreamOverlayContainerView).setTranslationX(0.f); 169 verify(mDreamOverlayContainerView).setTranslationY(0.f); 170 } 171 172 @Test testBurnInProtectionReschedulesUpdate()173 public void testBurnInProtectionReschedulesUpdate() { 174 ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); 175 mController.onViewAttached(); 176 verify(mHandler).postDelayed( 177 runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); 178 runnableCaptor.getValue().run(); 179 verify(mHandler).postDelayed(runnableCaptor.getValue(), BURN_IN_PROTECTION_UPDATE_INTERVAL); 180 } 181 182 @Test testBouncerAnimation_doesNotApply()183 public void testBouncerAnimation_doesNotApply() { 184 final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor = 185 ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class); 186 mController.onViewAttached(); 187 verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback( 188 bouncerExpansionCaptor.capture()); 189 190 bouncerExpansionCaptor.getValue().onExpansionChanged(0.5f); 191 verify(mBlurUtils, never()).applyBlur(eq(mViewRoot), anyInt(), eq(false)); 192 } 193 194 @Test testBouncerAnimation_updateBlur()195 public void testBouncerAnimation_updateBlur() { 196 final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor = 197 ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class); 198 mController.onViewAttached(); 199 verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback( 200 bouncerExpansionCaptor.capture()); 201 202 final float blurRadius = 1337f; 203 when(mBlurUtils.blurRadiusOfRatio(anyFloat())).thenReturn(blurRadius); 204 205 bouncerExpansionCaptor.getValue().onStartingToShow(); 206 207 final float bouncerHideAmount = 0.05f; 208 final float scaledFraction = 209 BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(bouncerHideAmount); 210 211 bouncerExpansionCaptor.getValue().onExpansionChanged(bouncerHideAmount); 212 verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction); 213 verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false); 214 } 215 216 @Test testStartDreamEntryAnimationsOnAttachedNonLowLight()217 public void testStartDreamEntryAnimationsOnAttachedNonLowLight() { 218 when(mStateController.isLowLightActive()).thenReturn(false); 219 220 mController.onViewAttached(); 221 222 verify(mAnimationsController).startEntryAnimations(false); 223 verify(mAnimationsController, never()).cancelAnimations(); 224 } 225 226 @Test testNeverStartDreamEntryAnimationsOnAttachedForLowLight()227 public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() { 228 when(mStateController.isLowLightActive()).thenReturn(true); 229 230 mController.onViewAttached(); 231 232 verify(mAnimationsController, never()).startEntryAnimations(anyBoolean()); 233 } 234 235 @Test testDownwardEntryAnimationsWhenExitingLowLight()236 public void testDownwardEntryAnimationsWhenExitingLowLight() { 237 ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = 238 ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); 239 when(mStateController.isLowLightActive()).thenReturn(false); 240 241 // Call onInit so that the callback is added. 242 mController.onInit(); 243 verify(mStateController).addCallback(callbackCaptor.capture()); 244 245 // Send the signal that low light is exiting 246 callbackCaptor.getValue().onExitLowLight(); 247 248 // View is attached to trigger animations. 249 mController.onViewAttached(); 250 251 // Entry animations should be started then immediately ended to skip to the end. 252 verify(mAnimationsController).startEntryAnimations(true); 253 } 254 255 @Test testStartsExitAnimationsBeforeEnteringLowLight()256 public void testStartsExitAnimationsBeforeEnteringLowLight() { 257 mController.onBeforeEnterLowLight(); 258 259 verify(mAnimationsController).startExitAnimations(); 260 } 261 262 @Test testCancelDreamEntryAnimationsOnDetached()263 public void testCancelDreamEntryAnimationsOnDetached() { 264 mController.onViewAttached(); 265 mController.onViewDetached(); 266 267 verify(mAnimationsController).cancelAnimations(); 268 } 269 } 270