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