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