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.graphics.Path; 23 import android.view.animation.AccelerateInterpolator; 24 import android.view.animation.PathInterpolator; 25 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 float SLOW_DOWN_FACTOR = 0.4f; 34 35 private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f; 36 private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f; 37 private static final float CLOCK_SCALE_FADE_START = 0.95f; 38 private static final float CLOCK_SCALE_FADE_END = 0.75f; 39 private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f; 40 41 private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f; 42 private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f; 43 44 private static final long MILLIS_PER_MINUTES = 1000 * 60; 45 private static final float BURN_IN_PREVENTION_PERIOD_Y = 521; 46 private static final float BURN_IN_PREVENTION_PERIOD_X = 83; 47 48 private int mClockNotificationsMarginMin; 49 private int mClockNotificationsMarginMax; 50 private float mClockYFractionMin; 51 private float mClockYFractionMax; 52 private int mMaxKeyguardNotifications; 53 private int mMaxPanelHeight; 54 private float mExpandedHeight; 55 private int mNotificationCount; 56 private int mHeight; 57 private int mKeyguardStatusHeight; 58 private float mEmptyDragAmount; 59 private float mDensity; 60 private int mBurnInPreventionOffsetX; 61 private int mBurnInPreventionOffsetY; 62 63 /** 64 * The number (fractional) of notifications the "more" card counts when calculating how many 65 * notifications are currently visible for the y positioning of the clock. 66 */ 67 private float mMoreCardNotificationAmount; 68 69 private static final PathInterpolator sSlowDownInterpolator; 70 71 static { 72 Path path = new Path(); 73 path.moveTo(0, 0); 74 path.cubicTo(0.3f, 0.875f, 0.6f, 1f, 1f, 1f); 75 sSlowDownInterpolator = new PathInterpolator(path); 76 } 77 78 private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); 79 private int mClockBottom; 80 private float mDarkAmount; 81 private int mDozingStackPadding; 82 83 /** 84 * Refreshes the dimension values. 85 */ 86 public void loadDimens(Resources res) { 87 mClockNotificationsMarginMin = res.getDimensionPixelSize( 88 R.dimen.keyguard_clock_notifications_margin_min); 89 mClockNotificationsMarginMax = res.getDimensionPixelSize( 90 R.dimen.keyguard_clock_notifications_margin_max); 91 mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); 92 mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); 93 mMoreCardNotificationAmount = 94 (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) / 95 res.getDimensionPixelSize(R.dimen.notification_min_height); 96 mDensity = res.getDisplayMetrics().density; 97 mBurnInPreventionOffsetX = res.getDimensionPixelSize( 98 R.dimen.burn_in_prevention_offset_x); 99 mBurnInPreventionOffsetY = res.getDimensionPixelSize( 100 R.dimen.burn_in_prevention_offset_y); 101 mDozingStackPadding = res.getDimensionPixelSize(R.dimen.dozing_stack_padding); 102 } 103 104 public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight, 105 int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount, 106 int clockBottom, float dark) { 107 mMaxKeyguardNotifications = maxKeyguardNotifications; 108 mMaxPanelHeight = maxPanelHeight; 109 mExpandedHeight = expandedHeight; 110 mNotificationCount = notificationCount; 111 mHeight = height; 112 mKeyguardStatusHeight = keyguardStatusHeight; 113 mEmptyDragAmount = emptyDragAmount; 114 mClockBottom = clockBottom; 115 mDarkAmount = dark; 116 } 117 118 public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) { 119 return mClockYFractionMin * height + keyguardStatusHeight / 2 120 + mClockNotificationsMarginMin; 121 } 122 123 public void run(Result result) { 124 int y = getClockY() - mKeyguardStatusHeight / 2; 125 float clockAdjustment = getClockYExpansionAdjustment(); 126 float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier(); 127 result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier); 128 int clockNotificationsPadding = getClockNotificationsPadding() 129 + result.stackScrollerPaddingAdjustment; 130 int padding = y + clockNotificationsPadding; 131 result.clockY = y; 132 result.stackScrollerPadding = mKeyguardStatusHeight + padding; 133 result.clockScale = getClockScale(result.stackScrollerPadding, 134 result.clockY, 135 y + getClockNotificationsPadding() + mKeyguardStatusHeight); 136 result.clockAlpha = getClockAlpha(result.clockScale); 137 138 result.stackScrollerPadding = (int) interpolate( 139 result.stackScrollerPadding, 140 mClockBottom + y + mDozingStackPadding, 141 mDarkAmount); 142 143 result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); 144 } 145 146 private float getClockScale(int notificationPadding, int clockY, int startPadding) { 147 float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f; 148 float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier; 149 float distanceToScaleEnd = notificationPadding - scaleEnd; 150 float progress = distanceToScaleEnd / (startPadding - scaleEnd); 151 progress = Math.max(0.0f, Math.min(progress, 1.0f)); 152 progress = mAccelerateInterpolator.getInterpolation(progress); 153 progress *= Math.pow(1 + mEmptyDragAmount / mDensity / 300, 0.3f); 154 return interpolate(progress, 1, mDarkAmount); 155 } 156 157 private int getClockNotificationsPadding() { 158 float t = getNotificationAmountT(); 159 t = Math.min(t, 1.0f); 160 return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax); 161 } 162 163 private float getClockYFraction() { 164 float t = getNotificationAmountT(); 165 t = Math.min(t, 1.0f); 166 return (1 - t) * mClockYFractionMax + t * mClockYFractionMin; 167 } 168 169 private int getClockY() { 170 // Dark: Align the bottom edge of the clock at one third: 171 // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom 172 float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom) 173 + burnInPreventionOffsetY(); 174 float clockYRegular = getClockYFraction() * mHeight; 175 return (int) interpolate(clockYRegular, clockYDark, mDarkAmount); 176 } 177 178 private float burnInPreventionOffsetY() { 179 return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES, 180 mBurnInPreventionOffsetY * 2, 181 BURN_IN_PREVENTION_PERIOD_Y) 182 - mBurnInPreventionOffsetY; 183 } 184 185 private float burnInPreventionOffsetX() { 186 return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES, 187 mBurnInPreventionOffsetX * 2, 188 BURN_IN_PREVENTION_PERIOD_X) 189 - mBurnInPreventionOffsetX; 190 } 191 192 /** 193 * Implements a continuous, piecewise linear, periodic zig-zag function 194 * 195 * Can be thought of as a linear approximation of abs(sin(x))) 196 * 197 * @param period period of the function, ie. zigzag(x + period) == zigzag(x) 198 * @param amplitude maximum value of the function 199 * @return a value between 0 and amplitude 200 */ 201 private float zigzag(float x, float amplitude, float period) { 202 float xprime = (x % period) / (period / 2); 203 float interpolationAmount = (xprime <= 1) ? xprime : (2 - xprime); 204 return interpolate(0, amplitude, interpolationAmount); 205 } 206 207 private float getClockYExpansionAdjustment() { 208 float rubberbandFactor = getClockYExpansionRubberbandFactor(); 209 float value = (rubberbandFactor * (mMaxPanelHeight - mExpandedHeight)); 210 float t = value / mMaxPanelHeight; 211 float slowedDownValue = -sSlowDownInterpolator.getInterpolation(t) * SLOW_DOWN_FACTOR 212 * mMaxPanelHeight; 213 if (mNotificationCount == 0) { 214 return (-2*value + slowedDownValue)/3; 215 } else { 216 return slowedDownValue; 217 } 218 } 219 220 private float getClockYExpansionRubberbandFactor() { 221 float t = getNotificationAmountT(); 222 t = Math.min(t, 1.0f); 223 t = (float) Math.pow(t, 0.3f); 224 return (1 - t) * CLOCK_RUBBERBAND_FACTOR_MAX + t * CLOCK_RUBBERBAND_FACTOR_MIN; 225 } 226 227 private float getTopPaddingAdjMultiplier() { 228 float t = getNotificationAmountT(); 229 t = Math.min(t, 1.0f); 230 return (1 - t) * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN 231 + t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX; 232 } 233 234 private float getClockAlpha(float scale) { 235 float fadeEnd = getNotificationAmountT() == 0.0f 236 ? CLOCK_SCALE_FADE_END_NO_NOTIFS 237 : CLOCK_SCALE_FADE_END; 238 float alpha = (scale - fadeEnd) 239 / (CLOCK_SCALE_FADE_START - fadeEnd); 240 return Math.max(0, Math.min(1, alpha)); 241 } 242 243 /** 244 * @return a value from 0 to 1 depending on how many notification there are 245 */ 246 private float getNotificationAmountT() { 247 return mNotificationCount 248 / (mMaxKeyguardNotifications + mMoreCardNotificationAmount); 249 } 250 251 public static class Result { 252 253 /** 254 * The y translation of the clock. 255 */ 256 public int clockY; 257 258 /** 259 * The scale of the Clock 260 */ 261 public float clockScale; 262 263 /** 264 * The alpha value of the clock. 265 */ 266 public float clockAlpha; 267 268 /** 269 * The top padding of the stack scroller, in pixels. 270 */ 271 public int stackScrollerPadding; 272 273 /** 274 * The top padding adjustment of the stack scroller, in pixels. This value is used to adjust 275 * the padding, but not the overall panel size. 276 */ 277 public int stackScrollerPaddingAdjustment; 278 279 /** The x translation of the clock. */ 280 public int clockX; 281 } 282 } 283