OSDN Git Service

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