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