/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.phone; import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate; import android.content.res.Resources; import android.graphics.Path; import android.view.animation.AccelerateInterpolator; import android.view.animation.PathInterpolator; import com.android.systemui.R; /** * Utility class to calculate the clock position and top padding of notifications on Keyguard. */ public class KeyguardClockPositionAlgorithm { private static final float SLOW_DOWN_FACTOR = 0.4f; private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f; private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f; private static final float CLOCK_SCALE_FADE_START = 0.95f; private static final float CLOCK_SCALE_FADE_END = 0.75f; private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f; private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f; private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f; private static final long MILLIS_PER_MINUTES = 1000 * 60; private static final float BURN_IN_PREVENTION_PERIOD_Y = 521; private static final float BURN_IN_PREVENTION_PERIOD_X = 83; private int mClockNotificationsMarginMin; private int mClockNotificationsMarginMax; private float mClockYFractionMin; private float mClockYFractionMax; private int mMaxKeyguardNotifications; private int mMaxPanelHeight; private float mExpandedHeight; private int mNotificationCount; private int mHeight; private int mKeyguardStatusHeight; private float mEmptyDragAmount; private float mDensity; private int mBurnInPreventionOffsetX; private int mBurnInPreventionOffsetY; /** * The number (fractional) of notifications the "more" card counts when calculating how many * notifications are currently visible for the y positioning of the clock. */ private float mMoreCardNotificationAmount; private static final PathInterpolator sSlowDownInterpolator; static { Path path = new Path(); path.moveTo(0, 0); path.cubicTo(0.3f, 0.875f, 0.6f, 1f, 1f, 1f); sSlowDownInterpolator = new PathInterpolator(path); } private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator(); private int mClockBottom; private float mDarkAmount; private int mDozingStackPadding; /** * Refreshes the dimension values. */ public void loadDimens(Resources res) { mClockNotificationsMarginMin = res.getDimensionPixelSize( R.dimen.keyguard_clock_notifications_margin_min); mClockNotificationsMarginMax = res.getDimensionPixelSize( R.dimen.keyguard_clock_notifications_margin_max); mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); mMoreCardNotificationAmount = (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) / res.getDimensionPixelSize(R.dimen.notification_min_height); mDensity = res.getDisplayMetrics().density; mBurnInPreventionOffsetX = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetY = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_y); mDozingStackPadding = res.getDimensionPixelSize(R.dimen.dozing_stack_padding); } public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight, int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount, int clockBottom, float dark) { mMaxKeyguardNotifications = maxKeyguardNotifications; mMaxPanelHeight = maxPanelHeight; mExpandedHeight = expandedHeight; mNotificationCount = notificationCount; mHeight = height; mKeyguardStatusHeight = keyguardStatusHeight; mEmptyDragAmount = emptyDragAmount; mClockBottom = clockBottom; mDarkAmount = dark; } public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) { return mClockYFractionMin * height + keyguardStatusHeight / 2 + mClockNotificationsMarginMin; } public void run(Result result) { int y = getClockY() - mKeyguardStatusHeight / 2; float clockAdjustment = getClockYExpansionAdjustment(); float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier(); result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier); int clockNotificationsPadding = getClockNotificationsPadding() + result.stackScrollerPaddingAdjustment; int padding = y + clockNotificationsPadding; result.clockY = y; result.stackScrollerPadding = mKeyguardStatusHeight + padding; result.clockScale = getClockScale(result.stackScrollerPadding, result.clockY, y + getClockNotificationsPadding() + mKeyguardStatusHeight); result.clockAlpha = getClockAlpha(result.clockScale); result.stackScrollerPadding = (int) interpolate( result.stackScrollerPadding, mClockBottom + y + mDozingStackPadding, mDarkAmount); result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); } private float getClockScale(int notificationPadding, int clockY, int startPadding) { float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f; float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier; float distanceToScaleEnd = notificationPadding - scaleEnd; float progress = distanceToScaleEnd / (startPadding - scaleEnd); progress = Math.max(0.0f, Math.min(progress, 1.0f)); progress = mAccelerateInterpolator.getInterpolation(progress); progress *= Math.pow(1 + mEmptyDragAmount / mDensity / 300, 0.3f); return interpolate(progress, 1, mDarkAmount); } private int getClockNotificationsPadding() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax); } private float getClockYFraction() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); return (1 - t) * mClockYFractionMax + t * mClockYFractionMin; } private int getClockY() { // Dark: Align the bottom edge of the clock at one third: // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom) + burnInPreventionOffsetY(); float clockYRegular = getClockYFraction() * mHeight; return (int) interpolate(clockYRegular, clockYDark, mDarkAmount); } private float burnInPreventionOffsetY() { return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES, mBurnInPreventionOffsetY * 2, BURN_IN_PREVENTION_PERIOD_Y) - mBurnInPreventionOffsetY; } private float burnInPreventionOffsetX() { return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES, mBurnInPreventionOffsetX * 2, BURN_IN_PREVENTION_PERIOD_X) - mBurnInPreventionOffsetX; } /** * Implements a continuous, piecewise linear, periodic zig-zag function * * Can be thought of as a linear approximation of abs(sin(x))) * * @param period period of the function, ie. zigzag(x + period) == zigzag(x) * @param amplitude maximum value of the function * @return a value between 0 and amplitude */ private float zigzag(float x, float amplitude, float period) { float xprime = (x % period) / (period / 2); float interpolationAmount = (xprime <= 1) ? xprime : (2 - xprime); return interpolate(0, amplitude, interpolationAmount); } private float getClockYExpansionAdjustment() { float rubberbandFactor = getClockYExpansionRubberbandFactor(); float value = (rubberbandFactor * (mMaxPanelHeight - mExpandedHeight)); float t = value / mMaxPanelHeight; float slowedDownValue = -sSlowDownInterpolator.getInterpolation(t) * SLOW_DOWN_FACTOR * mMaxPanelHeight; if (mNotificationCount == 0) { return (-2*value + slowedDownValue)/3; } else { return slowedDownValue; } } private float getClockYExpansionRubberbandFactor() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); t = (float) Math.pow(t, 0.3f); return (1 - t) * CLOCK_RUBBERBAND_FACTOR_MAX + t * CLOCK_RUBBERBAND_FACTOR_MIN; } private float getTopPaddingAdjMultiplier() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); return (1 - t) * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN + t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX; } private float getClockAlpha(float scale) { float fadeEnd = getNotificationAmountT() == 0.0f ? CLOCK_SCALE_FADE_END_NO_NOTIFS : CLOCK_SCALE_FADE_END; float alpha = (scale - fadeEnd) / (CLOCK_SCALE_FADE_START - fadeEnd); return Math.max(0, Math.min(1, alpha)); } /** * @return a value from 0 to 1 depending on how many notification there are */ private float getNotificationAmountT() { return mNotificationCount / (mMaxKeyguardNotifications + mMoreCardNotificationAmount); } public static class Result { /** * The y translation of the clock. */ public int clockY; /** * The scale of the Clock */ public float clockScale; /** * The alpha value of the clock. */ public float clockAlpha; /** * The top padding of the stack scroller, in pixels. */ public int stackScrollerPadding; /** * The top padding adjustment of the stack scroller, in pixels. This value is used to adjust * the padding, but not the overall panel size. */ public int stackScrollerPaddingAdjustment; /** The x translation of the clock. */ public int clockX; } }