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