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