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