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.doze.util.BurnInHelperKt.getBurnInScale; 21 import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate; 22 23 import android.content.res.Resources; 24 import android.util.MathUtils; 25 26 import com.android.keyguard.KeyguardStatusView; 27 import com.android.systemui.R; 28 import com.android.systemui.animation.Interpolators; 29 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; 30 31 /** 32 * Utility class to calculate the clock position and top padding of notifications on Keyguard. 33 */ 34 public class KeyguardClockPositionAlgorithm { 35 36 /** 37 * Margin between the bottom of the status view and the notification shade. 38 */ 39 private int mStatusViewBottomMargin; 40 41 /** 42 * Height of {@link KeyguardStatusView}. 43 */ 44 private int mKeyguardStatusHeight; 45 46 /** 47 * Height of user avatar used by the multi-user switcher. This could either be the 48 * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is 49 * visible, or it could be height of the avatar used by the 50 * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}. 51 */ 52 private int mUserSwitchHeight; 53 54 /** 55 * Preferred Y position of user avatar used by the multi-user switcher. 56 */ 57 private int mUserSwitchPreferredY; 58 59 /** 60 * Minimum top margin to avoid overlap with status bar or multi-user switcher avatar. 61 */ 62 private int mMinTopMargin; 63 64 /** 65 * Minimum top inset (in pixels) to avoid overlap with any display cutouts. 66 */ 67 private int mCutoutTopInset = 0; 68 69 /** 70 * Recommended distance from the status bar. 71 */ 72 private int mContainerTopPadding; 73 74 /** 75 * Top margin of notifications introduced by presence of split shade header / status bar 76 */ 77 private int mSplitShadeTopNotificationsMargin; 78 79 /** 80 * Target margin for notifications and clock from the top of the screen in split shade 81 */ 82 private int mSplitShadeTargetTopMargin; 83 84 /** 85 * @see NotificationPanelViewController#getExpandedFraction() 86 */ 87 private float mPanelExpansion; 88 89 /** 90 * Burn-in prevention x translation. 91 */ 92 private int mBurnInPreventionOffsetX; 93 94 /** 95 * Burn-in prevention y translation for clock layouts. 96 */ 97 private int mBurnInPreventionOffsetYClock; 98 99 /** 100 * Doze/AOD transition amount. 101 */ 102 private float mDarkAmount; 103 104 /** 105 * How visible the quick settings panel is. 106 */ 107 private float mQsExpansion; 108 109 private float mOverStretchAmount; 110 111 /** 112 * Setting if bypass is enabled. If true the clock should always be positioned like it's dark 113 * and other minor adjustments. 114 */ 115 private boolean mBypassEnabled; 116 117 /** 118 * The stackscroller padding when unlocked 119 */ 120 private int mUnlockedStackScrollerPadding; 121 122 private boolean mIsSplitShade; 123 124 /** 125 * Top location of the udfps icon. This includes the worst case (highest) burn-in 126 * offset that would make the top physically highest on the screen. 127 * 128 * Set to -1 if udfps is not enrolled on the device. 129 */ 130 private float mUdfpsTop; 131 132 /** 133 * Bottom y-position of the currently visible clock 134 */ 135 private float mClockBottom; 136 137 /** 138 * If true, try to keep clock aligned to the top of the display. Else, assume the clock 139 * is center aligned. 140 */ 141 private boolean mIsClockTopAligned; 142 143 /** 144 * Refreshes the dimension values. 145 */ loadDimens(Resources res)146 public void loadDimens(Resources res) { 147 mStatusViewBottomMargin = res.getDimensionPixelSize( 148 R.dimen.keyguard_status_view_bottom_margin); 149 mSplitShadeTopNotificationsMargin = 150 res.getDimensionPixelSize(R.dimen.split_shade_header_height); 151 mSplitShadeTargetTopMargin = 152 res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin); 153 154 mContainerTopPadding = 155 res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); 156 mBurnInPreventionOffsetX = res.getDimensionPixelSize( 157 R.dimen.burn_in_prevention_offset_x); 158 mBurnInPreventionOffsetYClock = res.getDimensionPixelSize( 159 R.dimen.burn_in_prevention_offset_y_clock); 160 } 161 162 /** 163 * Sets up algorithm values. 164 */ setup(int keyguardStatusBarHeaderHeight, float panelExpansion, int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, float dark, float overStretchAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned)165 public void setup(int keyguardStatusBarHeaderHeight, float panelExpansion, 166 int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, 167 float dark, float overStretchAmount, boolean bypassEnabled, 168 int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset, 169 boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) { 170 mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding, 171 userSwitchHeight); 172 mPanelExpansion = panelExpansion; 173 mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin; 174 mUserSwitchHeight = userSwitchHeight; 175 mUserSwitchPreferredY = userSwitchPreferredY; 176 mDarkAmount = dark; 177 mOverStretchAmount = overStretchAmount; 178 mBypassEnabled = bypassEnabled; 179 mUnlockedStackScrollerPadding = unlockedStackScrollerPadding; 180 mQsExpansion = qsExpansion; 181 mCutoutTopInset = cutoutTopInset; 182 mIsSplitShade = isSplitShade; 183 mUdfpsTop = udfpsTop; 184 mClockBottom = clockBottom; 185 mIsClockTopAligned = isClockTopAligned; 186 } 187 run(Result result)188 public void run(Result result) { 189 final int y = getClockY(mPanelExpansion, mDarkAmount); 190 result.clockY = y; 191 result.userSwitchY = getUserSwitcherY(mPanelExpansion); 192 result.clockYFullyDozing = getClockY( 193 1.0f /* panelExpansion */, 1.0f /* darkAmount */); 194 result.clockAlpha = getClockAlpha(y); 195 result.stackScrollerPadding = getStackScrollerPadding(y); 196 result.stackScrollerPaddingExpanded = getStackScrollerPaddingExpanded(); 197 result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); 198 result.clockScale = interpolate(getBurnInScale(), 1.0f, 1.0f - mDarkAmount); 199 } 200 getStackScrollerPaddingExpanded()201 private int getStackScrollerPaddingExpanded() { 202 if (mBypassEnabled) { 203 return mUnlockedStackScrollerPadding; 204 } else if (mIsSplitShade) { 205 return getClockY(1.0f, mDarkAmount) + mUserSwitchHeight; 206 } else { 207 return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight; 208 } 209 } 210 getStackScrollerPadding(int clockYPosition)211 private int getStackScrollerPadding(int clockYPosition) { 212 if (mBypassEnabled) { 213 return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount); 214 } else if (mIsSplitShade) { 215 return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight; 216 } else { 217 return clockYPosition + mKeyguardStatusHeight; 218 } 219 } 220 getMinStackScrollerPadding()221 public float getMinStackScrollerPadding() { 222 if (mBypassEnabled) { 223 return mUnlockedStackScrollerPadding; 224 } else if (mIsSplitShade) { 225 return mSplitShadeTargetTopMargin + mUserSwitchHeight; 226 } else { 227 return mMinTopMargin + mKeyguardStatusHeight; 228 } 229 } 230 getExpandedPreferredClockY()231 private int getExpandedPreferredClockY() { 232 if (mIsSplitShade) { 233 return mSplitShadeTargetTopMargin; 234 } else { 235 return mMinTopMargin; 236 } 237 } 238 getLockscreenStatusViewHeight()239 public int getLockscreenStatusViewHeight() { 240 return mKeyguardStatusHeight; 241 } 242 getClockY(float panelExpansion, float darkAmount)243 private int getClockY(float panelExpansion, float darkAmount) { 244 float clockYRegular = getExpandedPreferredClockY(); 245 246 // Dividing the height creates a smoother transition when the user swipes up to unlock 247 float clockYBouncer = -mKeyguardStatusHeight / 3.0f; 248 249 // Move clock up while collapsing the shade 250 float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); 251 float clockY = MathUtils.lerp(clockYBouncer, clockYRegular, shadeExpansion); 252 253 // This will keep the clock at the top but out of the cutout area 254 float shift = 0; 255 if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) { 256 shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock); 257 } 258 259 int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset 260 final boolean hasUdfps = mUdfpsTop > -1; 261 if (hasUdfps && !mIsClockTopAligned) { 262 // ensure clock doesn't overlap with the udfps icon 263 if (mUdfpsTop < mClockBottom) { 264 // sometimes the clock textView extends beyond udfps, so let's just use the 265 // space above the KeyguardStatusView/clock as our burn-in offset 266 burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2; 267 if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { 268 burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; 269 } 270 shift = -burnInPreventionOffsetY; 271 } else { 272 float upperSpace = clockY - mCutoutTopInset; 273 float lowerSpace = mUdfpsTop - mClockBottom; 274 // center the burn-in offset within the upper + lower space 275 burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2; 276 if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { 277 burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; 278 } 279 shift = (lowerSpace - upperSpace) / 2; 280 } 281 } 282 283 float clockYDark = clockY 284 + burnInPreventionOffsetY(burnInPreventionOffsetY) 285 + shift; 286 return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); 287 } 288 getUserSwitcherY(float panelExpansion)289 private int getUserSwitcherY(float panelExpansion) { 290 float userSwitchYRegular = mUserSwitchPreferredY; 291 float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight; 292 293 // Move user-switch up while collapsing the shade 294 float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); 295 float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion); 296 297 return (int) (userSwitchY + mOverStretchAmount); 298 } 299 300 /** 301 * We might want to fade out the clock when the user is swiping up. 302 * One exception is when the bouncer will become visible, in this cause the clock 303 * should always persist. 304 * 305 * @param y Current clock Y. 306 * @return Alpha from 0 to 1. 307 */ getClockAlpha(int y)308 private float getClockAlpha(int y) { 309 float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount))); 310 float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f); 311 qsAlphaFactor = 1f - qsAlphaFactor; 312 alphaKeyguard *= qsAlphaFactor; 313 alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard); 314 return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); 315 } 316 burnInPreventionOffsetY(int offset)317 private float burnInPreventionOffsetY(int offset) { 318 return getBurnInOffset(offset * 2, false /* xAxis */) - offset; 319 } 320 burnInPreventionOffsetX()321 private float burnInPreventionOffsetX() { 322 return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */); 323 } 324 325 public static class Result { 326 327 /** 328 * The x translation of the clock. 329 */ 330 public int clockX; 331 332 /** 333 * The y translation of the clock. 334 */ 335 public int clockY; 336 337 /** 338 * The y translation of the multi-user switch. 339 */ 340 public int userSwitchY; 341 342 /** 343 * The y translation of the clock when we're fully dozing. 344 */ 345 public int clockYFullyDozing; 346 347 /** 348 * The alpha value of the clock. 349 */ 350 public float clockAlpha; 351 352 /** 353 * Amount to scale the large clock (0.0 - 1.0) 354 */ 355 public float clockScale; 356 357 /** 358 * The top padding of the stack scroller, in pixels. 359 */ 360 public int stackScrollerPadding; 361 362 /** 363 * The top padding of the stack scroller, in pixels when fully expanded. 364 */ 365 public int stackScrollerPaddingExpanded; 366 } 367 } 368