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