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