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.statusbar.notification.NotificationUtils.interpolate;
21 
22 import android.content.res.Resources;
23 import android.util.MathUtils;
24 
25 import com.android.keyguard.KeyguardStatusView;
26 import com.android.systemui.Interpolators;
27 import com.android.systemui.R;
28 
29 /**
30  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
31  */
32 public class KeyguardClockPositionAlgorithm {
33 
34     /**
35      * How much the clock height influences the shade position.
36      * 0 means nothing, 1 means move the shade up by the height of the clock
37      * 0.5f means move the shade up by half of the size of the clock.
38      */
39     private static float CLOCK_HEIGHT_WEIGHT = 0.7f;
40 
41     /**
42      * Margin between the bottom of the clock and the notification shade.
43      */
44     private int mClockNotificationsMargin;
45 
46     /**
47      * Height of the parent view - display size in px.
48      */
49     private int mHeight;
50 
51     /**
52      * Height of {@link KeyguardStatusView}.
53      */
54     private int mKeyguardStatusHeight;
55 
56     /**
57      * Preferred Y position of clock.
58      */
59     private int mClockPreferredY;
60 
61     /**
62      * Whether or not there is a custom clock face on keyguard.
63      */
64     private boolean mHasCustomClock;
65 
66     /**
67      * Whether or not the NSSL contains any visible notifications.
68      */
69     private boolean mHasVisibleNotifs;
70 
71     /**
72      * Height of notification stack: Sum of height of each notification.
73      */
74     private int mNotificationStackHeight;
75 
76     /**
77      * Minimum top margin to avoid overlap with status bar.
78      */
79     private int mMinTopMargin;
80 
81     /**
82      * Maximum bottom padding to avoid overlap with {@link KeyguardBottomAreaView} or
83      * the ambient indication.
84      */
85     private int mMaxShadeBottom;
86 
87     /**
88      * Minimum distance from the status bar.
89      */
90     private int mContainerTopPadding;
91 
92     /**
93      * @see NotificationPanelView#getExpandedFraction()
94      */
95     private float mPanelExpansion;
96 
97     /**
98      * Burn-in prevention x translation.
99      */
100     private int mBurnInPreventionOffsetX;
101 
102     /**
103      * Burn-in prevention y translation.
104      */
105     private int mBurnInPreventionOffsetY;
106 
107     /**
108      * Doze/AOD transition amount.
109      */
110     private float mDarkAmount;
111 
112     private float mEmptyDragAmount;
113 
114     /**
115      * Setting if bypass is enabled. If true the clock should always be positioned like it's dark
116      * and other minor adjustments.
117      */
118     private boolean mBypassEnabled;
119 
120     /**
121      * The stackscroller padding when unlocked
122      */
123     private int mUnlockedStackScrollerPadding;
124 
125     /**
126      * Refreshes the dimension values.
127      */
loadDimens(Resources res)128     public void loadDimens(Resources res) {
129         mClockNotificationsMargin = res.getDimensionPixelSize(
130                 R.dimen.keyguard_clock_notifications_margin);
131         // Consider the lock icon when determining the minimum top padding between the status bar
132         // and top of the clock.
133         mContainerTopPadding = Math.max(res.getDimensionPixelSize(
134                 R.dimen.keyguard_clock_top_margin),
135                 res.getDimensionPixelSize(R.dimen.keyguard_lock_height)
136                         + res.getDimensionPixelSize(R.dimen.keyguard_lock_padding)
137                         + res.getDimensionPixelSize(R.dimen.keyguard_clock_lock_margin));
138         mBurnInPreventionOffsetX = res.getDimensionPixelSize(
139                 R.dimen.burn_in_prevention_offset_x);
140         mBurnInPreventionOffsetY = res.getDimensionPixelSize(
141                 R.dimen.burn_in_prevention_offset_y);
142     }
143 
setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight, float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY, boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled, int unlockedStackScrollerPadding)144     public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
145             float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
146             boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
147             boolean bypassEnabled, int unlockedStackScrollerPadding) {
148         mMinTopMargin = minTopMargin + mContainerTopPadding;
149         mMaxShadeBottom = maxShadeBottom;
150         mNotificationStackHeight = notificationStackHeight;
151         mPanelExpansion = panelExpansion;
152         mHeight = parentHeight;
153         mKeyguardStatusHeight = keyguardStatusHeight;
154         mClockPreferredY = clockPreferredY;
155         mHasCustomClock = hasCustomClock;
156         mHasVisibleNotifs = hasVisibleNotifs;
157         mDarkAmount = dark;
158         mEmptyDragAmount = emptyDragAmount;
159         mBypassEnabled = bypassEnabled;
160         mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
161     }
162 
run(Result result)163     public void run(Result result) {
164         final int y = getClockY(mPanelExpansion);
165         result.clockY = y;
166         result.clockAlpha = getClockAlpha(y);
167         result.stackScrollerPadding = mBypassEnabled ? mUnlockedStackScrollerPadding
168                 : y + mKeyguardStatusHeight;
169         result.stackScrollerPaddingExpanded = mBypassEnabled ? mUnlockedStackScrollerPadding
170                 : getClockY(1.0f) + mKeyguardStatusHeight;
171         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
172     }
173 
getMinStackScrollerPadding()174     public float getMinStackScrollerPadding() {
175         return mBypassEnabled ? mUnlockedStackScrollerPadding
176                 : mMinTopMargin + mKeyguardStatusHeight + mClockNotificationsMargin;
177     }
178 
getMaxClockY()179     private int getMaxClockY() {
180         return mHeight / 2 - mKeyguardStatusHeight - mClockNotificationsMargin;
181     }
182 
getPreferredClockY()183     private int getPreferredClockY() {
184         return mClockPreferredY;
185     }
186 
getExpandedPreferredClockY()187     private int getExpandedPreferredClockY() {
188         return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
189                 : getExpandedClockPosition();
190     }
191 
192     /**
193      * Vertically align the clock and the shade in the available space considering only
194      * a percentage of the clock height defined by {@code CLOCK_HEIGHT_WEIGHT}.
195      * @return Clock Y in pixels.
196      */
getExpandedClockPosition()197     public int getExpandedClockPosition() {
198         final int availableHeight = mMaxShadeBottom - mMinTopMargin;
199         final int containerCenter = mMinTopMargin + availableHeight / 2;
200 
201         float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
202                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
203         if (y < mMinTopMargin) {
204             y = mMinTopMargin;
205         }
206 
207         // Don't allow the clock base to be under half of the screen
208         final float maxClockY = getMaxClockY();
209         if (y > maxClockY) {
210             y = maxClockY;
211         }
212 
213         return (int) y;
214     }
215 
getClockY(float panelExpansion)216     private int getClockY(float panelExpansion) {
217         // Dark: Align the bottom edge of the clock at about half of the screen:
218         float clockYDark = (mHasCustomClock ? getPreferredClockY() : getMaxClockY())
219                 + burnInPreventionOffsetY();
220         clockYDark = MathUtils.max(0, clockYDark);
221 
222         float clockYRegular = getExpandedPreferredClockY();
223         float clockYBouncer = -mKeyguardStatusHeight;
224 
225         // Move clock up while collapsing the shade
226         float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
227         float clockY = MathUtils.lerp(clockYBouncer, clockYRegular, shadeExpansion);
228         clockYDark = MathUtils.lerp(clockYBouncer, clockYDark, shadeExpansion);
229 
230         float darkAmount = mBypassEnabled && !mHasCustomClock ? 1.0f : mDarkAmount;
231         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
232     }
233 
234     /**
235      * We might want to fade out the clock when the user is swiping up.
236      * One exception is when the bouncer will become visible, in this cause the clock
237      * should always persist.
238      *
239      * @param y Current clock Y.
240      * @return Alpha from 0 to 1.
241      */
getClockAlpha(int y)242     private float getClockAlpha(int y) {
243         float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f)));
244         alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
245         return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
246     }
247 
burnInPreventionOffsetY()248     private float burnInPreventionOffsetY() {
249         return getBurnInOffset(mBurnInPreventionOffsetY * 2, false /* xAxis */)
250                 - mBurnInPreventionOffsetY;
251     }
252 
burnInPreventionOffsetX()253     private float burnInPreventionOffsetX() {
254         return getBurnInOffset(mBurnInPreventionOffsetX * 2, true /* xAxis */)
255                 - mBurnInPreventionOffsetX;
256     }
257 
258     public static class Result {
259 
260         /**
261          * The x translation of the clock.
262          */
263         public int clockX;
264 
265         /**
266          * The y translation of the clock.
267          */
268         public int clockY;
269 
270         /**
271          * The alpha value of the clock.
272          */
273         public float clockAlpha;
274 
275         /**
276          * The top padding of the stack scroller, in pixels.
277          */
278         public int stackScrollerPadding;
279 
280         /**
281          * The top padding of the stack scroller, in pixels when fully expanded.
282          */
283         public int stackScrollerPaddingExpanded;
284     }
285 }
286