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 17package com.android.systemui.statusbar.phone; 18 19import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate; 20 21import android.content.res.Resources; 22import android.util.MathUtils; 23 24import com.android.keyguard.KeyguardStatusView; 25import com.android.systemui.Interpolators; 26import com.android.systemui.R; 27 28/** 29 * Utility class to calculate the clock position and top padding of notifications on Keyguard. 30 */ 31public 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 */ 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 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 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 160 public float getMinStackScrollerPadding() { 161 return mMinTopMargin + mKeyguardStatusHeight + mClockNotificationsMargin; 162 } 163 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 */ 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 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