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.BouncerPanelExpansionCalculator; 27 import com.android.keyguard.KeyguardStatusView; 28 import com.android.systemui.R; 29 import com.android.systemui.animation.Interpolators; 30 import com.android.systemui.shade.NotificationPanelViewController; 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 NotificationPanelViewController#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 getLockscreenMinStackScrollerPadding()232 public float getLockscreenMinStackScrollerPadding() { 233 if (mBypassEnabled) { 234 return mUnlockedStackScrollerPadding; 235 } else if (mIsSplitShade) { 236 return mSplitShadeTargetTopMargin + mUserSwitchHeight; 237 } else { 238 return mMinTopMargin + mKeyguardStatusHeight; 239 } 240 } 241 getExpandedPreferredClockY()242 private int getExpandedPreferredClockY() { 243 if (mIsSplitShade) { 244 return mSplitShadeTargetTopMargin; 245 } else { 246 return mMinTopMargin; 247 } 248 } 249 getLockscreenStatusViewHeight()250 public int getLockscreenStatusViewHeight() { 251 return mKeyguardStatusHeight; 252 } 253 getClockY(float panelExpansion, float darkAmount)254 private int getClockY(float panelExpansion, float darkAmount) { 255 float clockYRegular = getExpandedPreferredClockY(); 256 257 // Dividing the height creates a smoother transition when the user swipes up to unlock 258 float clockYBouncer = -mKeyguardStatusHeight / 3.0f; 259 260 // Move clock up while collapsing the shade 261 float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); 262 float clockY = MathUtils.lerp(clockYBouncer, clockYRegular, shadeExpansion); 263 264 // This will keep the clock at the top but out of the cutout area 265 float shift = 0; 266 if (clockY - mMaxBurnInPreventionOffsetYClock < mCutoutTopInset) { 267 shift = mCutoutTopInset - (clockY - mMaxBurnInPreventionOffsetYClock); 268 } 269 270 int burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; // requested offset 271 final boolean hasUdfps = mUdfpsTop > -1; 272 if (hasUdfps && !mIsClockTopAligned) { 273 // ensure clock doesn't overlap with the udfps icon 274 if (mUdfpsTop < mClockBottom) { 275 // sometimes the clock textView extends beyond udfps, so let's just use the 276 // space above the KeyguardStatusView/clock as our burn-in offset 277 burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2; 278 if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { 279 burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; 280 } 281 shift = -burnInPreventionOffsetY; 282 } else { 283 float upperSpace = clockY - mCutoutTopInset; 284 float lowerSpace = mUdfpsTop - mClockBottom; 285 // center the burn-in offset within the upper + lower space 286 burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2; 287 if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { 288 burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; 289 } 290 shift = (lowerSpace - upperSpace) / 2; 291 } 292 } 293 294 float fullyDarkBurnInOffset = burnInPreventionOffsetY(burnInPreventionOffsetY); 295 float clockYDark = clockY 296 + fullyDarkBurnInOffset 297 + shift; 298 mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount); 299 return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); 300 } 301 getUserSwitcherY(float panelExpansion)302 private int getUserSwitcherY(float panelExpansion) { 303 float userSwitchYRegular = mUserSwitchPreferredY; 304 float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight; 305 306 // Move user-switch up while collapsing the shade 307 float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion); 308 float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion); 309 310 return (int) (userSwitchY + mOverStretchAmount); 311 } 312 313 /** 314 * We might want to fade out the clock when the user is swiping up. 315 * One exception is when the bouncer will become visible, in this cause the clock 316 * should always persist. 317 * 318 * @param y Current clock Y. 319 * @return Alpha from 0 to 1. 320 */ getClockAlpha(int y)321 private float getClockAlpha(int y) { 322 float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount))); 323 if (!mIsSplitShade) { 324 // in split shade QS are always expanded so this factor shouldn't apply 325 float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f); 326 qsAlphaFactor = 1f - qsAlphaFactor; 327 alphaKeyguard *= qsAlphaFactor; 328 } 329 alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard); 330 return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); 331 } 332 burnInPreventionOffsetY(int offset)333 private float burnInPreventionOffsetY(int offset) { 334 return getBurnInOffset(offset * 2, false /* xAxis */) - offset; 335 } 336 burnInPreventionOffsetX()337 private float burnInPreventionOffsetX() { 338 return getBurnInOffset(mMaxBurnInPreventionOffsetX, true /* xAxis */); 339 } 340 341 public static class Result { 342 343 /** 344 * The x translation of the clock. 345 */ 346 public int clockX; 347 348 /** 349 * The y translation of the clock. 350 */ 351 public int clockY; 352 353 /** 354 * The y translation of the multi-user switch. 355 */ 356 public int userSwitchY; 357 358 /** 359 * The y translation of the clock when we're fully dozing. 360 */ 361 public int clockYFullyDozing; 362 363 /** 364 * The alpha value of the clock. 365 */ 366 public float clockAlpha; 367 368 /** 369 * Amount to scale the large clock (0.0 - 1.0) 370 */ 371 public float clockScale; 372 373 /** 374 * The top padding of the stack scroller, in pixels. 375 */ 376 public int stackScrollerPadding; 377 378 /** 379 * The top padding of the stack scroller, in pixels when fully expanded. 380 */ 381 public int stackScrollerPaddingExpanded; 382 } 383 } 384