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