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