1 /*
2  * Copyright (C) 2014 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.statusbar.phone;
18 
19 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
20 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale;
21 import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
22 
23 import android.content.res.Resources;
24 import android.util.MathUtils;
25 
26 import com.android.keyguard.KeyguardStatusView;
27 import com.android.systemui.R;
28 import com.android.systemui.animation.Interpolators;
29 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
30 
31 /**
32  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
33  */
34 public class KeyguardClockPositionAlgorithm {
35 
36     /**
37      * Margin between the bottom of the status view and the notification shade.
38      */
39     private int mStatusViewBottomMargin;
40 
41     /**
42      * Height of {@link KeyguardStatusView}.
43      */
44     private int mKeyguardStatusHeight;
45 
46     /**
47      * Height of user avatar used by the multi-user switcher. This could either be the
48      * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is
49      * visible, or it could be height of the avatar used by the
50      * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}.
51      */
52     private int mUserSwitchHeight;
53 
54     /**
55      * Preferred Y position of user avatar used by the multi-user switcher.
56      */
57     private int mUserSwitchPreferredY;
58 
59     /**
60      * Minimum top margin to avoid overlap with status bar or multi-user switcher avatar.
61      */
62     private int mMinTopMargin;
63 
64     /**
65      * Minimum top inset (in pixels) to avoid overlap with any display cutouts.
66      */
67     private int mCutoutTopInset = 0;
68 
69     /**
70      * Recommended distance from the status bar.
71      */
72     private int mContainerTopPadding;
73 
74     /**
75      * Top margin of notifications introduced by presence of split shade header / status bar
76      */
77     private int mSplitShadeTopNotificationsMargin;
78 
79     /**
80      * Target margin for notifications and clock from the top of the screen in split shade
81      */
82     private int mSplitShadeTargetTopMargin;
83 
84     /**
85      * @see NotificationPanelViewController#getExpandedFraction()
86      */
87     private float mPanelExpansion;
88 
89     /**
90      * Burn-in prevention x translation.
91      */
92     private int mBurnInPreventionOffsetX;
93 
94     /**
95      * Burn-in prevention y translation for clock layouts.
96      */
97     private int mBurnInPreventionOffsetYClock;
98 
99     /**
100      * Doze/AOD transition amount.
101      */
102     private float mDarkAmount;
103 
104     /**
105      * How visible the quick settings panel is.
106      */
107     private float mQsExpansion;
108 
109     private float mOverStretchAmount;
110 
111     /**
112      * Setting if bypass is enabled. If true the clock should always be positioned like it's dark
113      * and other minor adjustments.
114      */
115     private boolean mBypassEnabled;
116 
117     /**
118      * The stackscroller padding when unlocked
119      */
120     private int mUnlockedStackScrollerPadding;
121 
122     private boolean mIsSplitShade;
123 
124     /**
125      * Top location of the udfps icon. This includes the worst case (highest) burn-in
126      * offset that would make the top physically highest on the screen.
127      *
128      * Set to -1 if udfps is not enrolled on the device.
129      */
130     private float mUdfpsTop;
131 
132     /**
133      * Bottom y-position of the currently visible clock
134      */
135     private float mClockBottom;
136 
137     /**
138      * If true, try to keep clock aligned to the top of the display. Else, assume the clock
139      * is center aligned.
140      */
141     private boolean mIsClockTopAligned;
142 
143     /**
144      * Refreshes the dimension values.
145      */
loadDimens(Resources res)146     public void loadDimens(Resources res) {
147         mStatusViewBottomMargin = res.getDimensionPixelSize(
148                 R.dimen.keyguard_status_view_bottom_margin);
149         mSplitShadeTopNotificationsMargin =
150                 res.getDimensionPixelSize(R.dimen.split_shade_header_height);
151         mSplitShadeTargetTopMargin =
152                 res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin);
153 
154         mContainerTopPadding =
155                 res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
156         mBurnInPreventionOffsetX = res.getDimensionPixelSize(
157                 R.dimen.burn_in_prevention_offset_x);
158         mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
159                 R.dimen.burn_in_prevention_offset_y_clock);
160     }
161 
162     /**
163      * Sets up algorithm values.
164      */
setup(int keyguardStatusBarHeaderHeight, float panelExpansion, int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, float dark, float overStretchAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned)165     public void setup(int keyguardStatusBarHeaderHeight, float panelExpansion,
166             int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
167             float dark, float overStretchAmount, boolean bypassEnabled,
168             int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset,
169             boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) {
170         mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
171                 userSwitchHeight);
172         mPanelExpansion = panelExpansion;
173         mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin;
174         mUserSwitchHeight = userSwitchHeight;
175         mUserSwitchPreferredY = userSwitchPreferredY;
176         mDarkAmount = dark;
177         mOverStretchAmount = overStretchAmount;
178         mBypassEnabled = bypassEnabled;
179         mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
180         mQsExpansion = qsExpansion;
181         mCutoutTopInset = cutoutTopInset;
182         mIsSplitShade = isSplitShade;
183         mUdfpsTop = udfpsTop;
184         mClockBottom = clockBottom;
185         mIsClockTopAligned = isClockTopAligned;
186     }
187 
run(Result result)188     public void run(Result result) {
189         final int y = getClockY(mPanelExpansion, mDarkAmount);
190         result.clockY = y;
191         result.userSwitchY = getUserSwitcherY(mPanelExpansion);
192         result.clockYFullyDozing = getClockY(
193                 1.0f /* panelExpansion */, 1.0f /* darkAmount */);
194         result.clockAlpha = getClockAlpha(y);
195         result.stackScrollerPadding = getStackScrollerPadding(y);
196         result.stackScrollerPaddingExpanded = getStackScrollerPaddingExpanded();
197         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
198         result.clockScale = interpolate(getBurnInScale(), 1.0f, 1.0f - mDarkAmount);
199     }
200 
getStackScrollerPaddingExpanded()201     private int getStackScrollerPaddingExpanded() {
202         if (mBypassEnabled) {
203             return mUnlockedStackScrollerPadding;
204         } else if (mIsSplitShade) {
205             return getClockY(1.0f, mDarkAmount) + mUserSwitchHeight;
206         } else {
207             return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
208         }
209     }
210 
getStackScrollerPadding(int clockYPosition)211     private int getStackScrollerPadding(int clockYPosition) {
212         if (mBypassEnabled) {
213             return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
214         } else if (mIsSplitShade) {
215             return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight;
216         } else {
217             return clockYPosition + mKeyguardStatusHeight;
218         }
219     }
220 
getMinStackScrollerPadding()221     public float getMinStackScrollerPadding() {
222         if (mBypassEnabled) {
223             return mUnlockedStackScrollerPadding;
224         } else if (mIsSplitShade) {
225             return mSplitShadeTargetTopMargin + mUserSwitchHeight;
226         } else {
227             return mMinTopMargin + mKeyguardStatusHeight;
228         }
229     }
230 
getExpandedPreferredClockY()231     private int getExpandedPreferredClockY() {
232         if (mIsSplitShade) {
233             return mSplitShadeTargetTopMargin;
234         } else {
235             return mMinTopMargin;
236         }
237     }
238 
getLockscreenStatusViewHeight()239     public int getLockscreenStatusViewHeight() {
240         return mKeyguardStatusHeight;
241     }
242 
getClockY(float panelExpansion, float darkAmount)243     private int getClockY(float panelExpansion, float darkAmount) {
244         float clockYRegular = getExpandedPreferredClockY();
245 
246         // Dividing the height creates a smoother transition when the user swipes up to unlock
247         float clockYBouncer = -mKeyguardStatusHeight / 3.0f;
248 
249         // Move clock up while collapsing the shade
250         float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
251         float clockY = MathUtils.lerp(clockYBouncer, clockYRegular, shadeExpansion);
252 
253         // This will keep the clock at the top but out of the cutout area
254         float shift = 0;
255         if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
256             shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
257         }
258 
259         int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
260         final boolean hasUdfps = mUdfpsTop > -1;
261         if (hasUdfps && !mIsClockTopAligned) {
262             // ensure clock doesn't overlap with the udfps icon
263             if (mUdfpsTop < mClockBottom) {
264                 // sometimes the clock textView extends beyond udfps, so let's just use the
265                 // space above the KeyguardStatusView/clock as our burn-in offset
266                 burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
267                 if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
268                     burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
269                 }
270                 shift = -burnInPreventionOffsetY;
271             } else {
272                 float upperSpace = clockY - mCutoutTopInset;
273                 float lowerSpace = mUdfpsTop - mClockBottom;
274                 // center the burn-in offset within the upper + lower space
275                 burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
276                 if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
277                     burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
278                 }
279                 shift = (lowerSpace - upperSpace) / 2;
280             }
281         }
282 
283         float clockYDark = clockY
284                 + burnInPreventionOffsetY(burnInPreventionOffsetY)
285                 + shift;
286         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
287     }
288 
getUserSwitcherY(float panelExpansion)289     private int getUserSwitcherY(float panelExpansion) {
290         float userSwitchYRegular = mUserSwitchPreferredY;
291         float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight;
292 
293         // Move user-switch up while collapsing the shade
294         float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
295         float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion);
296 
297         return (int) (userSwitchY + mOverStretchAmount);
298     }
299 
300     /**
301      * We might want to fade out the clock when the user is swiping up.
302      * One exception is when the bouncer will become visible, in this cause the clock
303      * should always persist.
304      *
305      * @param y Current clock Y.
306      * @return Alpha from 0 to 1.
307      */
getClockAlpha(int y)308     private float getClockAlpha(int y) {
309         float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount)));
310         float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f);
311         qsAlphaFactor = 1f - qsAlphaFactor;
312         alphaKeyguard *= qsAlphaFactor;
313         alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
314         return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
315     }
316 
burnInPreventionOffsetY(int offset)317     private float burnInPreventionOffsetY(int offset) {
318         return getBurnInOffset(offset * 2, false /* xAxis */) - offset;
319     }
320 
burnInPreventionOffsetX()321     private float burnInPreventionOffsetX() {
322         return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */);
323     }
324 
325     public static class Result {
326 
327         /**
328          * The x translation of the clock.
329          */
330         public int clockX;
331 
332         /**
333          * The y translation of the clock.
334          */
335         public int clockY;
336 
337         /**
338          * The y translation of the multi-user switch.
339          */
340         public int userSwitchY;
341 
342         /**
343          * The y translation of the clock when we're fully dozing.
344          */
345         public int clockYFullyDozing;
346 
347         /**
348          * The alpha value of the clock.
349          */
350         public float clockAlpha;
351 
352         /**
353          * Amount to scale the large clock (0.0 - 1.0)
354          */
355         public float clockScale;
356 
357         /**
358          * The top padding of the stack scroller, in pixels.
359          */
360         public int stackScrollerPadding;
361 
362         /**
363          * The top padding of the stack scroller, in pixels when fully expanded.
364          */
365         public int stackScrollerPaddingExpanded;
366     }
367 }
368