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