• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
20 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
21 import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
22 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
23 import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
24 import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
25 
26 import android.animation.Animator;
27 import android.content.res.Resources;
28 import android.os.Handler;
29 import android.util.MathUtils;
30 import android.view.View;
31 import android.view.ViewGroup;
32 
33 import androidx.annotation.NonNull;
34 
35 import com.android.dream.lowlight.LowLightTransitionCoordinator;
36 import com.android.systemui.R;
37 import com.android.systemui.animation.Interpolators;
38 import com.android.systemui.dagger.qualifiers.Main;
39 import com.android.systemui.dreams.complication.ComplicationHostViewController;
40 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
41 import com.android.systemui.dreams.dagger.DreamOverlayModule;
42 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
43 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
44 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
45 import com.android.systemui.shade.ShadeExpansionChangeEvent;
46 import com.android.systemui.statusbar.BlurUtils;
47 import com.android.systemui.util.ViewController;
48 import com.android.systemui.util.concurrency.DelayableExecutor;
49 
50 import java.util.Arrays;
51 
52 import javax.inject.Inject;
53 import javax.inject.Named;
54 
55 /**
56  * View controller for {@link DreamOverlayContainerView}.
57  */
58 @DreamOverlayComponent.DreamOverlayScope
59 public class DreamOverlayContainerViewController extends
60         ViewController<DreamOverlayContainerView> implements
61         LowLightTransitionCoordinator.LowLightEnterListener {
62     private final DreamOverlayStatusBarViewController mStatusBarViewController;
63     private final BlurUtils mBlurUtils;
64     private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
65     private final DreamOverlayStateController mStateController;
66     private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
67 
68     private final ComplicationHostViewController mComplicationHostViewController;
69 
70     // The dream overlay's content view, which is located below the status bar (in z-order) and is
71     // the space into which widgets are placed.
72     private final ViewGroup mDreamOverlayContentView;
73 
74     // The maximum translation offset to apply to the overlay container to avoid screen burn-in.
75     private final int mMaxBurnInOffset;
76 
77     // The interval in milliseconds between burn-in protection updates.
78     private final long mBurnInProtectionUpdateInterval;
79 
80     // Amount of time in milliseconds to linear interpolate toward the final jitter offset. Once
81     // this time is achieved, the normal jitter algorithm applies in full.
82     private final long mMillisUntilFullJitter;
83 
84     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
85     private final Handler mHandler;
86     private final int mDreamOverlayMaxTranslationY;
87     private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
88 
89     private long mJitterStartTimeMillis;
90 
91     private boolean mBouncerAnimating;
92     private boolean mWakingUpFromSwipe;
93 
94     private final BouncerlessScrimController mBouncerlessScrimController;
95 
96     private final BouncerlessScrimController.Callback mBouncerlessExpansionCallback =
97             new BouncerlessScrimController.Callback() {
98         @Override
99         public void onExpansion(ShadeExpansionChangeEvent event) {
100             updateTransitionState(event.getFraction());
101         }
102 
103         @Override
104         public void onWakeup() {
105             mWakingUpFromSwipe = true;
106         }
107     };
108 
109     private final PrimaryBouncerExpansionCallback
110             mBouncerExpansionCallback =
111             new PrimaryBouncerExpansionCallback() {
112 
113                 @Override
114                 public void onStartingToShow() {
115                     mBouncerAnimating = true;
116                 }
117 
118                 @Override
119                 public void onStartingToHide() {
120                     mBouncerAnimating = true;
121                 }
122 
123                 @Override
124                 public void onFullyHidden() {
125                     mBouncerAnimating = false;
126                 }
127 
128                 @Override
129                 public void onFullyShown() {
130                     mBouncerAnimating = false;
131                 }
132 
133                 @Override
134                 public void onExpansionChanged(float bouncerHideAmount) {
135                     if (mBouncerAnimating) {
136                         updateTransitionState(bouncerHideAmount);
137                     }
138                 }
139 
140                 @Override
141                 public void onVisibilityChanged(boolean isVisible) {
142                     // The bouncer may be hidden abruptly without triggering onExpansionChanged.
143                     // In this case, we should reset the transition state.
144                     if (!isVisible) {
145                         updateTransitionState(1f);
146                     }
147                 }
148             };
149 
150     /**
151      * If {@code true}, the dream has just transitioned from the low light dream back to the user
152      * dream and we should play an entry animation where the overlay slides in downwards from the
153      * top instead of the typicla slide in upwards from the bottom.
154      */
155     private boolean mExitingLowLight;
156 
157     private final DreamOverlayStateController.Callback
158             mDreamOverlayStateCallback =
159             new DreamOverlayStateController.Callback() {
160                 @Override
161                 public void onExitLowLight() {
162                     mExitingLowLight = true;
163                 }
164             };
165 
166     @Inject
DreamOverlayContainerViewController( DreamOverlayContainerView containerView, ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, DreamOverlayStatusBarViewController statusBarViewController, LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @Main Handler handler, @Main Resources resources, @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long burnInProtectionUpdateInterval, @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, DreamOverlayAnimationsController animationsController, DreamOverlayStateController stateController, BouncerlessScrimController bouncerlessScrimController)167     public DreamOverlayContainerViewController(
168             DreamOverlayContainerView containerView,
169             ComplicationHostViewController complicationHostViewController,
170             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
171             DreamOverlayStatusBarViewController statusBarViewController,
172             LowLightTransitionCoordinator lowLightTransitionCoordinator,
173             BlurUtils blurUtils,
174             @Main Handler handler,
175             @Main Resources resources,
176             @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
177             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
178                     burnInProtectionUpdateInterval,
179             @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
180             PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
181             DreamOverlayAnimationsController animationsController,
182             DreamOverlayStateController stateController,
183             BouncerlessScrimController bouncerlessScrimController) {
184         super(containerView);
185         mDreamOverlayContentView = contentView;
186         mStatusBarViewController = statusBarViewController;
187         mBlurUtils = blurUtils;
188         mDreamOverlayAnimationsController = animationsController;
189         mStateController = stateController;
190         mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
191 
192         mBouncerlessScrimController = bouncerlessScrimController;
193         mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
194 
195         mComplicationHostViewController = complicationHostViewController;
196         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
197                 R.dimen.dream_overlay_y_offset);
198         final View view = mComplicationHostViewController.getView();
199 
200         mDreamOverlayContentView.addView(view,
201                 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
202                         ViewGroup.LayoutParams.MATCH_PARENT));
203 
204         mHandler = handler;
205         mMaxBurnInOffset = maxBurnInOffset;
206         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
207         mMillisUntilFullJitter = millisUntilFullJitter;
208         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
209     }
210 
211     @Override
onInit()212     protected void onInit() {
213         mStateController.addCallback(mDreamOverlayStateCallback);
214         mStatusBarViewController.init();
215         mComplicationHostViewController.init();
216         mDreamOverlayAnimationsController.init(mView);
217         mLowLightTransitionCoordinator.setLowLightEnterListener(this);
218     }
219 
220     @Override
onViewAttached()221     protected void onViewAttached() {
222         mWakingUpFromSwipe = false;
223         mJitterStartTimeMillis = System.currentTimeMillis();
224         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
225         mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
226 
227         // Start dream entry animations. Skip animations for low light clock.
228         if (!mStateController.isLowLightActive()) {
229             // If this is transitioning from the low light dream to the user dream, the overlay
230             // should translate in downwards instead of upwards.
231             mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
232             mExitingLowLight = false;
233         }
234     }
235 
236     @Override
onViewDetached()237     protected void onViewDetached() {
238         mHandler.removeCallbacks(this::updateBurnInOffsets);
239         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
240 
241         mDreamOverlayAnimationsController.cancelAnimations();
242     }
243 
getContainerView()244     View getContainerView() {
245         return mView;
246     }
247 
updateBurnInOffsets()248     private void updateBurnInOffsets() {
249         // Make sure the offset starts at zero, to avoid a big jump in the overlay when it first
250         // appears.
251         final long millisSinceStart = System.currentTimeMillis() - mJitterStartTimeMillis;
252         final int burnInOffset;
253         if (millisSinceStart < mMillisUntilFullJitter) {
254             float lerpAmount = (float) millisSinceStart / (float) mMillisUntilFullJitter;
255             burnInOffset = Math.round(MathUtils.lerp(0f, mMaxBurnInOffset, lerpAmount));
256         } else {
257             burnInOffset = mMaxBurnInOffset;
258         }
259 
260         // These translation values change slowly, and the set translation methods are idempotent,
261         // so no translation occurs when the values don't change.
262         final int halfBurnInOffset = burnInOffset / 2;
263         final int burnInOffsetX = getBurnInOffset(burnInOffset, true) - halfBurnInOffset;
264         final int burnInOffsetY = getBurnInOffset(burnInOffset, false) - halfBurnInOffset;
265         mView.setTranslationX(burnInOffsetX);
266         mView.setTranslationY(burnInOffsetY);
267 
268         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
269     }
270 
updateTransitionState(float bouncerHideAmount)271     private void updateTransitionState(float bouncerHideAmount) {
272         for (int position : Arrays.asList(POSITION_TOP, POSITION_BOTTOM)) {
273             final float alpha = getAlpha(position, bouncerHideAmount);
274             final float translationY = getTranslationY(position, bouncerHideAmount);
275             mComplicationHostViewController.getViewsAtPosition(position).forEach(v -> {
276                 v.setAlpha(alpha);
277                 v.setTranslationY(translationY);
278             });
279         }
280 
281         mBlurUtils.applyBlur(mView.getViewRootImpl(),
282                 (int) mBlurUtils.blurRadiusOfRatio(
283                         1 - aboutToShowBouncerProgress(bouncerHideAmount)), false);
284     }
285 
getAlpha(int position, float expansion)286     private static float getAlpha(int position, float expansion) {
287         return Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
288                 position == POSITION_TOP ? getDreamAlphaScaledExpansion(expansion)
289                         : aboutToShowBouncerProgress(expansion + 0.03f));
290     }
291 
getTranslationY(int position, float expansion)292     private float getTranslationY(int position, float expansion) {
293         final float fraction = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(
294                 position == POSITION_TOP ? getDreamYPositionScaledExpansion(expansion)
295                         : aboutToShowBouncerProgress(expansion + 0.03f));
296         return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
297     }
298 
299     /**
300      * Handle the dream waking up and run any necessary animations.
301      *
302      * @param onAnimationEnd Callback to trigger once animations are finished.
303      * @param callbackExecutor Executor to execute the callback on.
304      */
wakeUp(@onNull Runnable onAnimationEnd, @NonNull DelayableExecutor callbackExecutor)305     public void wakeUp(@NonNull Runnable onAnimationEnd,
306             @NonNull DelayableExecutor callbackExecutor) {
307         // When swiping causes wakeup, do not run any animations as the dream should exit as soon
308         // as possible.
309         if (mWakingUpFromSwipe) {
310             onAnimationEnd.run();
311             return;
312         }
313 
314         mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
315     }
316 
317     @Override
onBeforeEnterLowLight()318     public Animator onBeforeEnterLowLight() {
319         // Return the animator so that the transition coordinator waits for the overlay exit
320         // animations to finish before entering low light, as otherwise the default DreamActivity
321         // animation plays immediately and there's no time for this animation to play.
322         return mDreamOverlayAnimationsController.startExitAnimations();
323     }
324 }
325