Home | History | Annotate | Download | only in phone
      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.statusbar.notification.NotificationUtils.interpolate;
     20 
     21 import android.content.res.Resources;
     22 import android.graphics.Path;
     23 import android.view.animation.AccelerateInterpolator;
     24 import android.view.animation.PathInterpolator;
     25 
     26 import com.android.systemui.R;
     27 
     28 /**
     29  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
     30  */
     31 public class KeyguardClockPositionAlgorithm {
     32 
     33     private static final float SLOW_DOWN_FACTOR = 0.4f;
     34 
     35     private static final float CLOCK_RUBBERBAND_FACTOR_MIN = 0.08f;
     36     private static final float CLOCK_RUBBERBAND_FACTOR_MAX = 0.8f;
     37     private static final float CLOCK_SCALE_FADE_START = 0.95f;
     38     private static final float CLOCK_SCALE_FADE_END = 0.75f;
     39     private static final float CLOCK_SCALE_FADE_END_NO_NOTIFS = 0.5f;
     40 
     41     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
     42     private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
     43 
     44     private static final long MILLIS_PER_MINUTES = 1000 * 60;
     45     private static final float BURN_IN_PREVENTION_PERIOD_Y = 521;
     46     private static final float BURN_IN_PREVENTION_PERIOD_X = 83;
     47 
     48     private int mClockNotificationsMarginMin;
     49     private int mClockNotificationsMarginMax;
     50     private float mClockYFractionMin;
     51     private float mClockYFractionMax;
     52     private int mMaxKeyguardNotifications;
     53     private int mMaxPanelHeight;
     54     private float mExpandedHeight;
     55     private int mNotificationCount;
     56     private int mHeight;
     57     private int mKeyguardStatusHeight;
     58     private float mEmptyDragAmount;
     59     private float mDensity;
     60     private int mBurnInPreventionOffsetX;
     61     private int mBurnInPreventionOffsetY;
     62 
     63     /**
     64      * The number (fractional) of notifications the "more" card counts when calculating how many
     65      * notifications are currently visible for the y positioning of the clock.
     66      */
     67     private float mMoreCardNotificationAmount;
     68 
     69     private static final PathInterpolator sSlowDownInterpolator;
     70 
     71     static {
     72         Path path = new Path();
     73         path.moveTo(0, 0);
     74         path.cubicTo(0.3f, 0.875f, 0.6f, 1f, 1f, 1f);
     75         sSlowDownInterpolator = new PathInterpolator(path);
     76     }
     77 
     78     private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
     79     private int mClockBottom;
     80     private float mDarkAmount;
     81     private int mDozingStackPadding;
     82 
     83     /**
     84      * Refreshes the dimension values.
     85      */
     86     public void loadDimens(Resources res) {
     87         mClockNotificationsMarginMin = res.getDimensionPixelSize(
     88                 R.dimen.keyguard_clock_notifications_margin_min);
     89         mClockNotificationsMarginMax = res.getDimensionPixelSize(
     90                 R.dimen.keyguard_clock_notifications_margin_max);
     91         mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
     92         mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
     93         mMoreCardNotificationAmount =
     94                 (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) /
     95                         res.getDimensionPixelSize(R.dimen.notification_min_height);
     96         mDensity = res.getDisplayMetrics().density;
     97         mBurnInPreventionOffsetX = res.getDimensionPixelSize(
     98                 R.dimen.burn_in_prevention_offset_x);
     99         mBurnInPreventionOffsetY = res.getDimensionPixelSize(
    100                 R.dimen.burn_in_prevention_offset_y);
    101         mDozingStackPadding = res.getDimensionPixelSize(R.dimen.dozing_stack_padding);
    102     }
    103 
    104     public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
    105             int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount,
    106             int clockBottom, float dark) {
    107         mMaxKeyguardNotifications = maxKeyguardNotifications;
    108         mMaxPanelHeight = maxPanelHeight;
    109         mExpandedHeight = expandedHeight;
    110         mNotificationCount = notificationCount;
    111         mHeight = height;
    112         mKeyguardStatusHeight = keyguardStatusHeight;
    113         mEmptyDragAmount = emptyDragAmount;
    114         mClockBottom = clockBottom;
    115         mDarkAmount = dark;
    116     }
    117 
    118     public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
    119         return mClockYFractionMin * height + keyguardStatusHeight / 2
    120                 + mClockNotificationsMarginMin;
    121     }
    122 
    123     public void run(Result result) {
    124         int y = getClockY() - mKeyguardStatusHeight / 2;
    125         float clockAdjustment = getClockYExpansionAdjustment();
    126         float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier();
    127         result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier);
    128         int clockNotificationsPadding = getClockNotificationsPadding()
    129                 + result.stackScrollerPaddingAdjustment;
    130         int padding = y + clockNotificationsPadding;
    131         result.clockY = y;
    132         result.stackScrollerPadding = mKeyguardStatusHeight + padding;
    133         result.clockScale = getClockScale(result.stackScrollerPadding,
    134                 result.clockY,
    135                 y + getClockNotificationsPadding() + mKeyguardStatusHeight);
    136         result.clockAlpha = getClockAlpha(result.clockScale);
    137 
    138         result.stackScrollerPadding = (int) interpolate(
    139                 result.stackScrollerPadding,
    140                 mClockBottom + y + mDozingStackPadding,
    141                 mDarkAmount);
    142 
    143         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
    144     }
    145 
    146     private float getClockScale(int notificationPadding, int clockY, int startPadding) {
    147         float scaleMultiplier = getNotificationAmountT() == 0 ? 6.0f : 5.0f;
    148         float scaleEnd = clockY - mKeyguardStatusHeight * scaleMultiplier;
    149         float distanceToScaleEnd = notificationPadding - scaleEnd;
    150         float progress = distanceToScaleEnd / (startPadding - scaleEnd);
    151         progress = Math.max(0.0f, Math.min(progress, 1.0f));
    152         progress = mAccelerateInterpolator.getInterpolation(progress);
    153         progress *= Math.pow(1 + mEmptyDragAmount / mDensity / 300, 0.3f);
    154         return interpolate(progress, 1, mDarkAmount);
    155     }
    156 
    157     private int getClockNotificationsPadding() {
    158         float t = getNotificationAmountT();
    159         t = Math.min(t, 1.0f);
    160         return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax);
    161     }
    162 
    163     private float getClockYFraction() {
    164         float t = getNotificationAmountT();
    165         t = Math.min(t, 1.0f);
    166         return (1 - t) * mClockYFractionMax + t * mClockYFractionMin;
    167     }
    168 
    169     private int getClockY() {
    170         // Dark: Align the bottom edge of the clock at one third:
    171         // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom
    172         float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom)
    173                 + burnInPreventionOffsetY();
    174         float clockYRegular = getClockYFraction() * mHeight;
    175         return (int) interpolate(clockYRegular, clockYDark, mDarkAmount);
    176     }
    177 
    178     private float burnInPreventionOffsetY() {
    179         return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES,
    180                 mBurnInPreventionOffsetY * 2,
    181                 BURN_IN_PREVENTION_PERIOD_Y)
    182                 - mBurnInPreventionOffsetY;
    183     }
    184 
    185     private float burnInPreventionOffsetX() {
    186         return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES,
    187                 mBurnInPreventionOffsetX * 2,
    188                 BURN_IN_PREVENTION_PERIOD_X)
    189                 - mBurnInPreventionOffsetX;
    190     }
    191 
    192     /**
    193      * Implements a continuous, piecewise linear, periodic zig-zag function
    194      *
    195      * Can be thought of as a linear approximation of abs(sin(x)))
    196      *
    197      * @param period period of the function, ie. zigzag(x + period) == zigzag(x)
    198      * @param amplitude maximum value of the function
    199      * @return a value between 0 and amplitude
    200      */
    201     private float zigzag(float x, float amplitude, float period) {
    202         float xprime = (x % period) / (period / 2);
    203         float interpolationAmount = (xprime <= 1) ? xprime : (2 - xprime);
    204         return interpolate(0, amplitude, interpolationAmount);
    205     }
    206 
    207     private float getClockYExpansionAdjustment() {
    208         float rubberbandFactor = getClockYExpansionRubberbandFactor();
    209         float value = (rubberbandFactor * (mMaxPanelHeight - mExpandedHeight));
    210         float t = value / mMaxPanelHeight;
    211         float slowedDownValue = -sSlowDownInterpolator.getInterpolation(t) * SLOW_DOWN_FACTOR
    212                 * mMaxPanelHeight;
    213         if (mNotificationCount == 0) {
    214             return (-2*value + slowedDownValue)/3;
    215         } else {
    216             return slowedDownValue;
    217         }
    218     }
    219 
    220     private float getClockYExpansionRubberbandFactor() {
    221         float t = getNotificationAmountT();
    222         t = Math.min(t, 1.0f);
    223         t = (float) Math.pow(t, 0.3f);
    224         return (1 - t) * CLOCK_RUBBERBAND_FACTOR_MAX + t * CLOCK_RUBBERBAND_FACTOR_MIN;
    225     }
    226 
    227     private float getTopPaddingAdjMultiplier() {
    228         float t = getNotificationAmountT();
    229         t = Math.min(t, 1.0f);
    230         return (1 - t) * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN
    231                 + t * CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX;
    232     }
    233 
    234     private float getClockAlpha(float scale) {
    235         float fadeEnd = getNotificationAmountT() == 0.0f
    236                 ? CLOCK_SCALE_FADE_END_NO_NOTIFS
    237                 : CLOCK_SCALE_FADE_END;
    238         float alpha = (scale - fadeEnd)
    239                 / (CLOCK_SCALE_FADE_START - fadeEnd);
    240         return Math.max(0, Math.min(1, alpha));
    241     }
    242 
    243     /**
    244      * @return a value from 0 to 1 depending on how many notification there are
    245      */
    246     private float getNotificationAmountT() {
    247         return mNotificationCount
    248                 / (mMaxKeyguardNotifications + mMoreCardNotificationAmount);
    249     }
    250 
    251     public static class Result {
    252 
    253         /**
    254          * The y translation of the clock.
    255          */
    256         public int clockY;
    257 
    258         /**
    259          * The scale of the Clock
    260          */
    261         public float clockScale;
    262 
    263         /**
    264          * The alpha value of the clock.
    265          */
    266         public float clockAlpha;
    267 
    268         /**
    269          * The top padding of the stack scroller, in pixels.
    270          */
    271         public int stackScrollerPadding;
    272 
    273         /**
    274          * The top padding adjustment of the stack scroller, in pixels. This value is used to adjust
    275          * the padding, but not the overall panel size.
    276          */
    277         public int stackScrollerPaddingAdjustment;
    278 
    279         /** The x translation of the clock. */
    280         public int clockX;
    281     }
    282 }
    283