blob: f7aa818f0abf174c8d3c045beeb649273781350b [file] [log] [blame]
/*
* 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;
}
}