Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2012 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 android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.ValueAnimator;
     24 import android.content.Context;
     25 import android.content.res.Configuration;
     26 import android.graphics.Canvas;
     27 import android.graphics.Color;
     28 import android.graphics.Paint;
     29 import android.graphics.Rect;
     30 import android.util.AttributeSet;
     31 import android.util.MathUtils;
     32 import android.view.MotionEvent;
     33 import android.view.VelocityTracker;
     34 import android.view.View;
     35 import android.view.ViewTreeObserver;
     36 import android.view.WindowInsets;
     37 import android.view.accessibility.AccessibilityEvent;
     38 import android.view.animation.AnimationUtils;
     39 import android.view.animation.Interpolator;
     40 import android.view.animation.PathInterpolator;
     41 import android.widget.FrameLayout;
     42 import android.widget.TextView;
     43 
     44 import com.android.internal.logging.MetricsLogger;
     45 import com.android.keyguard.KeyguardStatusView;
     46 import com.android.systemui.DejankUtils;
     47 import com.android.systemui.EventLogConstants;
     48 import com.android.systemui.EventLogTags;
     49 import com.android.systemui.R;
     50 import com.android.systemui.qs.QSContainer;
     51 import com.android.systemui.qs.QSPanel;
     52 import com.android.systemui.statusbar.ExpandableNotificationRow;
     53 import com.android.systemui.statusbar.ExpandableView;
     54 import com.android.systemui.statusbar.FlingAnimationUtils;
     55 import com.android.systemui.statusbar.GestureRecorder;
     56 import com.android.systemui.statusbar.KeyguardAffordanceView;
     57 import com.android.systemui.statusbar.NotificationData;
     58 import com.android.systemui.statusbar.StatusBarState;
     59 import com.android.systemui.statusbar.policy.HeadsUpManager;
     60 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
     61 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     62 import com.android.systemui.statusbar.stack.StackStateAnimator;
     63 
     64 public class NotificationPanelView extends PanelView implements
     65         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
     66         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
     67         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
     68         HeadsUpManager.OnHeadsUpChangedListener {
     69 
     70     private static final boolean DEBUG = false;
     71 
     72     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
     73     // changed.
     74     private static final int CAP_HEIGHT = 1456;
     75     private static final int FONT_HEIGHT = 2163;
     76 
     77     private static final float HEADER_RUBBERBAND_FACTOR = 2.05f;
     78     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
     79 
     80     private static final String COUNTER_PANEL_OPEN = "panel_open";
     81     private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
     82     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
     83 
     84     private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
     85 
     86     public static final long DOZE_ANIMATION_DURATION = 700;
     87 
     88     private KeyguardAffordanceHelper mAfforanceHelper;
     89     private StatusBarHeaderView mHeader;
     90     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     91     private KeyguardStatusBarView mKeyguardStatusBar;
     92     private QSContainer mQsContainer;
     93     private QSPanel mQsPanel;
     94     private KeyguardStatusView mKeyguardStatusView;
     95     private ObservableScrollView mScrollView;
     96     private TextView mClockView;
     97     private View mReserveNotificationSpace;
     98     private View mQsNavbarScrim;
     99     private NotificationsQuickSettingsContainer mNotificationContainerParent;
    100     private NotificationStackScrollLayout mNotificationStackScroller;
    101     private int mNotificationTopPadding;
    102     private boolean mAnimateNextTopPaddingChange;
    103 
    104     private int mTrackingPointer;
    105     private VelocityTracker mVelocityTracker;
    106     private boolean mQsTracking;
    107 
    108     /**
    109      * Handles launching the secure camera properly even when other applications may be using the
    110      * camera hardware.
    111      */
    112     private SecureCameraLaunchManager mSecureCameraLaunchManager;
    113 
    114     /**
    115      * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
    116      * the expansion for quick settings.
    117      */
    118     private boolean mConflictingQsExpansionGesture;
    119 
    120     /**
    121      * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
    122      * intercepted yet.
    123      */
    124     private boolean mIntercepting;
    125     private boolean mPanelExpanded;
    126     private boolean mQsExpanded;
    127     private boolean mQsExpandedWhenExpandingStarted;
    128     private boolean mQsFullyExpanded;
    129     private boolean mKeyguardShowing;
    130     private boolean mDozing;
    131     private boolean mDozingOnDown;
    132     private int mStatusBarState;
    133     private float mInitialHeightOnTouch;
    134     private float mInitialTouchX;
    135     private float mInitialTouchY;
    136     private float mLastTouchX;
    137     private float mLastTouchY;
    138     private float mQsExpansionHeight;
    139     private int mQsMinExpansionHeight;
    140     private int mQsMaxExpansionHeight;
    141     private int mQsPeekHeight;
    142     private boolean mStackScrollerOverscrolling;
    143     private boolean mQsExpansionFromOverscroll;
    144     private float mLastOverscroll;
    145     private boolean mQsExpansionEnabled = true;
    146     private ValueAnimator mQsExpansionAnimator;
    147     private FlingAnimationUtils mFlingAnimationUtils;
    148     private int mStatusBarMinHeight;
    149     private boolean mUnlockIconActive;
    150     private int mNotificationsHeaderCollideDistance;
    151     private int mUnlockMoveDistance;
    152     private float mEmptyDragAmount;
    153 
    154     private Interpolator mFastOutSlowInInterpolator;
    155     private Interpolator mFastOutLinearInterpolator;
    156     private Interpolator mDozeAnimationInterpolator;
    157     private ObjectAnimator mClockAnimator;
    158     private int mClockAnimationTarget = -1;
    159     private int mTopPaddingAdjustment;
    160     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
    161             new KeyguardClockPositionAlgorithm();
    162     private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
    163             new KeyguardClockPositionAlgorithm.Result();
    164     private boolean mIsExpanding;
    165 
    166     private boolean mBlockTouches;
    167     private int mNotificationScrimWaitDistance;
    168     // Used for two finger gesture as well as accessibility shortcut to QS.
    169     private boolean mQsExpandImmediate;
    170     private boolean mTwoFingerQsExpandPossible;
    171 
    172     /**
    173      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
    174      * need to take this into account in our panel height calculation.
    175      */
    176     private int mScrollYOverride = -1;
    177     private boolean mQsAnimatorExpand;
    178     private boolean mIsLaunchTransitionFinished;
    179     private boolean mIsLaunchTransitionRunning;
    180     private Runnable mLaunchAnimationEndRunnable;
    181     private boolean mOnlyAffordanceInThisMotion;
    182     private boolean mKeyguardStatusViewAnimating;
    183     private boolean mHeaderAnimating;
    184     private ObjectAnimator mQsContainerAnimator;
    185     private ValueAnimator mQsSizeChangeAnimator;
    186 
    187     private boolean mShadeEmpty;
    188 
    189     private boolean mQsScrimEnabled = true;
    190     private boolean mLastAnnouncementWasQuickSettings;
    191     private boolean mQsTouchAboveFalsingThreshold;
    192     private int mQsFalsingThreshold;
    193 
    194     private float mKeyguardStatusBarAnimateAlpha = 1f;
    195     private int mOldLayoutDirection;
    196     private HeadsUpTouchHelper mHeadsUpTouchHelper;
    197     private boolean mIsExpansionFromHeadsUp;
    198     private boolean mListenForHeadsUp;
    199     private int mNavigationBarBottomHeight;
    200     private boolean mExpandingFromHeadsUp;
    201     private boolean mCollapsedOnDown;
    202     private int mPositionMinSideMargin;
    203     private int mLastOrientation = -1;
    204     private boolean mClosingWithAlphaFadeOut;
    205     private boolean mHeadsUpAnimatingAway;
    206 
    207     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
    208         @Override
    209         public void run() {
    210             mHeadsUpAnimatingAway = false;
    211             notifyBarPanelExpansionChanged();
    212         }
    213     };
    214 
    215     /** Interpolator to be used for animations that respond directly to a touch */
    216     private final Interpolator mTouchResponseInterpolator =
    217             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
    218 
    219     public NotificationPanelView(Context context, AttributeSet attrs) {
    220         super(context, attrs);
    221         setWillNotDraw(!DEBUG);
    222     }
    223 
    224     public void setStatusBar(PhoneStatusBar bar) {
    225         mStatusBar = bar;
    226     }
    227 
    228     @Override
    229     protected void onFinishInflate() {
    230         super.onFinishInflate();
    231         mHeader = (StatusBarHeaderView) findViewById(R.id.header);
    232         mHeader.setOnClickListener(this);
    233         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
    234         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
    235         mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
    236         mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
    237         mClockView = (TextView) findViewById(R.id.clock_view);
    238         mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
    239         mScrollView.setListener(this);
    240         mScrollView.setFocusable(false);
    241         mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
    242         mNotificationContainerParent = (NotificationsQuickSettingsContainer)
    243                 findViewById(R.id.notification_container_parent);
    244         mNotificationStackScroller = (NotificationStackScrollLayout)
    245                 findViewById(R.id.notification_stack_scroller);
    246         mNotificationStackScroller.setOnHeightChangedListener(this);
    247         mNotificationStackScroller.setOverscrollTopChangedListener(this);
    248         mNotificationStackScroller.setOnEmptySpaceClickListener(this);
    249         mNotificationStackScroller.setScrollView(mScrollView);
    250         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
    251                 android.R.interpolator.fast_out_slow_in);
    252         mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
    253                 android.R.interpolator.fast_out_linear_in);
    254         mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(),
    255                 android.R.interpolator.linear_out_slow_in);
    256         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
    257         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
    258         mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
    259         mSecureCameraLaunchManager =
    260                 new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
    261         mLastOrientation = getResources().getConfiguration().orientation;
    262 
    263         // recompute internal state when qspanel height changes
    264         mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
    265             @Override
    266             public void onLayoutChange(View v, int left, int top, int right, int bottom,
    267                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
    268                 final int height = bottom - top;
    269                 final int oldHeight = oldBottom - oldTop;
    270                 if (height != oldHeight) {
    271                     onScrollChanged();
    272                 }
    273             }
    274         });
    275     }
    276 
    277     @Override
    278     protected void loadDimens() {
    279         super.loadDimens();
    280         mNotificationTopPadding = getResources().getDimensionPixelSize(
    281                 R.dimen.notifications_top_padding);
    282         mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
    283         mStatusBarMinHeight = getResources().getDimensionPixelSize(
    284                 com.android.internal.R.dimen.status_bar_height);
    285         mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
    286         mNotificationsHeaderCollideDistance =
    287                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
    288         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
    289         mClockPositionAlgorithm.loadDimens(getResources());
    290         mNotificationScrimWaitDistance =
    291                 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
    292         mQsFalsingThreshold = getResources().getDimensionPixelSize(
    293                 R.dimen.qs_falsing_threshold);
    294         mPositionMinSideMargin = getResources().getDimensionPixelSize(
    295                 R.dimen.notification_panel_min_side_margin);
    296     }
    297 
    298     public void updateResources() {
    299         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
    300         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
    301         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
    302         if (lp.width != panelWidth) {
    303             lp.width = panelWidth;
    304             lp.gravity = panelGravity;
    305             mHeader.setLayoutParams(lp);
    306             mHeader.post(mUpdateHeader);
    307         }
    308 
    309         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
    310         if (lp.width != panelWidth) {
    311             lp.width = panelWidth;
    312             lp.gravity = panelGravity;
    313             mNotificationStackScroller.setLayoutParams(lp);
    314         }
    315 
    316         lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
    317         if (lp.width != panelWidth) {
    318             lp.width = panelWidth;
    319             lp.gravity = panelGravity;
    320             mScrollView.setLayoutParams(lp);
    321         }
    322     }
    323 
    324     @Override
    325     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    326         super.onLayout(changed, left, top, right, bottom);
    327 
    328         // Update Clock Pivot
    329         mKeyguardStatusView.setPivotX(getWidth() / 2);
    330         mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
    331 
    332         // Calculate quick setting heights.
    333         int oldMaxHeight = mQsMaxExpansionHeight;
    334         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
    335         mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
    336         positionClockAndNotifications();
    337         if (mQsExpanded && mQsFullyExpanded) {
    338             mQsExpansionHeight = mQsMaxExpansionHeight;
    339             requestScrollerTopPaddingUpdate(false /* animate */);
    340             requestPanelHeightUpdate();
    341 
    342             // Size has changed, start an animation.
    343             if (mQsMaxExpansionHeight != oldMaxHeight) {
    344                 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
    345             }
    346         } else if (!mQsExpanded) {
    347             setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
    348         }
    349         updateStackHeight(getExpandedHeight());
    350         updateHeader();
    351         mNotificationStackScroller.updateIsSmallScreen(
    352                 mHeader.getCollapsedHeight() + mQsPeekHeight);
    353 
    354         // If we are running a size change animation, the animation takes care of the height of
    355         // the container. However, if we are not animating, we always need to make the QS container
    356         // the desired height so when closing the QS detail, it stays smaller after the size change
    357         // animation is finished but the detail view is still being animated away (this animation
    358         // takes longer than the size change animation).
    359         if (mQsSizeChangeAnimator == null) {
    360             mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
    361         }
    362         updateMaxHeadsUpTranslation();
    363     }
    364 
    365     @Override
    366     public void onAttachedToWindow() {
    367         mSecureCameraLaunchManager.create();
    368     }
    369 
    370     @Override
    371     public void onDetachedFromWindow() {
    372         mSecureCameraLaunchManager.destroy();
    373     }
    374 
    375     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
    376         if (mQsSizeChangeAnimator != null) {
    377             oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
    378             mQsSizeChangeAnimator.cancel();
    379         }
    380         mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
    381         mQsSizeChangeAnimator.setDuration(300);
    382         mQsSizeChangeAnimator.setInterpolator(mFastOutSlowInInterpolator);
    383         mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    384             @Override
    385             public void onAnimationUpdate(ValueAnimator animation) {
    386                 requestScrollerTopPaddingUpdate(false /* animate */);
    387                 requestPanelHeightUpdate();
    388                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
    389                 mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
    390             }
    391         });
    392         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
    393             @Override
    394             public void onAnimationEnd(Animator animation) {
    395                 mQsSizeChangeAnimator = null;
    396             }
    397         });
    398         mQsSizeChangeAnimator.start();
    399     }
    400 
    401     /**
    402      * Positions the clock and notifications dynamically depending on how many notifications are
    403      * showing.
    404      */
    405     private void positionClockAndNotifications() {
    406         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
    407         int stackScrollerPadding;
    408         if (mStatusBarState != StatusBarState.KEYGUARD) {
    409             int bottom = mHeader.getCollapsedHeight();
    410             stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
    411                     ? bottom + mQsPeekHeight + mNotificationTopPadding
    412                     : mKeyguardStatusBar.getHeight() + mNotificationTopPadding;
    413             mTopPaddingAdjustment = 0;
    414         } else {
    415             mClockPositionAlgorithm.setup(
    416                     mStatusBar.getMaxKeyguardNotifications(),
    417                     getMaxPanelHeight(),
    418                     getExpandedHeight(),
    419                     mNotificationStackScroller.getNotGoneChildCount(),
    420                     getHeight(),
    421                     mKeyguardStatusView.getHeight(),
    422                     mEmptyDragAmount);
    423             mClockPositionAlgorithm.run(mClockPositionResult);
    424             if (animate || mClockAnimator != null) {
    425                 startClockAnimation(mClockPositionResult.clockY);
    426             } else {
    427                 mKeyguardStatusView.setY(mClockPositionResult.clockY);
    428             }
    429             updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
    430             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
    431             mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
    432         }
    433         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
    434         requestScrollerTopPaddingUpdate(animate);
    435     }
    436 
    437     private void startClockAnimation(int y) {
    438         if (mClockAnimationTarget == y) {
    439             return;
    440         }
    441         mClockAnimationTarget = y;
    442         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    443             @Override
    444             public boolean onPreDraw() {
    445                 getViewTreeObserver().removeOnPreDrawListener(this);
    446                 if (mClockAnimator != null) {
    447                     mClockAnimator.removeAllListeners();
    448                     mClockAnimator.cancel();
    449                 }
    450                 mClockAnimator = ObjectAnimator
    451                         .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
    452                 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
    453                 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    454                 mClockAnimator.addListener(new AnimatorListenerAdapter() {
    455                     @Override
    456                     public void onAnimationEnd(Animator animation) {
    457                         mClockAnimator = null;
    458                         mClockAnimationTarget = -1;
    459                     }
    460                 });
    461                 mClockAnimator.start();
    462                 return true;
    463             }
    464         });
    465     }
    466 
    467     private void updateClock(float alpha, float scale) {
    468         if (!mKeyguardStatusViewAnimating) {
    469             mKeyguardStatusView.setAlpha(alpha);
    470         }
    471         mKeyguardStatusView.setScaleX(scale);
    472         mKeyguardStatusView.setScaleY(scale);
    473     }
    474 
    475     public void animateToFullShade(long delay) {
    476         mAnimateNextTopPaddingChange = true;
    477         mNotificationStackScroller.goToFullShade(delay);
    478         requestLayout();
    479     }
    480 
    481     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
    482         mQsExpansionEnabled = qsExpansionEnabled;
    483         mHeader.setClickable(qsExpansionEnabled);
    484     }
    485 
    486     @Override
    487     public void resetViews() {
    488         mIsLaunchTransitionFinished = false;
    489         mBlockTouches = false;
    490         mUnlockIconActive = false;
    491         mAfforanceHelper.reset(true);
    492         closeQs();
    493         mStatusBar.dismissPopups();
    494         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
    495                 true /* cancelAnimators */);
    496         mNotificationStackScroller.resetScrollPosition();
    497     }
    498 
    499     public void closeQs() {
    500         cancelQsAnimation();
    501         setQsExpansion(mQsMinExpansionHeight);
    502     }
    503 
    504     public void animateCloseQs() {
    505         if (mQsExpansionAnimator != null) {
    506             if (!mQsAnimatorExpand) {
    507                 return;
    508             }
    509             float height = mQsExpansionHeight;
    510             mQsExpansionAnimator.cancel();
    511             setQsExpansion(height);
    512         }
    513         flingSettings(0 /* vel */, false);
    514     }
    515 
    516     public void openQs() {
    517         cancelQsAnimation();
    518         if (mQsExpansionEnabled) {
    519             setQsExpansion(mQsMaxExpansionHeight);
    520         }
    521     }
    522 
    523     public void expandWithQs() {
    524         if (mQsExpansionEnabled) {
    525             mQsExpandImmediate = true;
    526         }
    527         expand();
    528     }
    529 
    530     @Override
    531     public void fling(float vel, boolean expand) {
    532         GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
    533         if (gr != null) {
    534             gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
    535         }
    536         super.fling(vel, expand);
    537     }
    538 
    539     @Override
    540     protected void flingToHeight(float vel, boolean expand, float target,
    541             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
    542         mHeadsUpTouchHelper.notifyFling(!expand);
    543         setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f);
    544         super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
    545     }
    546 
    547     @Override
    548     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    549         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
    550             event.getText().add(getKeyguardOrLockScreenString());
    551             mLastAnnouncementWasQuickSettings = false;
    552             return true;
    553         }
    554         return super.dispatchPopulateAccessibilityEventInternal(event);
    555     }
    556 
    557     @Override
    558     public boolean onInterceptTouchEvent(MotionEvent event) {
    559         if (mBlockTouches) {
    560             return false;
    561         }
    562         initDownStates(event);
    563         if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
    564             mIsExpansionFromHeadsUp = true;
    565             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
    566             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
    567             return true;
    568         }
    569         if (!isFullyCollapsed() && onQsIntercept(event)) {
    570             return true;
    571         }
    572         return super.onInterceptTouchEvent(event);
    573     }
    574 
    575     private boolean onQsIntercept(MotionEvent event) {
    576         int pointerIndex = event.findPointerIndex(mTrackingPointer);
    577         if (pointerIndex < 0) {
    578             pointerIndex = 0;
    579             mTrackingPointer = event.getPointerId(pointerIndex);
    580         }
    581         final float x = event.getX(pointerIndex);
    582         final float y = event.getY(pointerIndex);
    583 
    584         switch (event.getActionMasked()) {
    585             case MotionEvent.ACTION_DOWN:
    586                 mIntercepting = true;
    587                 mInitialTouchY = y;
    588                 mInitialTouchX = x;
    589                 initVelocityTracker();
    590                 trackMovement(event);
    591                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
    592                     getParent().requestDisallowInterceptTouchEvent(true);
    593                 }
    594                 if (mQsExpansionAnimator != null) {
    595                     onQsExpansionStarted();
    596                     mInitialHeightOnTouch = mQsExpansionHeight;
    597                     mQsTracking = true;
    598                     mIntercepting = false;
    599                     mNotificationStackScroller.removeLongPressCallback();
    600                 }
    601                 break;
    602             case MotionEvent.ACTION_POINTER_UP:
    603                 final int upPointer = event.getPointerId(event.getActionIndex());
    604                 if (mTrackingPointer == upPointer) {
    605                     // gesture is ongoing, find a new pointer to track
    606                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
    607                     mTrackingPointer = event.getPointerId(newIndex);
    608                     mInitialTouchX = event.getX(newIndex);
    609                     mInitialTouchY = event.getY(newIndex);
    610                 }
    611                 break;
    612 
    613             case MotionEvent.ACTION_MOVE:
    614                 final float h = y - mInitialTouchY;
    615                 trackMovement(event);
    616                 if (mQsTracking) {
    617 
    618                     // Already tracking because onOverscrolled was called. We need to update here
    619                     // so we don't stop for a frame until the next touch event gets handled in
    620                     // onTouchEvent.
    621                     setQsExpansion(h + mInitialHeightOnTouch);
    622                     trackMovement(event);
    623                     mIntercepting = false;
    624                     return true;
    625                 }
    626                 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
    627                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
    628                     mQsTracking = true;
    629                     onQsExpansionStarted();
    630                     notifyExpandingFinished();
    631                     mInitialHeightOnTouch = mQsExpansionHeight;
    632                     mInitialTouchY = y;
    633                     mInitialTouchX = x;
    634                     mIntercepting = false;
    635                     mNotificationStackScroller.removeLongPressCallback();
    636                     return true;
    637                 }
    638                 break;
    639 
    640             case MotionEvent.ACTION_CANCEL:
    641             case MotionEvent.ACTION_UP:
    642                 trackMovement(event);
    643                 if (mQsTracking) {
    644                     flingQsWithCurrentVelocity(y,
    645                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
    646                     mQsTracking = false;
    647                 }
    648                 mIntercepting = false;
    649                 break;
    650         }
    651         return false;
    652     }
    653 
    654     @Override
    655     protected boolean isInContentBounds(float x, float y) {
    656         float stackScrollerX = mNotificationStackScroller.getX();
    657         return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
    658                 && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
    659     }
    660 
    661     private void initDownStates(MotionEvent event) {
    662         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
    663             mOnlyAffordanceInThisMotion = false;
    664             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
    665             mDozingOnDown = isDozing();
    666             mCollapsedOnDown = isFullyCollapsed();
    667             mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
    668         }
    669     }
    670 
    671     @Override
    672     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    673 
    674         // Block request when interacting with the scroll view so we can still intercept the
    675         // scrolling when QS is expanded.
    676         if (mScrollView.isHandlingTouchEvent()) {
    677             return;
    678         }
    679         super.requestDisallowInterceptTouchEvent(disallowIntercept);
    680     }
    681 
    682     private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
    683         float vel = getCurrentVelocity();
    684         final boolean expandsQs = flingExpandsQs(vel);
    685         if (expandsQs) {
    686             logQsSwipeDown(y);
    687         }
    688         flingSettings(vel, expandsQs && !isCancelMotionEvent);
    689     }
    690 
    691     private void logQsSwipeDown(float y) {
    692         float vel = getCurrentVelocity();
    693         final int gesture = mStatusBarState == StatusBarState.KEYGUARD
    694                 ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS
    695                 : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS;
    696         EventLogTags.writeSysuiLockscreenGesture(
    697                 gesture,
    698                 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
    699                 (int) (vel / mStatusBar.getDisplayDensity()));
    700     }
    701 
    702     private boolean flingExpandsQs(float vel) {
    703         if (isBelowFalsingThreshold()) {
    704             return false;
    705         }
    706         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
    707             return getQsExpansionFraction() > 0.5f;
    708         } else {
    709             return vel > 0;
    710         }
    711     }
    712 
    713     private boolean isBelowFalsingThreshold() {
    714         return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD;
    715     }
    716 
    717     private float getQsExpansionFraction() {
    718         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
    719                 / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
    720     }
    721 
    722     @Override
    723     public boolean onTouchEvent(MotionEvent event) {
    724         if (mBlockTouches) {
    725             return false;
    726         }
    727         initDownStates(event);
    728         if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
    729                 && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
    730             mIsExpansionFromHeadsUp = true;
    731             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
    732         }
    733         if ((!mIsExpanding || mHintAnimationRunning)
    734                 && !mQsExpanded
    735                 && mStatusBar.getBarState() != StatusBarState.SHADE) {
    736             mAfforanceHelper.onTouchEvent(event);
    737         }
    738         if (mOnlyAffordanceInThisMotion) {
    739             return true;
    740         }
    741         mHeadsUpTouchHelper.onTouchEvent(event);
    742         if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
    743             return true;
    744         }
    745         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
    746             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
    747             updateVerticalPanelPosition(event.getX());
    748         }
    749         super.onTouchEvent(event);
    750         return true;
    751     }
    752 
    753     private boolean handleQsTouch(MotionEvent event) {
    754         final int action = event.getActionMasked();
    755         if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
    756                 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
    757                 && mQsExpansionEnabled) {
    758 
    759             // Down in the empty area while fully expanded - go to QS.
    760             mQsTracking = true;
    761             mConflictingQsExpansionGesture = true;
    762             onQsExpansionStarted();
    763             mInitialHeightOnTouch = mQsExpansionHeight;
    764             mInitialTouchY = event.getX();
    765             mInitialTouchX = event.getY();
    766         }
    767         if (!isFullyCollapsed()) {
    768             handleQsDown(event);
    769         }
    770         if (!mQsExpandImmediate && mQsTracking) {
    771             onQsTouch(event);
    772             if (!mConflictingQsExpansionGesture) {
    773                 return true;
    774             }
    775         }
    776         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
    777             mConflictingQsExpansionGesture = false;
    778         }
    779         if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
    780                 && mQsExpansionEnabled) {
    781             mTwoFingerQsExpandPossible = true;
    782         }
    783         if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
    784                 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
    785             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
    786             mQsExpandImmediate = true;
    787             requestPanelHeightUpdate();
    788 
    789             // Normally, we start listening when the panel is expanded, but here we need to start
    790             // earlier so the state is already up to date when dragging down.
    791             setListening(true);
    792         }
    793         return false;
    794     }
    795 
    796     private boolean isInQsArea(float x, float y) {
    797         return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) &&
    798                 (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
    799                 || y <= mQsContainer.getY() + mQsContainer.getHeight());
    800     }
    801 
    802     private boolean isOpenQsEvent(MotionEvent event) {
    803         final int pointerCount = event.getPointerCount();
    804         final int action = event.getActionMasked();
    805 
    806         final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
    807                 && pointerCount == 2;
    808 
    809         final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
    810                 && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
    811                         || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
    812 
    813         final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
    814                 && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
    815                         || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
    816 
    817         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
    818     }
    819 
    820     private void handleQsDown(MotionEvent event) {
    821         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
    822                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
    823             mQsTracking = true;
    824             onQsExpansionStarted();
    825             mInitialHeightOnTouch = mQsExpansionHeight;
    826             mInitialTouchY = event.getX();
    827             mInitialTouchX = event.getY();
    828 
    829             // If we interrupt an expansion gesture here, make sure to update the state correctly.
    830             notifyExpandingFinished();
    831         }
    832     }
    833 
    834     @Override
    835     protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
    836         boolean expands = super.flingExpands(vel, vectorVel, x, y);
    837 
    838         // If we are already running a QS expansion, make sure that we keep the panel open.
    839         if (mQsExpansionAnimator != null) {
    840             expands = true;
    841         }
    842         return expands;
    843     }
    844 
    845     @Override
    846     protected boolean hasConflictingGestures() {
    847         return mStatusBar.getBarState() != StatusBarState.SHADE;
    848     }
    849 
    850     @Override
    851     protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
    852         return !mAfforanceHelper.isOnAffordanceIcon(x, y);
    853     }
    854 
    855     private void onQsTouch(MotionEvent event) {
    856         int pointerIndex = event.findPointerIndex(mTrackingPointer);
    857         if (pointerIndex < 0) {
    858             pointerIndex = 0;
    859             mTrackingPointer = event.getPointerId(pointerIndex);
    860         }
    861         final float y = event.getY(pointerIndex);
    862         final float x = event.getX(pointerIndex);
    863         final float h = y - mInitialTouchY;
    864 
    865         switch (event.getActionMasked()) {
    866             case MotionEvent.ACTION_DOWN:
    867                 mQsTracking = true;
    868                 mInitialTouchY = y;
    869                 mInitialTouchX = x;
    870                 onQsExpansionStarted();
    871                 mInitialHeightOnTouch = mQsExpansionHeight;
    872                 initVelocityTracker();
    873                 trackMovement(event);
    874                 break;
    875 
    876             case MotionEvent.ACTION_POINTER_UP:
    877                 final int upPointer = event.getPointerId(event.getActionIndex());
    878                 if (mTrackingPointer == upPointer) {
    879                     // gesture is ongoing, find a new pointer to track
    880                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
    881                     final float newY = event.getY(newIndex);
    882                     final float newX = event.getX(newIndex);
    883                     mTrackingPointer = event.getPointerId(newIndex);
    884                     mInitialHeightOnTouch = mQsExpansionHeight;
    885                     mInitialTouchY = newY;
    886                     mInitialTouchX = newX;
    887                 }
    888                 break;
    889 
    890             case MotionEvent.ACTION_MOVE:
    891                 setQsExpansion(h + mInitialHeightOnTouch);
    892                 if (h >= getFalsingThreshold()) {
    893                     mQsTouchAboveFalsingThreshold = true;
    894                 }
    895                 trackMovement(event);
    896                 break;
    897 
    898             case MotionEvent.ACTION_UP:
    899             case MotionEvent.ACTION_CANCEL:
    900                 mQsTracking = false;
    901                 mTrackingPointer = -1;
    902                 trackMovement(event);
    903                 float fraction = getQsExpansionFraction();
    904                 if ((fraction != 0f || y >= mInitialTouchY)
    905                         && (fraction != 1f || y <= mInitialTouchY)) {
    906                     flingQsWithCurrentVelocity(y,
    907                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
    908                 } else {
    909                     logQsSwipeDown(y);
    910                     mScrollYOverride = -1;
    911                 }
    912                 if (mVelocityTracker != null) {
    913                     mVelocityTracker.recycle();
    914                     mVelocityTracker = null;
    915                 }
    916                 break;
    917         }
    918     }
    919 
    920     private int getFalsingThreshold() {
    921         float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
    922         return (int) (mQsFalsingThreshold * factor);
    923     }
    924 
    925     @Override
    926     public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
    927         if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
    928                 -1 /* yDiff: Not relevant here */)) {
    929             mQsTracking = true;
    930             onQsExpansionStarted(amount);
    931             mInitialHeightOnTouch = mQsExpansionHeight;
    932             mInitialTouchY = mLastTouchY;
    933             mInitialTouchX = mLastTouchX;
    934         }
    935     }
    936 
    937     @Override
    938     public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
    939         cancelQsAnimation();
    940         if (!mQsExpansionEnabled) {
    941             amount = 0f;
    942         }
    943         float rounded = amount >= 1f ? amount : 0f;
    944         mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
    945         mQsExpansionFromOverscroll = rounded != 0f;
    946         mLastOverscroll = rounded;
    947         updateQsState();
    948         setQsExpansion(mQsMinExpansionHeight + rounded);
    949     }
    950 
    951     @Override
    952     public void flingTopOverscroll(float velocity, boolean open) {
    953         mLastOverscroll = 0f;
    954         setQsExpansion(mQsExpansionHeight);
    955         flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
    956                 new Runnable() {
    957                     @Override
    958                     public void run() {
    959                         mStackScrollerOverscrolling = false;
    960                         mQsExpansionFromOverscroll = false;
    961                         updateQsState();
    962                     }
    963                 }, false /* isClick */);
    964     }
    965 
    966     private void onQsExpansionStarted() {
    967         onQsExpansionStarted(0);
    968     }
    969 
    970     private void onQsExpansionStarted(int overscrollAmount) {
    971         cancelQsAnimation();
    972         cancelHeightAnimator();
    973 
    974         // Reset scroll position and apply that position to the expanded height.
    975         float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
    976         if (mScrollView.getScrollY() != 0) {
    977             mScrollYOverride = mScrollView.getScrollY();
    978         }
    979         mScrollView.scrollTo(0, 0);
    980         setQsExpansion(height);
    981         requestPanelHeightUpdate();
    982     }
    983 
    984     private void setQsExpanded(boolean expanded) {
    985         boolean changed = mQsExpanded != expanded;
    986         if (changed) {
    987             mQsExpanded = expanded;
    988             updateQsState();
    989             requestPanelHeightUpdate();
    990             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
    991             mStatusBar.setQsExpanded(expanded);
    992             mQsPanel.setExpanded(expanded);
    993             mNotificationContainerParent.setQsExpanded(expanded);
    994         }
    995     }
    996 
    997     public void setBarState(int statusBarState, boolean keyguardFadingAway,
    998             boolean goingToFullShade) {
    999         int oldState = mStatusBarState;
   1000         boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
   1001         setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
   1002         setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
   1003 
   1004         mStatusBarState = statusBarState;
   1005         mKeyguardShowing = keyguardShowing;
   1006 
   1007         if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
   1008                 && statusBarState == StatusBarState.SHADE_LOCKED)) {
   1009             animateKeyguardStatusBarOut();
   1010             animateHeaderSlidingIn();
   1011         } else if (oldState == StatusBarState.SHADE_LOCKED
   1012                 && statusBarState == StatusBarState.KEYGUARD) {
   1013             animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
   1014             animateHeaderSlidingOut();
   1015         } else {
   1016             mKeyguardStatusBar.setAlpha(1f);
   1017             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
   1018             if (keyguardShowing && oldState != mStatusBarState) {
   1019                 mKeyguardBottomArea.updateLeftAffordance();
   1020                 mAfforanceHelper.updatePreviews();
   1021             }
   1022         }
   1023         if (keyguardShowing) {
   1024             updateDozingVisibilities(false /* animate */);
   1025         }
   1026         resetVerticalPanelPosition();
   1027         updateQsState();
   1028     }
   1029 
   1030     private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
   1031         @Override
   1032         public void run() {
   1033             mKeyguardStatusViewAnimating = false;
   1034             mKeyguardStatusView.setVisibility(View.GONE);
   1035         }
   1036     };
   1037 
   1038     private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
   1039         @Override
   1040         public void run() {
   1041             mKeyguardStatusViewAnimating = false;
   1042         }
   1043     };
   1044 
   1045     private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
   1046             = new AnimatorListenerAdapter() {
   1047         @Override
   1048         public void onAnimationEnd(Animator animation) {
   1049             mHeaderAnimating = false;
   1050             mQsContainerAnimator = null;
   1051             mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
   1052         }
   1053     };
   1054 
   1055     private final OnLayoutChangeListener mQsContainerAnimatorUpdater
   1056             = new OnLayoutChangeListener() {
   1057         @Override
   1058         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
   1059                 int oldTop, int oldRight, int oldBottom) {
   1060             int oldHeight = oldBottom - oldTop;
   1061             int height = bottom - top;
   1062             if (height != oldHeight && mQsContainerAnimator != null) {
   1063                 PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
   1064                 float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
   1065                 float newStartValue = -height - top;
   1066                 values[0].setFloatValues(newStartValue, newEndValue);
   1067                 mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
   1068             }
   1069         }
   1070     };
   1071 
   1072     private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
   1073             = new ViewTreeObserver.OnPreDrawListener() {
   1074         @Override
   1075         public boolean onPreDraw() {
   1076             getViewTreeObserver().removeOnPreDrawListener(this);
   1077             long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
   1078                     ? 0
   1079                     : mStatusBar.calculateGoingToFullShadeDelay();
   1080             mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
   1081             mHeader.animate()
   1082                     .translationY(0f)
   1083                     .setStartDelay(delay)
   1084                     .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
   1085                     .setInterpolator(mFastOutSlowInInterpolator)
   1086                     .start();
   1087             mQsContainer.setY(-mQsContainer.getHeight());
   1088             mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
   1089                     mQsContainer.getTranslationY(),
   1090                     mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
   1091                             - mQsContainer.getTop());
   1092             mQsContainerAnimator.setStartDelay(delay);
   1093             mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
   1094             mQsContainerAnimator.setInterpolator(mFastOutSlowInInterpolator);
   1095             mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
   1096             mQsContainerAnimator.start();
   1097             mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
   1098             return true;
   1099         }
   1100     };
   1101 
   1102     private void animateHeaderSlidingIn() {
   1103         // If the QS is already expanded we don't need to slide in the header as it's already
   1104         // visible.
   1105         if (!mQsExpanded) {
   1106             mHeaderAnimating = true;
   1107             getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
   1108         }
   1109     }
   1110 
   1111     private void animateHeaderSlidingOut() {
   1112         mHeaderAnimating = true;
   1113         mHeader.animate().y(-mHeader.getHeight())
   1114                 .setStartDelay(0)
   1115                 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
   1116                 .setInterpolator(mFastOutSlowInInterpolator)
   1117                 .setListener(new AnimatorListenerAdapter() {
   1118                     @Override
   1119                     public void onAnimationEnd(Animator animation) {
   1120                         mHeader.animate().setListener(null);
   1121                         mHeaderAnimating = false;
   1122                         updateQsState();
   1123                     }
   1124                 })
   1125                 .start();
   1126         mQsContainer.animate()
   1127                 .y(-mQsContainer.getHeight())
   1128                 .setStartDelay(0)
   1129                 .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
   1130                 .setInterpolator(mFastOutSlowInInterpolator)
   1131                 .start();
   1132     }
   1133 
   1134     private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
   1135         @Override
   1136         public void run() {
   1137             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
   1138             mKeyguardStatusBar.setAlpha(1f);
   1139             mKeyguardStatusBarAnimateAlpha = 1f;
   1140         }
   1141     };
   1142 
   1143     private void animateKeyguardStatusBarOut() {
   1144         ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
   1145         anim.addUpdateListener(mStatusBarAnimateAlphaListener);
   1146         anim.setStartDelay(mStatusBar.isKeyguardFadingAway()
   1147                 ? mStatusBar.getKeyguardFadingAwayDelay()
   1148                 : 0);
   1149         anim.setDuration(mStatusBar.isKeyguardFadingAway()
   1150                 ? mStatusBar.getKeyguardFadingAwayDuration() / 2
   1151                 : StackStateAnimator.ANIMATION_DURATION_STANDARD);
   1152         anim.setInterpolator(mDozeAnimationInterpolator);
   1153         anim.addListener(new AnimatorListenerAdapter() {
   1154             @Override
   1155             public void onAnimationEnd(Animator animation) {
   1156                 mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
   1157             }
   1158         });
   1159         anim.start();
   1160     }
   1161 
   1162     private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
   1163             new ValueAnimator.AnimatorUpdateListener() {
   1164         @Override
   1165         public void onAnimationUpdate(ValueAnimator animation) {
   1166             mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
   1167             updateHeaderKeyguardAlpha();
   1168         }
   1169     };
   1170 
   1171     private void animateKeyguardStatusBarIn(long duration) {
   1172         mKeyguardStatusBar.setVisibility(View.VISIBLE);
   1173         mKeyguardStatusBar.setAlpha(0f);
   1174         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
   1175         anim.addUpdateListener(mStatusBarAnimateAlphaListener);
   1176         anim.setDuration(duration);
   1177         anim.setInterpolator(mDozeAnimationInterpolator);
   1178         anim.start();
   1179     }
   1180 
   1181     private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
   1182         @Override
   1183         public void run() {
   1184             mKeyguardBottomArea.setVisibility(View.GONE);
   1185         }
   1186     };
   1187 
   1188     private void setKeyguardBottomAreaVisibility(int statusBarState,
   1189             boolean goingToFullShade) {
   1190         if (goingToFullShade) {
   1191             mKeyguardBottomArea.animate().cancel();
   1192             mKeyguardBottomArea.animate()
   1193                     .alpha(0f)
   1194                     .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
   1195                     .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
   1196                     .setInterpolator(PhoneStatusBar.ALPHA_OUT)
   1197                     .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
   1198                     .start();
   1199         } else if (statusBarState == StatusBarState.KEYGUARD
   1200                 || statusBarState == StatusBarState.SHADE_LOCKED) {
   1201             mKeyguardBottomArea.animate().cancel();
   1202             if (!mDozing) {
   1203                 mKeyguardBottomArea.setVisibility(View.VISIBLE);
   1204             }
   1205             mKeyguardBottomArea.setAlpha(1f);
   1206         } else {
   1207             mKeyguardBottomArea.animate().cancel();
   1208             mKeyguardBottomArea.setVisibility(View.GONE);
   1209             mKeyguardBottomArea.setAlpha(1f);
   1210         }
   1211     }
   1212 
   1213     private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
   1214             boolean goingToFullShade) {
   1215         if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
   1216                 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
   1217             mKeyguardStatusView.animate().cancel();
   1218             mKeyguardStatusViewAnimating = true;
   1219             mKeyguardStatusView.animate()
   1220                     .alpha(0f)
   1221                     .setStartDelay(0)
   1222                     .setDuration(160)
   1223                     .setInterpolator(PhoneStatusBar.ALPHA_OUT)
   1224                     .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
   1225             if (keyguardFadingAway) {
   1226                 mKeyguardStatusView.animate()
   1227                         .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
   1228                         .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
   1229                         .start();
   1230             }
   1231         } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
   1232                 && statusBarState == StatusBarState.KEYGUARD) {
   1233             mKeyguardStatusView.animate().cancel();
   1234             mKeyguardStatusView.setVisibility(View.VISIBLE);
   1235             mKeyguardStatusViewAnimating = true;
   1236             mKeyguardStatusView.setAlpha(0f);
   1237             mKeyguardStatusView.animate()
   1238                     .alpha(1f)
   1239                     .setStartDelay(0)
   1240                     .setDuration(320)
   1241                     .setInterpolator(PhoneStatusBar.ALPHA_IN)
   1242                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
   1243         } else if (statusBarState == StatusBarState.KEYGUARD) {
   1244             mKeyguardStatusView.animate().cancel();
   1245             mKeyguardStatusViewAnimating = false;
   1246             mKeyguardStatusView.setVisibility(View.VISIBLE);
   1247             mKeyguardStatusView.setAlpha(1f);
   1248         } else {
   1249             mKeyguardStatusView.animate().cancel();
   1250             mKeyguardStatusViewAnimating = false;
   1251             mKeyguardStatusView.setVisibility(View.GONE);
   1252             mKeyguardStatusView.setAlpha(1f);
   1253         }
   1254     }
   1255 
   1256     private void updateQsState() {
   1257         boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
   1258         mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
   1259                 ? View.VISIBLE
   1260                 : View.INVISIBLE);
   1261         mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
   1262                 || (mQsExpanded && !mStackScrollerOverscrolling));
   1263         mNotificationStackScroller.setScrollingEnabled(
   1264                 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
   1265                         || mQsExpansionFromOverscroll));
   1266         mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
   1267         mQsContainer.setVisibility(
   1268                 mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
   1269         mScrollView.setTouchEnabled(mQsExpanded);
   1270         updateEmptyShadeView();
   1271         mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
   1272                 && !mStackScrollerOverscrolling && mQsScrimEnabled
   1273                         ? View.VISIBLE
   1274                         : View.INVISIBLE);
   1275         if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
   1276             mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
   1277         }
   1278     }
   1279 
   1280     private void setQsExpansion(float height) {
   1281         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
   1282         mQsFullyExpanded = height == mQsMaxExpansionHeight;
   1283         if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
   1284             setQsExpanded(true);
   1285         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
   1286             setQsExpanded(false);
   1287             if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
   1288                 announceForAccessibility(getKeyguardOrLockScreenString());
   1289                 mLastAnnouncementWasQuickSettings = false;
   1290             }
   1291         }
   1292         mQsExpansionHeight = height;
   1293         mHeader.setExpansion(getHeaderExpansionFraction());
   1294         setQsTranslation(height);
   1295         requestScrollerTopPaddingUpdate(false /* animate */);
   1296         updateNotificationScrim(height);
   1297         if (mKeyguardShowing) {
   1298             updateHeaderKeyguard();
   1299         }
   1300         if (mStatusBarState == StatusBarState.SHADE_LOCKED
   1301                 || mStatusBarState == StatusBarState.KEYGUARD) {
   1302             updateKeyguardBottomAreaAlpha();
   1303         }
   1304         if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
   1305                 && !mStackScrollerOverscrolling && mQsScrimEnabled) {
   1306             mQsNavbarScrim.setAlpha(getQsExpansionFraction());
   1307         }
   1308 
   1309         // Upon initialisation when we are not layouted yet we don't want to announce that we are
   1310         // fully expanded, hence the != 0.0f check.
   1311         if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
   1312             announceForAccessibility(getContext().getString(
   1313                     R.string.accessibility_desc_quick_settings));
   1314             mLastAnnouncementWasQuickSettings = true;
   1315         }
   1316         if (DEBUG) {
   1317             invalidate();
   1318         }
   1319     }
   1320 
   1321     private String getKeyguardOrLockScreenString() {
   1322         if (mStatusBarState == StatusBarState.KEYGUARD) {
   1323             return getContext().getString(R.string.accessibility_desc_lock_screen);
   1324         } else {
   1325             return getContext().getString(R.string.accessibility_desc_notification_shade);
   1326         }
   1327     }
   1328 
   1329     private void updateNotificationScrim(float height) {
   1330         int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
   1331         float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
   1332         progress = Math.max(0.0f, Math.min(progress, 1.0f));
   1333     }
   1334 
   1335     private float getHeaderExpansionFraction() {
   1336         if (!mKeyguardShowing) {
   1337             return getQsExpansionFraction();
   1338         } else {
   1339             return 1f;
   1340         }
   1341     }
   1342 
   1343     private void setQsTranslation(float height) {
   1344         if (!mHeaderAnimating) {
   1345             mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
   1346         }
   1347         if (mKeyguardShowing && !mHeaderAnimating) {
   1348             mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
   1349         }
   1350     }
   1351 
   1352     private float calculateQsTopPadding() {
   1353         if (mKeyguardShowing
   1354                 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
   1355 
   1356             // Either QS pushes the notifications down when fully expanded, or QS is fully above the
   1357             // notifications (mostly on tablets). maxNotifications denotes the normal top padding
   1358             // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to
   1359             // take the maximum and linearly interpolate with the panel expansion for a nice motion.
   1360             int maxNotifications = mClockPositionResult.stackScrollerPadding
   1361                     - mClockPositionResult.stackScrollerPaddingAdjustment
   1362                     - mNotificationTopPadding;
   1363             int maxQs = getTempQsMaxExpansion();
   1364             int max = mStatusBarState == StatusBarState.KEYGUARD
   1365                     ? Math.max(maxNotifications, maxQs)
   1366                     : maxQs;
   1367             return (int) interpolate(getExpandedFraction(),
   1368                     mQsMinExpansionHeight, max);
   1369         } else if (mQsSizeChangeAnimator != null) {
   1370             return (int) mQsSizeChangeAnimator.getAnimatedValue();
   1371         } else if (mKeyguardShowing && mScrollYOverride == -1) {
   1372 
   1373             // We can only do the smoother transition on Keyguard when we also are not collapsing
   1374             // from a scrolled quick settings.
   1375             return interpolate(getQsExpansionFraction(),
   1376                     mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
   1377                     mQsMaxExpansionHeight);
   1378         } else {
   1379             return mQsExpansionHeight;
   1380         }
   1381     }
   1382 
   1383     private void requestScrollerTopPaddingUpdate(boolean animate) {
   1384         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
   1385                 mScrollView.getScrollY(),
   1386                 mAnimateNextTopPaddingChange || animate,
   1387                 mKeyguardShowing
   1388                         && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
   1389         mAnimateNextTopPaddingChange = false;
   1390     }
   1391 
   1392     private void trackMovement(MotionEvent event) {
   1393         if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
   1394         mLastTouchX = event.getX();
   1395         mLastTouchY = event.getY();
   1396     }
   1397 
   1398     private void initVelocityTracker() {
   1399         if (mVelocityTracker != null) {
   1400             mVelocityTracker.recycle();
   1401         }
   1402         mVelocityTracker = VelocityTracker.obtain();
   1403     }
   1404 
   1405     private float getCurrentVelocity() {
   1406         if (mVelocityTracker == null) {
   1407             return 0;
   1408         }
   1409         mVelocityTracker.computeCurrentVelocity(1000);
   1410         return mVelocityTracker.getYVelocity();
   1411     }
   1412 
   1413     private void cancelQsAnimation() {
   1414         if (mQsExpansionAnimator != null) {
   1415             mQsExpansionAnimator.cancel();
   1416         }
   1417     }
   1418 
   1419     private void flingSettings(float vel, boolean expand) {
   1420         flingSettings(vel, expand, null, false /* isClick */);
   1421     }
   1422 
   1423     private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable,
   1424             boolean isClick) {
   1425         float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
   1426         if (target == mQsExpansionHeight) {
   1427             mScrollYOverride = -1;
   1428             if (onFinishRunnable != null) {
   1429                 onFinishRunnable.run();
   1430             }
   1431             return;
   1432         }
   1433         boolean belowFalsingThreshold = isBelowFalsingThreshold();
   1434         if (belowFalsingThreshold) {
   1435             vel = 0;
   1436         }
   1437         mScrollView.setBlockFlinging(true);
   1438         ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
   1439         if (isClick) {
   1440             animator.setInterpolator(mTouchResponseInterpolator);
   1441             animator.setDuration(368);
   1442         } else {
   1443             mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
   1444         }
   1445         if (belowFalsingThreshold) {
   1446             animator.setDuration(350);
   1447         }
   1448         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   1449             @Override
   1450             public void onAnimationUpdate(ValueAnimator animation) {
   1451                 setQsExpansion((Float) animation.getAnimatedValue());
   1452             }
   1453         });
   1454         animator.addListener(new AnimatorListenerAdapter() {
   1455             @Override
   1456             public void onAnimationEnd(Animator animation) {
   1457                 mScrollView.setBlockFlinging(false);
   1458                 mScrollYOverride = -1;
   1459                 mQsExpansionAnimator = null;
   1460                 if (onFinishRunnable != null) {
   1461                     onFinishRunnable.run();
   1462                 }
   1463             }
   1464         });
   1465         animator.start();
   1466         mQsExpansionAnimator = animator;
   1467         mQsAnimatorExpand = expand;
   1468     }
   1469 
   1470     /**
   1471      * @return Whether we should intercept a gesture to open Quick Settings.
   1472      */
   1473     private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
   1474         if (!mQsExpansionEnabled || mCollapsedOnDown) {
   1475             return false;
   1476         }
   1477         View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
   1478         boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
   1479                 && y >= header.getTop() && y <= header.getBottom();
   1480         if (mQsExpanded) {
   1481             return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
   1482         } else {
   1483             return onHeader;
   1484         }
   1485     }
   1486 
   1487     @Override
   1488     protected boolean isScrolledToBottom() {
   1489         if (!isInSettings()) {
   1490             return mStatusBar.getBarState() == StatusBarState.KEYGUARD
   1491                     || mNotificationStackScroller.isScrolledToBottom();
   1492         } else {
   1493             return mScrollView.isScrolledToBottom();
   1494         }
   1495     }
   1496 
   1497     @Override
   1498     protected int getMaxPanelHeight() {
   1499         int min = mStatusBarMinHeight;
   1500         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
   1501                 && mNotificationStackScroller.getNotGoneChildCount() == 0) {
   1502             int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
   1503                     * HEADER_RUBBERBAND_FACTOR);
   1504             min = Math.max(min, minHeight);
   1505         }
   1506         int maxHeight;
   1507         if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
   1508             maxHeight = calculatePanelHeightQsExpanded();
   1509         } else {
   1510             maxHeight = calculatePanelHeightShade();
   1511         }
   1512         maxHeight = Math.max(maxHeight, min);
   1513         return maxHeight;
   1514     }
   1515 
   1516     private boolean isInSettings() {
   1517         return mQsExpanded;
   1518     }
   1519 
   1520     @Override
   1521     protected void onHeightUpdated(float expandedHeight) {
   1522         if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
   1523             positionClockAndNotifications();
   1524         }
   1525         if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
   1526                 && !mQsExpansionFromOverscroll) {
   1527             float t;
   1528             if (mKeyguardShowing) {
   1529 
   1530                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
   1531                 t = expandedHeight / getMaxPanelHeight();
   1532             } else {
   1533 
   1534                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
   1535                 // minimum QS expansion + minStackHeight
   1536                 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
   1537                         + mNotificationStackScroller.getMinStackHeight();
   1538                 float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
   1539                 t = (expandedHeight - panelHeightQsCollapsed)
   1540                         / (panelHeightQsExpanded - panelHeightQsCollapsed);
   1541             }
   1542             setQsExpansion(mQsMinExpansionHeight
   1543                     + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
   1544         }
   1545         updateStackHeight(expandedHeight);
   1546         updateHeader();
   1547         updateUnlockIcon();
   1548         updateNotificationTranslucency();
   1549         updatePanelExpanded();
   1550         mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed());
   1551         if (DEBUG) {
   1552             invalidate();
   1553         }
   1554     }
   1555 
   1556     private void updatePanelExpanded() {
   1557         boolean isExpanded = !isFullyCollapsed();
   1558         if (mPanelExpanded != isExpanded) {
   1559             mHeadsUpManager.setIsExpanded(isExpanded);
   1560             mStatusBar.setPanelExpanded(isExpanded);
   1561             mPanelExpanded = isExpanded;
   1562         }
   1563     }
   1564 
   1565     /**
   1566      * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
   1567      *         collapsing QS / the panel when QS was scrolled
   1568      */
   1569     private int getTempQsMaxExpansion() {
   1570         int qsTempMaxExpansion = mQsMaxExpansionHeight;
   1571         if (mScrollYOverride != -1) {
   1572             qsTempMaxExpansion -= mScrollYOverride;
   1573         }
   1574         return qsTempMaxExpansion;
   1575     }
   1576 
   1577     private int calculatePanelHeightShade() {
   1578         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
   1579         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
   1580                 - mTopPaddingAdjustment;
   1581         maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
   1582         return maxHeight;
   1583     }
   1584 
   1585     private int calculatePanelHeightQsExpanded() {
   1586         float notificationHeight = mNotificationStackScroller.getHeight()
   1587                 - mNotificationStackScroller.getEmptyBottomMargin()
   1588                 - mNotificationStackScroller.getTopPadding();
   1589 
   1590         // When only empty shade view is visible in QS collapsed state, simulate that we would have
   1591         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
   1592         // and expanding/collapsing the whole panel from/to quick settings.
   1593         if (mNotificationStackScroller.getNotGoneChildCount() == 0
   1594                 && mShadeEmpty) {
   1595             notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight()
   1596                     + mNotificationStackScroller.getBottomStackPeekSize()
   1597                     + mNotificationStackScroller.getCollapseSecondCardPadding();
   1598         }
   1599         int maxQsHeight = mQsMaxExpansionHeight;
   1600 
   1601         // If an animation is changing the size of the QS panel, take the animated value.
   1602         if (mQsSizeChangeAnimator != null) {
   1603             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
   1604         }
   1605         float totalHeight = Math.max(
   1606                 maxQsHeight + mNotificationStackScroller.getNotificationTopPadding(),
   1607                 mStatusBarState == StatusBarState.KEYGUARD
   1608                         ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment
   1609                         : 0)
   1610                 + notificationHeight;
   1611         if (totalHeight > mNotificationStackScroller.getHeight()) {
   1612             float fullyCollapsedHeight = maxQsHeight
   1613                     + mNotificationStackScroller.getMinStackHeight()
   1614                     + mNotificationStackScroller.getNotificationTopPadding()
   1615                     - getScrollViewScrollY();
   1616             totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
   1617         }
   1618         return (int) totalHeight;
   1619     }
   1620 
   1621     private int getScrollViewScrollY() {
   1622         if (mScrollYOverride != -1 && !mQsTracking) {
   1623             return mScrollYOverride;
   1624         } else {
   1625             return mScrollView.getScrollY();
   1626         }
   1627     }
   1628     private void updateNotificationTranslucency() {
   1629         float alpha = 1f;
   1630         if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
   1631             alpha = getFadeoutAlpha();
   1632         }
   1633         mNotificationStackScroller.setAlpha(alpha);
   1634     }
   1635 
   1636     private float getFadeoutAlpha() {
   1637         float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
   1638                 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
   1639                 - mNotificationStackScroller.getCollapseSecondCardPadding());
   1640         alpha = Math.max(0, Math.min(alpha, 1));
   1641         alpha = (float) Math.pow(alpha, 0.75);
   1642         return alpha;
   1643     }
   1644 
   1645     @Override
   1646     protected float getOverExpansionAmount() {
   1647         return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
   1648     }
   1649 
   1650     @Override
   1651     protected float getOverExpansionPixels() {
   1652         return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
   1653     }
   1654 
   1655     private void updateUnlockIcon() {
   1656         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
   1657                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
   1658             boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
   1659             KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
   1660             if (active && !mUnlockIconActive && mTracking) {
   1661                 lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
   1662                 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
   1663                         mFastOutLinearInterpolator);
   1664             } else if (!active && mUnlockIconActive && mTracking) {
   1665                 lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */,
   1666                         150, mFastOutLinearInterpolator, null);
   1667                 lockIcon.setImageScale(1.0f, true, 150,
   1668                         mFastOutLinearInterpolator);
   1669             }
   1670             mUnlockIconActive = active;
   1671         }
   1672     }
   1673 
   1674     /**
   1675      * Hides the header when notifications are colliding with it.
   1676      */
   1677     private void updateHeader() {
   1678         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
   1679             updateHeaderKeyguard();
   1680         } else {
   1681             updateHeaderShade();
   1682         }
   1683 
   1684     }
   1685 
   1686     private void updateHeaderShade() {
   1687         if (!mHeaderAnimating) {
   1688             mHeader.setTranslationY(getHeaderTranslation());
   1689         }
   1690         setQsTranslation(mQsExpansionHeight);
   1691     }
   1692 
   1693     private float getHeaderTranslation() {
   1694         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
   1695             return 0;
   1696         }
   1697         if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
   1698             if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
   1699                 return 0;
   1700             } else {
   1701                 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
   1702             }
   1703         }
   1704         float stackTranslation = mNotificationStackScroller.getStackTranslation();
   1705         float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR;
   1706         if (mHeadsUpManager.hasPinnedHeadsUp() || mIsExpansionFromHeadsUp) {
   1707             translation = mNotificationStackScroller.getTopPadding() + stackTranslation
   1708                     - mNotificationTopPadding - mQsMinExpansionHeight;
   1709         }
   1710         return Math.min(0, translation);
   1711     }
   1712 
   1713     /**
   1714      * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
   1715      *         during swiping up
   1716      */
   1717     private float getKeyguardContentsAlpha() {
   1718         float alpha;
   1719         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
   1720 
   1721             // When on Keyguard, we hide the header as soon as the top card of the notification
   1722             // stack scroller is close enough (collision distance) to the bottom of the header.
   1723             alpha = getNotificationsTopY()
   1724                     /
   1725                     (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
   1726         } else {
   1727 
   1728             // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
   1729             // soon as we start translating the stack.
   1730             alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight();
   1731         }
   1732         alpha = MathUtils.constrain(alpha, 0, 1);
   1733         alpha = (float) Math.pow(alpha, 0.75);
   1734         return alpha;
   1735     }
   1736 
   1737     private void updateHeaderKeyguardAlpha() {
   1738         float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
   1739         mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
   1740                 * mKeyguardStatusBarAnimateAlpha);
   1741         mKeyguardStatusBar.setVisibility(mKeyguardStatusBar.getAlpha() != 0f
   1742                 && !mDozing ? VISIBLE : INVISIBLE);
   1743     }
   1744 
   1745     private void updateHeaderKeyguard() {
   1746         updateHeaderKeyguardAlpha();
   1747         setQsTranslation(mQsExpansionHeight);
   1748     }
   1749 
   1750     private void updateKeyguardBottomAreaAlpha() {
   1751         float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
   1752         mKeyguardBottomArea.setAlpha(alpha);
   1753         mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
   1754                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
   1755                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
   1756     }
   1757 
   1758     private float getNotificationsTopY() {
   1759         if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
   1760             return getExpandedHeight();
   1761         }
   1762         return mNotificationStackScroller.getNotificationsTopY();
   1763     }
   1764 
   1765     @Override
   1766     protected void onExpandingStarted() {
   1767         super.onExpandingStarted();
   1768         mNotificationStackScroller.onExpansionStarted();
   1769         mIsExpanding = true;
   1770         mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
   1771         if (mQsExpanded) {
   1772             onQsExpansionStarted();
   1773         }
   1774     }
   1775 
   1776     @Override
   1777     protected void onExpandingFinished() {
   1778         super.onExpandingFinished();
   1779         mNotificationStackScroller.onExpansionStopped();
   1780         mHeadsUpManager.onExpandingFinished();
   1781         mIsExpanding = false;
   1782         mScrollYOverride = -1;
   1783         if (isFullyCollapsed()) {
   1784             DejankUtils.postAfterTraversal(new Runnable() {
   1785                 @Override
   1786                 public void run() {
   1787                     setListening(false);
   1788                 }
   1789             });
   1790 
   1791             // Workaround b/22639032: Make sure we invalidate something because else RenderThread
   1792             // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
   1793             // ahead with rendering and we jank.
   1794             postOnAnimation(new Runnable() {
   1795                 @Override
   1796                 public void run() {
   1797                     getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
   1798                 }
   1799             });
   1800         } else {
   1801             setListening(true);
   1802         }
   1803         mQsExpandImmediate = false;
   1804         mTwoFingerQsExpandPossible = false;
   1805         mIsExpansionFromHeadsUp = false;
   1806         mNotificationStackScroller.setTrackingHeadsUp(false);
   1807         mExpandingFromHeadsUp = false;
   1808         setPanelScrimMinFraction(0.0f);
   1809     }
   1810 
   1811     private void setListening(boolean listening) {
   1812         mHeader.setListening(listening);
   1813         mKeyguardStatusBar.setListening(listening);
   1814         mQsPanel.setListening(listening);
   1815     }
   1816 
   1817     @Override
   1818     public void instantExpand() {
   1819         super.instantExpand();
   1820         setListening(true);
   1821     }
   1822 
   1823     @Override
   1824     protected void setOverExpansion(float overExpansion, boolean isPixels) {
   1825         if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
   1826             return;
   1827         }
   1828         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
   1829             mNotificationStackScroller.setOnHeightChangedListener(null);
   1830             if (isPixels) {
   1831                 mNotificationStackScroller.setOverScrolledPixels(
   1832                         overExpansion, true /* onTop */, false /* animate */);
   1833             } else {
   1834                 mNotificationStackScroller.setOverScrollAmount(
   1835                         overExpansion, true /* onTop */, false /* animate */);
   1836             }
   1837             mNotificationStackScroller.setOnHeightChangedListener(this);
   1838         }
   1839     }
   1840 
   1841     @Override
   1842     protected void onTrackingStarted() {
   1843         super.onTrackingStarted();
   1844         if (mQsFullyExpanded) {
   1845             mQsExpandImmediate = true;
   1846         }
   1847         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
   1848                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
   1849             mAfforanceHelper.animateHideLeftRightIcon();
   1850         }
   1851         mNotificationStackScroller.onPanelTrackingStarted();
   1852     }
   1853 
   1854     @Override
   1855     protected void onTrackingStopped(boolean expand) {
   1856         super.onTrackingStopped(expand);
   1857         if (expand) {
   1858             mNotificationStackScroller.setOverScrolledPixels(
   1859                     0.0f, true /* onTop */, true /* animate */);
   1860         }
   1861         mNotificationStackScroller.onPanelTrackingStopped();
   1862         if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
   1863                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
   1864             if (!mHintAnimationRunning) {
   1865                 mAfforanceHelper.reset(true);
   1866             }
   1867         }
   1868         if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
   1869                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
   1870             KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
   1871             lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
   1872             lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
   1873         }
   1874     }
   1875 
   1876     @Override
   1877     public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
   1878 
   1879         // Block update if we are in quick settings and just the top padding changed
   1880         // (i.e. view == null).
   1881         if (view == null && mQsExpanded) {
   1882             return;
   1883         }
   1884         requestPanelHeightUpdate();
   1885     }
   1886 
   1887     @Override
   1888     public void onReset(ExpandableView view) {
   1889     }
   1890 
   1891     @Override
   1892     public void onScrollChanged() {
   1893         if (mQsExpanded) {
   1894             requestScrollerTopPaddingUpdate(false /* animate */);
   1895             requestPanelHeightUpdate();
   1896         }
   1897     }
   1898 
   1899     @Override
   1900     protected void onConfigurationChanged(Configuration newConfig) {
   1901         super.onConfigurationChanged(newConfig);
   1902         mAfforanceHelper.onConfigurationChanged();
   1903         if (newConfig.orientation != mLastOrientation) {
   1904             resetVerticalPanelPosition();
   1905         }
   1906         mLastOrientation = newConfig.orientation;
   1907     }
   1908 
   1909     @Override
   1910     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
   1911         mNavigationBarBottomHeight = insets.getSystemWindowInsetBottom();
   1912         updateMaxHeadsUpTranslation();
   1913         return insets;
   1914     }
   1915 
   1916     private void updateMaxHeadsUpTranslation() {
   1917         mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
   1918     }
   1919 
   1920     @Override
   1921     public void onRtlPropertiesChanged(int layoutDirection) {
   1922         if (layoutDirection != mOldLayoutDirection) {
   1923             mAfforanceHelper.onRtlPropertiesChanged();
   1924             mOldLayoutDirection = layoutDirection;
   1925         }
   1926     }
   1927 
   1928     @Override
   1929     public void onClick(View v) {
   1930         if (v == mHeader) {
   1931             onQsExpansionStarted();
   1932             if (mQsExpanded) {
   1933                 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
   1934             } else if (mQsExpansionEnabled) {
   1935                 EventLogTags.writeSysuiLockscreenGesture(
   1936                         EventLogConstants.SYSUI_TAP_TO_OPEN_QS,
   1937                         0, 0);
   1938                 flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
   1939             }
   1940         }
   1941     }
   1942 
   1943     @Override
   1944     public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
   1945         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
   1946         mIsLaunchTransitionRunning = true;
   1947         mLaunchAnimationEndRunnable = null;
   1948         float displayDensity = mStatusBar.getDisplayDensity();
   1949         int lengthDp = Math.abs((int) (translation / displayDensity));
   1950         int velocityDp = Math.abs((int) (vel / displayDensity));
   1951         if (start) {
   1952             EventLogTags.writeSysuiLockscreenGesture(
   1953                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
   1954             mKeyguardBottomArea.launchLeftAffordance();
   1955         } else {
   1956             EventLogTags.writeSysuiLockscreenGesture(
   1957                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
   1958             mSecureCameraLaunchManager.startSecureCameraLaunch();
   1959         }
   1960         mStatusBar.startLaunchTransitionTimeout();
   1961         mBlockTouches = true;
   1962     }
   1963 
   1964     @Override
   1965     public void onAnimationToSideEnded() {
   1966         mIsLaunchTransitionRunning = false;
   1967         mIsLaunchTransitionFinished = true;
   1968         if (mLaunchAnimationEndRunnable != null) {
   1969             mLaunchAnimationEndRunnable.run();
   1970             mLaunchAnimationEndRunnable = null;
   1971         }
   1972     }
   1973 
   1974     @Override
   1975     protected void startUnlockHintAnimation() {
   1976         super.startUnlockHintAnimation();
   1977         startHighlightIconAnimation(getCenterIcon());
   1978     }
   1979 
   1980     /**
   1981      * Starts the highlight (making it fully opaque) animation on an icon.
   1982      */
   1983     private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
   1984         icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
   1985                 mFastOutSlowInInterpolator, new Runnable() {
   1986                     @Override
   1987                     public void run() {
   1988                         icon.setImageAlpha(icon.getRestingAlpha(),
   1989                                 true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
   1990                                 mFastOutSlowInInterpolator, null);
   1991                     }
   1992                 });
   1993     }
   1994 
   1995     @Override
   1996     public float getMaxTranslationDistance() {
   1997         return (float) Math.hypot(getWidth(), getHeight());
   1998     }
   1999 
   2000     @Override
   2001     public void onSwipingStarted(boolean rightIcon) {
   2002         boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
   2003                 : rightIcon;
   2004         if (camera) {
   2005             mSecureCameraLaunchManager.onSwipingStarted();
   2006             mKeyguardBottomArea.bindCameraPrewarmService();
   2007         }
   2008         requestDisallowInterceptTouchEvent(true);
   2009         mOnlyAffordanceInThisMotion = true;
   2010         mQsTracking = false;
   2011     }
   2012 
   2013     @Override
   2014     public void onSwipingAborted() {
   2015         mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
   2016     }
   2017 
   2018     @Override
   2019     public void onIconClicked(boolean rightIcon) {
   2020         if (mHintAnimationRunning) {
   2021             return;
   2022         }
   2023         mHintAnimationRunning = true;
   2024         mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
   2025             @Override
   2026             public void run() {
   2027                 mHintAnimationRunning = false;
   2028                 mStatusBar.onHintFinished();
   2029             }
   2030         });
   2031         rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
   2032         if (rightIcon) {
   2033             mStatusBar.onCameraHintStarted();
   2034         } else {
   2035             if (mKeyguardBottomArea.isLeftVoiceAssist()) {
   2036                 mStatusBar.onVoiceAssistHintStarted();
   2037             } else {
   2038                 mStatusBar.onPhoneHintStarted();
   2039             }
   2040         }
   2041     }
   2042 
   2043     @Override
   2044     public KeyguardAffordanceView getLeftIcon() {
   2045         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
   2046                 ? mKeyguardBottomArea.getRightView()
   2047                 : mKeyguardBottomArea.getLeftView();
   2048     }
   2049 
   2050     @Override
   2051     public KeyguardAffordanceView getCenterIcon() {
   2052         return mKeyguardBottomArea.getLockIcon();
   2053     }
   2054 
   2055     @Override
   2056     public KeyguardAffordanceView getRightIcon() {
   2057         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
   2058                 ? mKeyguardBottomArea.getLeftView()
   2059                 : mKeyguardBottomArea.getRightView();
   2060     }
   2061 
   2062     @Override
   2063     public View getLeftPreview() {
   2064         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
   2065                 ? mKeyguardBottomArea.getRightPreview()
   2066                 : mKeyguardBottomArea.getLeftPreview();
   2067     }
   2068 
   2069     @Override
   2070     public View getRightPreview() {
   2071         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
   2072                 ? mKeyguardBottomArea.getLeftPreview()
   2073                 : mKeyguardBottomArea.getRightPreview();
   2074     }
   2075 
   2076     @Override
   2077     public float getAffordanceFalsingFactor() {
   2078         return mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
   2079     }
   2080 
   2081     @Override
   2082     protected float getPeekHeight() {
   2083         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
   2084             return mNotificationStackScroller.getPeekHeight();
   2085         } else {
   2086             return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
   2087         }
   2088     }
   2089 
   2090     @Override
   2091     protected float getCannedFlingDurationFactor() {
   2092         if (mQsExpanded) {
   2093             return 0.7f;
   2094         } else {
   2095             return 0.6f;
   2096         }
   2097     }
   2098 
   2099     @Override
   2100     protected boolean fullyExpandedClearAllVisible() {
   2101         return mNotificationStackScroller.isDismissViewNotGone()
   2102                 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
   2103     }
   2104 
   2105     @Override
   2106     protected boolean isClearAllVisible() {
   2107         return mNotificationStackScroller.isDismissViewVisible();
   2108     }
   2109 
   2110     @Override
   2111     protected int getClearAllHeight() {
   2112         return mNotificationStackScroller.getDismissViewHeight();
   2113     }
   2114 
   2115     @Override
   2116     protected boolean isTrackingBlocked() {
   2117         return mConflictingQsExpansionGesture && mQsExpanded;
   2118     }
   2119 
   2120     public void notifyVisibleChildrenChanged() {
   2121         if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
   2122             mReserveNotificationSpace.setVisibility(View.VISIBLE);
   2123         } else {
   2124             mReserveNotificationSpace.setVisibility(View.GONE);
   2125         }
   2126     }
   2127 
   2128     public boolean isQsExpanded() {
   2129         return mQsExpanded;
   2130     }
   2131 
   2132     public boolean isQsDetailShowing() {
   2133         return mQsPanel.isShowingDetail();
   2134     }
   2135 
   2136     public void closeQsDetail() {
   2137         mQsPanel.closeDetail();
   2138     }
   2139 
   2140     @Override
   2141     public boolean shouldDelayChildPressedState() {
   2142         return true;
   2143     }
   2144 
   2145     public boolean isLaunchTransitionFinished() {
   2146         return mIsLaunchTransitionFinished;
   2147     }
   2148 
   2149     public boolean isLaunchTransitionRunning() {
   2150         return mIsLaunchTransitionRunning;
   2151     }
   2152 
   2153     public void setLaunchTransitionEndRunnable(Runnable r) {
   2154         mLaunchAnimationEndRunnable = r;
   2155     }
   2156 
   2157     public void setEmptyDragAmount(float amount) {
   2158         float factor = 0.8f;
   2159         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
   2160             factor = 0.4f;
   2161         } else if (!mStatusBar.hasActiveNotifications()) {
   2162             factor = 0.4f;
   2163         }
   2164         mEmptyDragAmount = amount * factor;
   2165         positionClockAndNotifications();
   2166     }
   2167 
   2168     private static float interpolate(float t, float start, float end) {
   2169         return (1 - t) * start + t * end;
   2170     }
   2171 
   2172     public void setDozing(boolean dozing, boolean animate) {
   2173         if (dozing == mDozing) return;
   2174         mDozing = dozing;
   2175         if (mStatusBarState == StatusBarState.KEYGUARD) {
   2176             updateDozingVisibilities(animate);
   2177         }
   2178     }
   2179 
   2180     private void updateDozingVisibilities(boolean animate) {
   2181         if (mDozing) {
   2182             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
   2183             mKeyguardBottomArea.setVisibility(View.INVISIBLE);
   2184         } else {
   2185             mKeyguardBottomArea.setVisibility(View.VISIBLE);
   2186             mKeyguardStatusBar.setVisibility(View.VISIBLE);
   2187             if (animate) {
   2188                 animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
   2189                 mKeyguardBottomArea.startFinishDozeAnimation();
   2190             }
   2191         }
   2192     }
   2193 
   2194     @Override
   2195     public boolean isDozing() {
   2196         return mDozing;
   2197     }
   2198 
   2199     public void setShadeEmpty(boolean shadeEmpty) {
   2200         mShadeEmpty = shadeEmpty;
   2201         updateEmptyShadeView();
   2202     }
   2203 
   2204     private void updateEmptyShadeView() {
   2205 
   2206         // Hide "No notifications" in QS.
   2207         mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
   2208     }
   2209 
   2210     public void setQsScrimEnabled(boolean qsScrimEnabled) {
   2211         boolean changed = mQsScrimEnabled != qsScrimEnabled;
   2212         mQsScrimEnabled = qsScrimEnabled;
   2213         if (changed) {
   2214             updateQsState();
   2215         }
   2216     }
   2217 
   2218     public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
   2219         mKeyguardUserSwitcher = keyguardUserSwitcher;
   2220     }
   2221 
   2222     private final Runnable mUpdateHeader = new Runnable() {
   2223         @Override
   2224         public void run() {
   2225             mHeader.updateEverything();
   2226         }
   2227     };
   2228 
   2229     public void onScreenTurningOn() {
   2230         mKeyguardStatusView.refreshTime();
   2231     }
   2232 
   2233     @Override
   2234     public void onEmptySpaceClicked(float x, float y) {
   2235         onEmptySpaceClick(x);
   2236     }
   2237 
   2238     protected boolean onMiddleClicked() {
   2239         switch (mStatusBar.getBarState()) {
   2240             case StatusBarState.KEYGUARD:
   2241                 if (!mDozingOnDown) {
   2242                     EventLogTags.writeSysuiLockscreenGesture(
   2243                             EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
   2244                             0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
   2245                     startUnlockHintAnimation();
   2246                 }
   2247                 return true;
   2248             case StatusBarState.SHADE_LOCKED:
   2249                 if (!mQsExpanded) {
   2250                     mStatusBar.goToKeyguard();
   2251                 }
   2252                 return true;
   2253             case StatusBarState.SHADE:
   2254 
   2255                 // This gets called in the middle of the touch handling, where the state is still
   2256                 // that we are tracking the panel. Collapse the panel after this is done.
   2257                 post(mPostCollapseRunnable);
   2258                 return false;
   2259             default:
   2260                 return true;
   2261         }
   2262     }
   2263 
   2264     @Override
   2265     protected void dispatchDraw(Canvas canvas) {
   2266         super.dispatchDraw(canvas);
   2267         if (DEBUG) {
   2268             Paint p = new Paint();
   2269             p.setColor(Color.RED);
   2270             p.setStrokeWidth(2);
   2271             p.setStyle(Paint.Style.STROKE);
   2272             canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
   2273             p.setColor(Color.BLUE);
   2274             canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
   2275             p.setColor(Color.GREEN);
   2276             canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
   2277                     calculatePanelHeightQsExpanded(), p);
   2278             p.setColor(Color.YELLOW);
   2279             canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
   2280                     calculatePanelHeightShade(), p);
   2281             p.setColor(Color.MAGENTA);
   2282             canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
   2283                     calculateQsTopPadding(), p);
   2284             p.setColor(Color.CYAN);
   2285             canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
   2286                     mNotificationStackScroller.getTopPadding(), p);
   2287         }
   2288     }
   2289 
   2290     @Override
   2291     public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
   2292         if (inPinnedMode) {
   2293             mHeadsUpExistenceChangedRunnable.run();
   2294             updateNotificationTranslucency();
   2295         } else {
   2296             mHeadsUpAnimatingAway = true;
   2297             mNotificationStackScroller.runAfterAnimationFinished(
   2298                     mHeadsUpExistenceChangedRunnable);
   2299         }
   2300     }
   2301 
   2302     @Override
   2303     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
   2304         mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
   2305     }
   2306 
   2307     @Override
   2308     public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
   2309     }
   2310 
   2311     @Override
   2312     public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
   2313         mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
   2314     }
   2315 
   2316     @Override
   2317     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
   2318         super.setHeadsUpManager(headsUpManager);
   2319         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
   2320                 this);
   2321     }
   2322 
   2323     public void setTrackingHeadsUp(boolean tracking) {
   2324         if (tracking) {
   2325             mNotificationStackScroller.setTrackingHeadsUp(true);
   2326             mExpandingFromHeadsUp = true;
   2327         }
   2328         // otherwise we update the state when the expansion is finished
   2329     }
   2330 
   2331     @Override
   2332     protected void onClosingFinished() {
   2333         super.onClosingFinished();
   2334         resetVerticalPanelPosition();
   2335         setClosingWithAlphaFadeout(false);
   2336     }
   2337 
   2338     private void setClosingWithAlphaFadeout(boolean closing) {
   2339         mClosingWithAlphaFadeOut = closing;
   2340         mNotificationStackScroller.forceNoOverlappingRendering(closing);
   2341     }
   2342 
   2343     /**
   2344      * Updates the vertical position of the panel so it is positioned closer to the touch
   2345      * responsible for opening the panel.
   2346      *
   2347      * @param x the x-coordinate the touch event
   2348      */
   2349     private void updateVerticalPanelPosition(float x) {
   2350         if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
   2351             resetVerticalPanelPosition();
   2352             return;
   2353         }
   2354         float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
   2355         float rightMost = getWidth() - mPositionMinSideMargin
   2356                 - mNotificationStackScroller.getWidth() / 2;
   2357         if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
   2358             x = getWidth() / 2;
   2359         }
   2360         x = Math.min(rightMost, Math.max(leftMost, x));
   2361         setVerticalPanelTranslation(x -
   2362                 (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
   2363      }
   2364 
   2365     private void resetVerticalPanelPosition() {
   2366         setVerticalPanelTranslation(0f);
   2367     }
   2368 
   2369     private void setVerticalPanelTranslation(float translation) {
   2370         mNotificationStackScroller.setTranslationX(translation);
   2371         mScrollView.setTranslationX(translation);
   2372         mHeader.setTranslationX(translation);
   2373     }
   2374 
   2375     private void updateStackHeight(float stackHeight) {
   2376         mNotificationStackScroller.setStackHeight(stackHeight);
   2377         updateKeyguardBottomAreaAlpha();
   2378     }
   2379 
   2380     public void setPanelScrimMinFraction(float minFraction) {
   2381         mBar.panelScrimMinFractionChanged(minFraction);
   2382     }
   2383 
   2384     public void clearNotificattonEffects() {
   2385         mStatusBar.clearNotificationEffects();
   2386     }
   2387 
   2388     protected boolean isPanelVisibleBecauseOfHeadsUp() {
   2389         return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
   2390     }
   2391 }
   2392