• 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.policy.OnHeadsUpChangedListener;
65 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
66 import com.android.systemui.statusbar.stack.StackStateAnimator;
67 
68 import java.util.List;
69 
70 public class NotificationPanelView extends PanelView implements
71         ExpandableView.OnHeightChangedListener,
72         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
73         KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
74         OnHeadsUpChangedListener {
75 
76     private static final boolean DEBUG = false;
77 
78     // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
79     // changed.
80     private static final int CAP_HEIGHT = 1456;
81     private static final int FONT_HEIGHT = 2163;
82 
83     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
84 
85     static final String COUNTER_PANEL_OPEN = "panel_open";
86     static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
87     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
88 
89     private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
90 
91     public static final long DOZE_ANIMATION_DURATION = 700;
92 
93     private KeyguardAffordanceHelper mAfforanceHelper;
94     private KeyguardUserSwitcher mKeyguardUserSwitcher;
95     private KeyguardStatusBarView mKeyguardStatusBar;
96     protected QSContainer mQsContainer;
97     private AutoReinflateContainer mQsAutoReinflateContainer;
98     private KeyguardStatusView mKeyguardStatusView;
99     private TextView mClockView;
100     private View mReserveNotificationSpace;
101     private View mQsNavbarScrim;
102     protected NotificationsQuickSettingsContainer mNotificationContainerParent;
103     protected NotificationStackScrollLayout mNotificationStackScroller;
104     private boolean mAnimateNextTopPaddingChange;
105 
106     private int mTrackingPointer;
107     private VelocityTracker mVelocityTracker;
108     private boolean mQsTracking;
109 
110     /**
111      * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
112      * the expansion for quick settings.
113      */
114     private boolean mConflictingQsExpansionGesture;
115 
116     /**
117      * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
118      * intercepted yet.
119      */
120     private boolean mIntercepting;
121     private boolean mPanelExpanded;
122     private boolean mQsExpanded;
123     private boolean mQsExpandedWhenExpandingStarted;
124     private boolean mQsFullyExpanded;
125     private boolean mKeyguardShowing;
126     private boolean mDozing;
127     private boolean mDozingOnDown;
128     private int mStatusBarState;
129     private float mInitialHeightOnTouch;
130     private float mInitialTouchX;
131     private float mInitialTouchY;
132     private float mLastTouchX;
133     private float mLastTouchY;
134     protected float mQsExpansionHeight;
135     protected int mQsMinExpansionHeight;
136     protected int mQsMaxExpansionHeight;
137     private int mQsPeekHeight;
138     private boolean mStackScrollerOverscrolling;
139     private boolean mQsExpansionFromOverscroll;
140     private float mLastOverscroll;
141     protected boolean mQsExpansionEnabled = true;
142     private ValueAnimator mQsExpansionAnimator;
143     private FlingAnimationUtils mFlingAnimationUtils;
144     private int mStatusBarMinHeight;
145     private boolean mUnlockIconActive;
146     private int mNotificationsHeaderCollideDistance;
147     private int mUnlockMoveDistance;
148     private float mEmptyDragAmount;
149 
150     private ObjectAnimator mClockAnimator;
151     private int mClockAnimationTarget = -1;
152     private int mTopPaddingAdjustment;
153     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
154             new KeyguardClockPositionAlgorithm();
155     private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
156             new KeyguardClockPositionAlgorithm.Result();
157     private boolean mIsExpanding;
158 
159     private boolean mBlockTouches;
160     private int mNotificationScrimWaitDistance;
161     // Used for two finger gesture as well as accessibility shortcut to QS.
162     private boolean mQsExpandImmediate;
163     private boolean mTwoFingerQsExpandPossible;
164 
165     /**
166      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
167      * need to take this into account in our panel height calculation.
168      */
169     private boolean mQsAnimatorExpand;
170     private boolean mIsLaunchTransitionFinished;
171     private boolean mIsLaunchTransitionRunning;
172     private Runnable mLaunchAnimationEndRunnable;
173     private boolean mOnlyAffordanceInThisMotion;
174     private boolean mKeyguardStatusViewAnimating;
175     private ValueAnimator mQsSizeChangeAnimator;
176 
177     private boolean mShadeEmpty;
178 
179     private boolean mQsScrimEnabled = true;
180     private boolean mLastAnnouncementWasQuickSettings;
181     private boolean mQsTouchAboveFalsingThreshold;
182     private int mQsFalsingThreshold;
183 
184     private float mKeyguardStatusBarAnimateAlpha = 1f;
185     private int mOldLayoutDirection;
186     private HeadsUpTouchHelper mHeadsUpTouchHelper;
187     private boolean mIsExpansionFromHeadsUp;
188     private boolean mListenForHeadsUp;
189     private int mNavigationBarBottomHeight;
190     private boolean mExpandingFromHeadsUp;
191     private boolean mCollapsedOnDown;
192     private int mPositionMinSideMargin;
193     private int mMaxFadeoutHeight;
194     private int mLastOrientation = -1;
195     private boolean mClosingWithAlphaFadeOut;
196     private boolean mHeadsUpAnimatingAway;
197     private boolean mLaunchingAffordance;
198     private FalsingManager mFalsingManager;
199     private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
200 
201     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
202         @Override
203         public void run() {
204             mHeadsUpAnimatingAway = false;
205             notifyBarPanelExpansionChanged();
206         }
207     };
208     private NotificationGroupManager mGroupManager;
209 
NotificationPanelView(Context context, AttributeSet attrs)210     public NotificationPanelView(Context context, AttributeSet attrs) {
211         super(context, attrs);
212         setWillNotDraw(!DEBUG);
213         mFalsingManager = FalsingManager.getInstance(context);
214     }
215 
setStatusBar(PhoneStatusBar bar)216     public void setStatusBar(PhoneStatusBar bar) {
217         mStatusBar = bar;
218     }
219 
220     @Override
onFinishInflate()221     protected void onFinishInflate() {
222         super.onFinishInflate();
223         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
224         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
225         mClockView = (TextView) findViewById(R.id.clock_view);
226 
227         mNotificationContainerParent = (NotificationsQuickSettingsContainer)
228                 findViewById(R.id.notification_container_parent);
229         mNotificationStackScroller = (NotificationStackScrollLayout)
230                 findViewById(R.id.notification_stack_scroller);
231         mNotificationStackScroller.setOnHeightChangedListener(this);
232         mNotificationStackScroller.setOverscrollTopChangedListener(this);
233         mNotificationStackScroller.setOnEmptySpaceClickListener(this);
234         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
235         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
236         mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
237         mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper);
238         mLastOrientation = getResources().getConfiguration().orientation;
239 
240         mQsAutoReinflateContainer =
241                 (AutoReinflateContainer) findViewById(R.id.qs_auto_reinflate_container);
242         mQsAutoReinflateContainer.addInflateListener(new InflateListener() {
243             @Override
244             public void onInflated(View v) {
245                 mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container);
246                 mQsContainer.setPanelView(NotificationPanelView.this);
247                 mQsContainer.getHeader().findViewById(R.id.expand_indicator)
248                         .setOnClickListener(NotificationPanelView.this);
249 
250                 // recompute internal state when qspanel height changes
251                 mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
252                     @Override
253                     public void onLayoutChange(View v, int left, int top, int right, int bottom,
254                             int oldLeft, int oldTop, int oldRight, int oldBottom) {
255                         final int height = bottom - top;
256                         final int oldHeight = oldBottom - oldTop;
257                         if (height != oldHeight) {
258                             onQsHeightChanged();
259                         }
260                     }
261                 });
262                 mNotificationStackScroller.setQsContainer(mQsContainer);
263             }
264         });
265     }
266 
267     @Override
loadDimens()268     protected void loadDimens() {
269         super.loadDimens();
270         mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
271         mStatusBarMinHeight = getResources().getDimensionPixelSize(
272                 com.android.internal.R.dimen.status_bar_height);
273         mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
274         mNotificationsHeaderCollideDistance =
275                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
276         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
277         mClockPositionAlgorithm.loadDimens(getResources());
278         mNotificationScrimWaitDistance =
279                 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
280         mQsFalsingThreshold = getResources().getDimensionPixelSize(
281                 R.dimen.qs_falsing_threshold);
282         mPositionMinSideMargin = getResources().getDimensionPixelSize(
283                 R.dimen.notification_panel_min_side_margin);
284         mMaxFadeoutHeight = getResources().getDimensionPixelSize(
285                 R.dimen.max_notification_fadeout_height);
286     }
287 
updateResources()288     public void updateResources() {
289         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
290         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
291         FrameLayout.LayoutParams lp =
292                 (FrameLayout.LayoutParams) mQsAutoReinflateContainer.getLayoutParams();
293         if (lp.width != panelWidth) {
294             lp.width = panelWidth;
295             lp.gravity = panelGravity;
296             mQsAutoReinflateContainer.setLayoutParams(lp);
297             mQsContainer.post(mUpdateHeader);
298         }
299 
300         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
301         if (lp.width != panelWidth) {
302             lp.width = panelWidth;
303             lp.gravity = panelGravity;
304             mNotificationStackScroller.setLayoutParams(lp);
305         }
306     }
307 
308     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)309     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
310         super.onLayout(changed, left, top, right, bottom);
311 
312         // Update Clock Pivot
313         mKeyguardStatusView.setPivotX(getWidth() / 2);
314         mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
315 
316         // Calculate quick setting heights.
317         int oldMaxHeight = mQsMaxExpansionHeight;
318         mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getQsMinExpansionHeight();
319         mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
320         positionClockAndNotifications();
321         if (mQsExpanded && mQsFullyExpanded) {
322             mQsExpansionHeight = mQsMaxExpansionHeight;
323             requestScrollerTopPaddingUpdate(false /* animate */);
324             requestPanelHeightUpdate();
325 
326             // Size has changed, start an animation.
327             if (mQsMaxExpansionHeight != oldMaxHeight) {
328                 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
329             }
330         } else if (!mQsExpanded) {
331             setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
332         }
333         updateExpandedHeight(getExpandedHeight());
334         updateHeader();
335 
336         // If we are running a size change animation, the animation takes care of the height of
337         // the container. However, if we are not animating, we always need to make the QS container
338         // the desired height so when closing the QS detail, it stays smaller after the size change
339         // animation is finished but the detail view is still being animated away (this animation
340         // takes longer than the size change animation).
341         if (mQsSizeChangeAnimator == null) {
342             mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
343         }
344         updateMaxHeadsUpTranslation();
345     }
346 
startQsSizeChangeAnimation(int oldHeight, final int newHeight)347     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
348         if (mQsSizeChangeAnimator != null) {
349             oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
350             mQsSizeChangeAnimator.cancel();
351         }
352         mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
353         mQsSizeChangeAnimator.setDuration(300);
354         mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
355         mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
356             @Override
357             public void onAnimationUpdate(ValueAnimator animation) {
358                 requestScrollerTopPaddingUpdate(false /* animate */);
359                 requestPanelHeightUpdate();
360                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
361                 mQsContainer.setHeightOverride(height);
362             }
363         });
364         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
365             @Override
366             public void onAnimationEnd(Animator animation) {
367                 mQsSizeChangeAnimator = null;
368             }
369         });
370         mQsSizeChangeAnimator.start();
371     }
372 
373     /**
374      * Positions the clock and notifications dynamically depending on how many notifications are
375      * showing.
376      */
positionClockAndNotifications()377     private void positionClockAndNotifications() {
378         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
379         int stackScrollerPadding;
380         if (mStatusBarState != StatusBarState.KEYGUARD) {
381             stackScrollerPadding = mQsContainer.getHeader().getHeight() + mQsPeekHeight;
382             mTopPaddingAdjustment = 0;
383         } else {
384             mClockPositionAlgorithm.setup(
385                     mStatusBar.getMaxKeyguardNotifications(),
386                     getMaxPanelHeight(),
387                     getExpandedHeight(),
388                     mNotificationStackScroller.getNotGoneChildCount(),
389                     getHeight(),
390                     mKeyguardStatusView.getHeight(),
391                     mEmptyDragAmount);
392             mClockPositionAlgorithm.run(mClockPositionResult);
393             if (animate || mClockAnimator != null) {
394                 startClockAnimation(mClockPositionResult.clockY);
395             } else {
396                 mKeyguardStatusView.setY(mClockPositionResult.clockY);
397             }
398             updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
399             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
400             mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
401         }
402         mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
403         requestScrollerTopPaddingUpdate(animate);
404     }
405 
406     /**
407      * @param maximum the maximum to return at most
408      * @return the maximum keyguard notifications that can fit on the screen
409      */
computeMaxKeyguardNotifications(int maximum)410     public int computeMaxKeyguardNotifications(int maximum) {
411         float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding(getHeight(),
412                 mKeyguardStatusView.getHeight());
413         int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
414                 R.dimen.notification_divider_height));
415         final int overflowheight = getResources().getDimensionPixelSize(
416                 R.dimen.notification_summary_height);
417         float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
418         float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
419                 - bottomStackSize;
420         int count = 0;
421         for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
422             ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
423             if (!(child instanceof ExpandableNotificationRow)) {
424                 continue;
425             }
426             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
427             boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
428                     row.getStatusBarNotification());
429             if (suppressedSummary) {
430                 continue;
431             }
432             if (!mStatusBar.shouldShowOnKeyguard(row.getStatusBarNotification())) {
433                 continue;
434             }
435             if (row.isRemoved()) {
436                 continue;
437             }
438             availableSpace -= child.getMinHeight() + notificationPadding;
439             if (availableSpace >= 0 && count < maximum) {
440                 count++;
441             } else {
442                 return count;
443             }
444         }
445         return count;
446     }
447 
startClockAnimation(int y)448     private void startClockAnimation(int y) {
449         if (mClockAnimationTarget == y) {
450             return;
451         }
452         mClockAnimationTarget = y;
453         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
454             @Override
455             public boolean onPreDraw() {
456                 getViewTreeObserver().removeOnPreDrawListener(this);
457                 if (mClockAnimator != null) {
458                     mClockAnimator.removeAllListeners();
459                     mClockAnimator.cancel();
460                 }
461                 mClockAnimator = ObjectAnimator
462                         .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
463                 mClockAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
464                 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
465                 mClockAnimator.addListener(new AnimatorListenerAdapter() {
466                     @Override
467                     public void onAnimationEnd(Animator animation) {
468                         mClockAnimator = null;
469                         mClockAnimationTarget = -1;
470                     }
471                 });
472                 mClockAnimator.start();
473                 return true;
474             }
475         });
476     }
477 
updateClock(float alpha, float scale)478     private void updateClock(float alpha, float scale) {
479         if (!mKeyguardStatusViewAnimating) {
480             mKeyguardStatusView.setAlpha(alpha);
481         }
482         mKeyguardStatusView.setScaleX(scale);
483         mKeyguardStatusView.setScaleY(scale);
484     }
485 
animateToFullShade(long delay)486     public void animateToFullShade(long delay) {
487         mAnimateNextTopPaddingChange = true;
488         mNotificationStackScroller.goToFullShade(delay);
489         requestLayout();
490     }
491 
setQsExpansionEnabled(boolean qsExpansionEnabled)492     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
493         mQsExpansionEnabled = qsExpansionEnabled;
494         mQsContainer.setHeaderClickable(qsExpansionEnabled);
495     }
496 
497     @Override
resetViews()498     public void resetViews() {
499         mIsLaunchTransitionFinished = false;
500         mBlockTouches = false;
501         mUnlockIconActive = false;
502         if (!mLaunchingAffordance) {
503             mAfforanceHelper.reset(false);
504             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
505         }
506         closeQs();
507         mStatusBar.dismissPopups();
508         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
509                 true /* cancelAnimators */);
510         mNotificationStackScroller.resetScrollPosition();
511     }
512 
closeQs()513     public void closeQs() {
514         cancelQsAnimation();
515         setQsExpansion(mQsMinExpansionHeight);
516     }
517 
animateCloseQs()518     public void animateCloseQs() {
519         if (mQsExpansionAnimator != null) {
520             if (!mQsAnimatorExpand) {
521                 return;
522             }
523             float height = mQsExpansionHeight;
524             mQsExpansionAnimator.cancel();
525             setQsExpansion(height);
526         }
527         flingSettings(0 /* vel */, false);
528     }
529 
openQs()530     public void openQs() {
531         cancelQsAnimation();
532         if (mQsExpansionEnabled) {
533             setQsExpansion(mQsMaxExpansionHeight);
534         }
535     }
536 
expandWithQs()537     public void expandWithQs() {
538         if (mQsExpansionEnabled) {
539             mQsExpandImmediate = true;
540         }
541         expand(true /* animate */);
542     }
543 
544     @Override
fling(float vel, boolean expand)545     public void fling(float vel, boolean expand) {
546         GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
547         if (gr != null) {
548             gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
549         }
550         super.fling(vel, expand);
551     }
552 
553     @Override
flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing)554     protected void flingToHeight(float vel, boolean expand, float target,
555             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
556         mHeadsUpTouchHelper.notifyFling(!expand);
557         setClosingWithAlphaFadeout(!expand
558                 && mNotificationStackScroller.getFirstChildIntrinsicHeight() <= mMaxFadeoutHeight
559                 && getFadeoutAlpha() == 1.0f);
560         super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
561     }
562 
563     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)564     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
565         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
566             event.getText().add(getKeyguardOrLockScreenString());
567             mLastAnnouncementWasQuickSettings = false;
568             return true;
569         }
570         return super.dispatchPopulateAccessibilityEventInternal(event);
571     }
572 
573     @Override
onInterceptTouchEvent(MotionEvent event)574     public boolean onInterceptTouchEvent(MotionEvent event) {
575         if (mBlockTouches || mQsContainer.isCustomizing()) {
576             return false;
577         }
578         initDownStates(event);
579         if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
580             mIsExpansionFromHeadsUp = true;
581             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
582             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
583             return true;
584         }
585         if (!isFullyCollapsed() && onQsIntercept(event)) {
586             return true;
587         }
588         return super.onInterceptTouchEvent(event);
589     }
590 
onQsIntercept(MotionEvent event)591     private boolean onQsIntercept(MotionEvent event) {
592         int pointerIndex = event.findPointerIndex(mTrackingPointer);
593         if (pointerIndex < 0) {
594             pointerIndex = 0;
595             mTrackingPointer = event.getPointerId(pointerIndex);
596         }
597         final float x = event.getX(pointerIndex);
598         final float y = event.getY(pointerIndex);
599 
600         switch (event.getActionMasked()) {
601             case MotionEvent.ACTION_DOWN:
602                 mIntercepting = true;
603                 mInitialTouchY = y;
604                 mInitialTouchX = x;
605                 initVelocityTracker();
606                 trackMovement(event);
607                 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
608                     getParent().requestDisallowInterceptTouchEvent(true);
609                 }
610                 if (mQsExpansionAnimator != null) {
611                     onQsExpansionStarted();
612                     mInitialHeightOnTouch = mQsExpansionHeight;
613                     mQsTracking = true;
614                     mIntercepting = false;
615                     mNotificationStackScroller.removeLongPressCallback();
616                 }
617                 break;
618             case MotionEvent.ACTION_POINTER_UP:
619                 final int upPointer = event.getPointerId(event.getActionIndex());
620                 if (mTrackingPointer == upPointer) {
621                     // gesture is ongoing, find a new pointer to track
622                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
623                     mTrackingPointer = event.getPointerId(newIndex);
624                     mInitialTouchX = event.getX(newIndex);
625                     mInitialTouchY = event.getY(newIndex);
626                 }
627                 break;
628 
629             case MotionEvent.ACTION_MOVE:
630                 final float h = y - mInitialTouchY;
631                 trackMovement(event);
632                 if (mQsTracking) {
633 
634                     // Already tracking because onOverscrolled was called. We need to update here
635                     // so we don't stop for a frame until the next touch event gets handled in
636                     // onTouchEvent.
637                     setQsExpansion(h + mInitialHeightOnTouch);
638                     trackMovement(event);
639                     mIntercepting = false;
640                     return true;
641                 }
642                 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
643                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
644                     mQsTracking = true;
645                     onQsExpansionStarted();
646                     notifyExpandingFinished();
647                     mInitialHeightOnTouch = mQsExpansionHeight;
648                     mInitialTouchY = y;
649                     mInitialTouchX = x;
650                     mIntercepting = false;
651                     mNotificationStackScroller.removeLongPressCallback();
652                     return true;
653                 }
654                 break;
655 
656             case MotionEvent.ACTION_CANCEL:
657             case MotionEvent.ACTION_UP:
658                 trackMovement(event);
659                 if (mQsTracking) {
660                     flingQsWithCurrentVelocity(y,
661                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
662                     mQsTracking = false;
663                 }
664                 mIntercepting = false;
665                 break;
666         }
667         return false;
668     }
669 
670     @Override
isInContentBounds(float x, float y)671     protected boolean isInContentBounds(float x, float y) {
672         float stackScrollerX = mNotificationStackScroller.getX();
673         return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
674                 && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
675     }
676 
initDownStates(MotionEvent event)677     private void initDownStates(MotionEvent event) {
678         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
679             mOnlyAffordanceInThisMotion = false;
680             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
681             mDozingOnDown = isDozing();
682             mCollapsedOnDown = isFullyCollapsed();
683             mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
684         }
685     }
686 
flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent)687     private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
688         float vel = getCurrentVelocity();
689         final boolean expandsQs = flingExpandsQs(vel);
690         if (expandsQs) {
691             logQsSwipeDown(y);
692         }
693         flingSettings(vel, expandsQs && !isCancelMotionEvent);
694     }
695 
logQsSwipeDown(float y)696     private void logQsSwipeDown(float y) {
697         float vel = getCurrentVelocity();
698         final int gesture = mStatusBarState == StatusBarState.KEYGUARD
699                 ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS
700                 : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS;
701         EventLogTags.writeSysuiLockscreenGesture(
702                 gesture,
703                 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
704                 (int) (vel / mStatusBar.getDisplayDensity()));
705     }
706 
flingExpandsQs(float vel)707     private boolean flingExpandsQs(float vel) {
708         if (isFalseTouch()) {
709             return false;
710         }
711         if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
712             return getQsExpansionFraction() > 0.5f;
713         } else {
714             return vel > 0;
715         }
716     }
717 
isFalseTouch()718     private boolean isFalseTouch() {
719         if (!needsAntiFalsing()) {
720             return false;
721         }
722         if (mFalsingManager.isClassiferEnabled()) {
723             return mFalsingManager.isFalseTouch();
724         }
725         return !mQsTouchAboveFalsingThreshold;
726     }
727 
getQsExpansionFraction()728     private float getQsExpansionFraction() {
729         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
730                 / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
731     }
732 
733     @Override
onTouchEvent(MotionEvent event)734     public boolean onTouchEvent(MotionEvent event) {
735         if (mBlockTouches || mQsContainer.isCustomizing()) {
736             return false;
737         }
738         initDownStates(event);
739         if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
740                 && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
741             mIsExpansionFromHeadsUp = true;
742             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
743         }
744         if ((!mIsExpanding || mHintAnimationRunning)
745                 && !mQsExpanded
746                 && mStatusBar.getBarState() != StatusBarState.SHADE) {
747             mAfforanceHelper.onTouchEvent(event);
748         }
749         if (mOnlyAffordanceInThisMotion) {
750             return true;
751         }
752         mHeadsUpTouchHelper.onTouchEvent(event);
753         if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
754             return true;
755         }
756         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
757             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
758             updateVerticalPanelPosition(event.getX());
759         }
760         super.onTouchEvent(event);
761         return true;
762     }
763 
handleQsTouch(MotionEvent event)764     private boolean handleQsTouch(MotionEvent event) {
765         final int action = event.getActionMasked();
766         if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
767                 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
768                 && mQsExpansionEnabled) {
769 
770             // Down in the empty area while fully expanded - go to QS.
771             mQsTracking = true;
772             mConflictingQsExpansionGesture = true;
773             onQsExpansionStarted();
774             mInitialHeightOnTouch = mQsExpansionHeight;
775             mInitialTouchY = event.getX();
776             mInitialTouchX = event.getY();
777         }
778         if (!isFullyCollapsed()) {
779             handleQsDown(event);
780         }
781         if (!mQsExpandImmediate && mQsTracking) {
782             onQsTouch(event);
783             if (!mConflictingQsExpansionGesture) {
784                 return true;
785             }
786         }
787         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
788             mConflictingQsExpansionGesture = false;
789         }
790         if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
791                 && mQsExpansionEnabled) {
792             mTwoFingerQsExpandPossible = true;
793         }
794         if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
795                 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
796             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
797             mQsExpandImmediate = true;
798             requestPanelHeightUpdate();
799 
800             // Normally, we start listening when the panel is expanded, but here we need to start
801             // earlier so the state is already up to date when dragging down.
802             setListening(true);
803         }
804         return false;
805     }
806 
isInQsArea(float x, float y)807     private boolean isInQsArea(float x, float y) {
808         return (x >= mQsAutoReinflateContainer.getX()
809                 && x <= mQsAutoReinflateContainer.getX() + mQsAutoReinflateContainer.getWidth())
810                 && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
811                 || y <= mQsContainer.getY() + mQsContainer.getHeight());
812     }
813 
isOpenQsEvent(MotionEvent event)814     private boolean isOpenQsEvent(MotionEvent event) {
815         final int pointerCount = event.getPointerCount();
816         final int action = event.getActionMasked();
817 
818         final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
819                 && pointerCount == 2;
820 
821         final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
822                 && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
823                         || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
824 
825         final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
826                 && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
827                         || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
828 
829         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
830     }
831 
handleQsDown(MotionEvent event)832     private void handleQsDown(MotionEvent event) {
833         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
834                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
835             mFalsingManager.onQsDown();
836             mQsTracking = true;
837             onQsExpansionStarted();
838             mInitialHeightOnTouch = mQsExpansionHeight;
839             mInitialTouchY = event.getX();
840             mInitialTouchX = event.getY();
841 
842             // If we interrupt an expansion gesture here, make sure to update the state correctly.
843             notifyExpandingFinished();
844         }
845     }
846 
847     @Override
flingExpands(float vel, float vectorVel, float x, float y)848     protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
849         boolean expands = super.flingExpands(vel, vectorVel, x, y);
850 
851         // If we are already running a QS expansion, make sure that we keep the panel open.
852         if (mQsExpansionAnimator != null) {
853             expands = true;
854         }
855         return expands;
856     }
857 
858     @Override
hasConflictingGestures()859     protected boolean hasConflictingGestures() {
860         return mStatusBar.getBarState() != StatusBarState.SHADE;
861     }
862 
863     @Override
shouldGestureIgnoreXTouchSlop(float x, float y)864     protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
865         return !mAfforanceHelper.isOnAffordanceIcon(x, y);
866     }
867 
onQsTouch(MotionEvent event)868     private void onQsTouch(MotionEvent event) {
869         int pointerIndex = event.findPointerIndex(mTrackingPointer);
870         if (pointerIndex < 0) {
871             pointerIndex = 0;
872             mTrackingPointer = event.getPointerId(pointerIndex);
873         }
874         final float y = event.getY(pointerIndex);
875         final float x = event.getX(pointerIndex);
876         final float h = y - mInitialTouchY;
877 
878         switch (event.getActionMasked()) {
879             case MotionEvent.ACTION_DOWN:
880                 mQsTracking = true;
881                 mInitialTouchY = y;
882                 mInitialTouchX = x;
883                 onQsExpansionStarted();
884                 mInitialHeightOnTouch = mQsExpansionHeight;
885                 initVelocityTracker();
886                 trackMovement(event);
887                 break;
888 
889             case MotionEvent.ACTION_POINTER_UP:
890                 final int upPointer = event.getPointerId(event.getActionIndex());
891                 if (mTrackingPointer == upPointer) {
892                     // gesture is ongoing, find a new pointer to track
893                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
894                     final float newY = event.getY(newIndex);
895                     final float newX = event.getX(newIndex);
896                     mTrackingPointer = event.getPointerId(newIndex);
897                     mInitialHeightOnTouch = mQsExpansionHeight;
898                     mInitialTouchY = newY;
899                     mInitialTouchX = newX;
900                 }
901                 break;
902 
903             case MotionEvent.ACTION_MOVE:
904                 setQsExpansion(h + mInitialHeightOnTouch);
905                 if (h >= getFalsingThreshold()) {
906                     mQsTouchAboveFalsingThreshold = true;
907                 }
908                 trackMovement(event);
909                 break;
910 
911             case MotionEvent.ACTION_UP:
912             case MotionEvent.ACTION_CANCEL:
913                 mQsTracking = false;
914                 mTrackingPointer = -1;
915                 trackMovement(event);
916                 float fraction = getQsExpansionFraction();
917                 if (fraction != 0f || y >= mInitialTouchY) {
918                     flingQsWithCurrentVelocity(y,
919                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
920                 }
921                 if (mVelocityTracker != null) {
922                     mVelocityTracker.recycle();
923                     mVelocityTracker = null;
924                 }
925                 break;
926         }
927     }
928 
getFalsingThreshold()929     private int getFalsingThreshold() {
930         float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
931         return (int) (mQsFalsingThreshold * factor);
932     }
933 
934     @Override
onOverscrollTopChanged(float amount, boolean isRubberbanded)935     public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
936         cancelQsAnimation();
937         if (!mQsExpansionEnabled) {
938             amount = 0f;
939         }
940         float rounded = amount >= 1f ? amount : 0f;
941         setOverScrolling(rounded != 0f && isRubberbanded);
942         mQsExpansionFromOverscroll = rounded != 0f;
943         mLastOverscroll = rounded;
944         updateQsState();
945         setQsExpansion(mQsMinExpansionHeight + rounded);
946     }
947 
948     @Override
flingTopOverscroll(float velocity, boolean open)949     public void flingTopOverscroll(float velocity, boolean open) {
950         mLastOverscroll = 0f;
951         mQsExpansionFromOverscroll = false;
952         setQsExpansion(mQsExpansionHeight);
953         flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
954                 new Runnable() {
955                     @Override
956                     public void run() {
957                         mStackScrollerOverscrolling = false;
958                         setOverScrolling(false);
959                         updateQsState();
960                     }
961                 }, false /* isClick */);
962     }
963 
setOverScrolling(boolean overscrolling)964     private void setOverScrolling(boolean overscrolling) {
965         mStackScrollerOverscrolling = overscrolling;
966         mQsContainer.setOverscrolling(overscrolling);
967     }
968 
onQsExpansionStarted()969     private void onQsExpansionStarted() {
970         onQsExpansionStarted(0);
971     }
972 
onQsExpansionStarted(int overscrollAmount)973     private void onQsExpansionStarted(int overscrollAmount) {
974         cancelQsAnimation();
975         cancelHeightAnimator();
976 
977         // Reset scroll position and apply that position to the expanded height.
978         float height = mQsExpansionHeight - overscrollAmount;
979         setQsExpansion(height);
980         requestPanelHeightUpdate();
981     }
982 
setQsExpanded(boolean expanded)983     private void setQsExpanded(boolean expanded) {
984         boolean changed = mQsExpanded != expanded;
985         if (changed) {
986             mQsExpanded = expanded;
987             updateQsState();
988             requestPanelHeightUpdate();
989             mFalsingManager.setQsExpanded(expanded);
990             mStatusBar.setQsExpanded(expanded);
991             mNotificationContainerParent.setQsExpanded(expanded);
992         }
993     }
994 
setBarState(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)995     public void setBarState(int statusBarState, boolean keyguardFadingAway,
996             boolean goingToFullShade) {
997         int oldState = mStatusBarState;
998         boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
999         setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
1000         setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
1001 
1002         mStatusBarState = statusBarState;
1003         mKeyguardShowing = keyguardShowing;
1004         mQsContainer.setKeyguardShowing(mKeyguardShowing);
1005 
1006         if (oldState == StatusBarState.KEYGUARD
1007                 && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
1008             animateKeyguardStatusBarOut();
1009             long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
1010                     ? 0 : mStatusBar.calculateGoingToFullShadeDelay();
1011             mQsContainer.animateHeaderSlidingIn(delay);
1012         } else if (oldState == StatusBarState.SHADE_LOCKED
1013                 && statusBarState == StatusBarState.KEYGUARD) {
1014             animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1015             mQsContainer.animateHeaderSlidingOut();
1016         } else {
1017             mKeyguardStatusBar.setAlpha(1f);
1018             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
1019             if (keyguardShowing && oldState != mStatusBarState) {
1020                 mKeyguardBottomArea.onKeyguardShowingChanged();
1021                 mQsContainer.hideImmediately();
1022             }
1023         }
1024         if (keyguardShowing) {
1025             updateDozingVisibilities(false /* animate */);
1026         }
1027         resetVerticalPanelPosition();
1028         updateQsState();
1029     }
1030 
1031     private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
1032         @Override
1033         public void run() {
1034             mKeyguardStatusViewAnimating = false;
1035             mKeyguardStatusView.setVisibility(View.GONE);
1036         }
1037     };
1038 
1039     private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
1040         @Override
1041         public void run() {
1042             mKeyguardStatusViewAnimating = false;
1043         }
1044     };
1045 
1046     private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
1047         @Override
1048         public void run() {
1049             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
1050             mKeyguardStatusBar.setAlpha(1f);
1051             mKeyguardStatusBarAnimateAlpha = 1f;
1052         }
1053     };
1054 
animateKeyguardStatusBarOut()1055     private void animateKeyguardStatusBarOut() {
1056         ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
1057         anim.addUpdateListener(mStatusBarAnimateAlphaListener);
1058         anim.setStartDelay(mStatusBar.isKeyguardFadingAway()
1059                 ? mStatusBar.getKeyguardFadingAwayDelay()
1060                 : 0);
1061         anim.setDuration(mStatusBar.isKeyguardFadingAway()
1062                 ? mStatusBar.getKeyguardFadingAwayDuration() / 2
1063                 : StackStateAnimator.ANIMATION_DURATION_STANDARD);
1064         anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
1065         anim.addListener(new AnimatorListenerAdapter() {
1066             @Override
1067             public void onAnimationEnd(Animator animation) {
1068                 mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
1069             }
1070         });
1071         anim.start();
1072     }
1073 
1074     private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
1075             new ValueAnimator.AnimatorUpdateListener() {
1076         @Override
1077         public void onAnimationUpdate(ValueAnimator animation) {
1078             mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
1079             updateHeaderKeyguardAlpha();
1080         }
1081     };
1082 
animateKeyguardStatusBarIn(long duration)1083     private void animateKeyguardStatusBarIn(long duration) {
1084         mKeyguardStatusBar.setVisibility(View.VISIBLE);
1085         mKeyguardStatusBar.setAlpha(0f);
1086         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
1087         anim.addUpdateListener(mStatusBarAnimateAlphaListener);
1088         anim.setDuration(duration);
1089         anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
1090         anim.start();
1091     }
1092 
1093     private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
1094         @Override
1095         public void run() {
1096             mKeyguardBottomArea.setVisibility(View.GONE);
1097         }
1098     };
1099 
setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade)1100     private void setKeyguardBottomAreaVisibility(int statusBarState,
1101             boolean goingToFullShade) {
1102         mKeyguardBottomArea.animate().cancel();
1103         if (goingToFullShade) {
1104             mKeyguardBottomArea.animate()
1105                     .alpha(0f)
1106                     .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1107                     .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
1108                     .setInterpolator(Interpolators.ALPHA_OUT)
1109                     .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
1110                     .start();
1111         } else if (statusBarState == StatusBarState.KEYGUARD
1112                 || statusBarState == StatusBarState.SHADE_LOCKED) {
1113             if (!mDozing) {
1114                 mKeyguardBottomArea.setVisibility(View.VISIBLE);
1115             }
1116             mKeyguardBottomArea.setAlpha(1f);
1117         } else {
1118             mKeyguardBottomArea.setVisibility(View.GONE);
1119             mKeyguardBottomArea.setAlpha(1f);
1120         }
1121     }
1122 
setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade)1123     private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
1124             boolean goingToFullShade) {
1125         if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
1126                 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
1127             mKeyguardStatusView.animate().cancel();
1128             mKeyguardStatusViewAnimating = true;
1129             mKeyguardStatusView.animate()
1130                     .alpha(0f)
1131                     .setStartDelay(0)
1132                     .setDuration(160)
1133                     .setInterpolator(Interpolators.ALPHA_OUT)
1134                     .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
1135             if (keyguardFadingAway) {
1136                 mKeyguardStatusView.animate()
1137                         .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
1138                         .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
1139                         .start();
1140             }
1141         } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
1142                 && statusBarState == StatusBarState.KEYGUARD) {
1143             mKeyguardStatusView.animate().cancel();
1144             mKeyguardStatusView.setVisibility(View.VISIBLE);
1145             mKeyguardStatusViewAnimating = true;
1146             mKeyguardStatusView.setAlpha(0f);
1147             mKeyguardStatusView.animate()
1148                     .alpha(1f)
1149                     .setStartDelay(0)
1150                     .setDuration(320)
1151                     .setInterpolator(Interpolators.ALPHA_IN)
1152                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
1153         } else if (statusBarState == StatusBarState.KEYGUARD) {
1154             mKeyguardStatusView.animate().cancel();
1155             mKeyguardStatusViewAnimating = false;
1156             mKeyguardStatusView.setVisibility(View.VISIBLE);
1157             mKeyguardStatusView.setAlpha(1f);
1158         } else {
1159             mKeyguardStatusView.animate().cancel();
1160             mKeyguardStatusViewAnimating = false;
1161             mKeyguardStatusView.setVisibility(View.GONE);
1162             mKeyguardStatusView.setAlpha(1f);
1163         }
1164     }
1165 
updateQsState()1166     private void updateQsState() {
1167         mQsContainer.setExpanded(mQsExpanded);
1168         mNotificationStackScroller.setQsExpanded(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         updateExpandedHeight(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 + mNotificationStackScroller.getTopPaddingOverflow();
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         ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
1734         ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
1735                 ? (ExpandableNotificationRow) firstChildNotGone
1736                 : null;
1737         if (firstRow != null
1738                 && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
1739             requestScrollerTopPaddingUpdate(false);
1740         }
1741         requestPanelHeightUpdate();
1742     }
1743 
1744     @Override
onReset(ExpandableView view)1745     public void onReset(ExpandableView view) {
1746     }
1747 
onQsHeightChanged()1748     public void onQsHeightChanged() {
1749         mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
1750         if (mQsExpanded && mQsFullyExpanded) {
1751             mQsExpansionHeight = mQsMaxExpansionHeight;
1752             requestScrollerTopPaddingUpdate(false /* animate */);
1753             requestPanelHeightUpdate();
1754         }
1755     }
1756 
1757     @Override
onConfigurationChanged(Configuration newConfig)1758     protected void onConfigurationChanged(Configuration newConfig) {
1759         super.onConfigurationChanged(newConfig);
1760         mAfforanceHelper.onConfigurationChanged();
1761         if (newConfig.orientation != mLastOrientation) {
1762             resetVerticalPanelPosition();
1763         }
1764         mLastOrientation = newConfig.orientation;
1765     }
1766 
1767     @Override
onApplyWindowInsets(WindowInsets insets)1768     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1769         mNavigationBarBottomHeight = insets.getStableInsetBottom();
1770         updateMaxHeadsUpTranslation();
1771         return insets;
1772     }
1773 
updateMaxHeadsUpTranslation()1774     private void updateMaxHeadsUpTranslation() {
1775         mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
1776     }
1777 
1778     @Override
onRtlPropertiesChanged(int layoutDirection)1779     public void onRtlPropertiesChanged(int layoutDirection) {
1780         if (layoutDirection != mOldLayoutDirection) {
1781             mAfforanceHelper.onRtlPropertiesChanged();
1782             mOldLayoutDirection = layoutDirection;
1783         }
1784     }
1785 
1786     @Override
onClick(View v)1787     public void onClick(View v) {
1788         if (v.getId() == R.id.expand_indicator) {
1789             onQsExpansionStarted();
1790             if (mQsExpanded) {
1791                 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
1792             } else if (mQsExpansionEnabled) {
1793                 EventLogTags.writeSysuiLockscreenGesture(
1794                         EventLogConstants.SYSUI_TAP_TO_OPEN_QS,
1795                         0, 0);
1796                 flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
1797             }
1798         }
1799     }
1800 
1801     @Override
onAnimationToSideStarted(boolean rightPage, float translation, float vel)1802     public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
1803         boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1804         mIsLaunchTransitionRunning = true;
1805         mLaunchAnimationEndRunnable = null;
1806         float displayDensity = mStatusBar.getDisplayDensity();
1807         int lengthDp = Math.abs((int) (translation / displayDensity));
1808         int velocityDp = Math.abs((int) (vel / displayDensity));
1809         if (start) {
1810             EventLogTags.writeSysuiLockscreenGesture(
1811                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp);
1812 
1813             mFalsingManager.onLeftAffordanceOn();
1814             if (mFalsingManager.shouldEnforceBouncer()) {
1815                 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
1816                     @Override
1817                     public void run() {
1818                         mKeyguardBottomArea.launchLeftAffordance();
1819                     }
1820                 }, null, true /* dismissShade */, false /* afterKeyguardGone */,
1821                         true /* deferred */);
1822             }
1823             else {
1824                 mKeyguardBottomArea.launchLeftAffordance();
1825             }
1826         } else {
1827             if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
1828                     mLastCameraLaunchSource)) {
1829                 EventLogTags.writeSysuiLockscreenGesture(
1830                         EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA,
1831                         lengthDp, velocityDp);
1832             }
1833             mFalsingManager.onCameraOn();
1834             if (mFalsingManager.shouldEnforceBouncer()) {
1835                 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
1836                     @Override
1837                     public void run() {
1838                         mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
1839                     }
1840                 }, null, true /* dismissShade */, false /* afterKeyguardGone */,
1841                     true /* deferred */);
1842             }
1843             else {
1844                 mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
1845             }
1846         }
1847         mStatusBar.startLaunchTransitionTimeout();
1848         mBlockTouches = true;
1849     }
1850 
1851     @Override
onAnimationToSideEnded()1852     public void onAnimationToSideEnded() {
1853         mIsLaunchTransitionRunning = false;
1854         mIsLaunchTransitionFinished = true;
1855         if (mLaunchAnimationEndRunnable != null) {
1856             mLaunchAnimationEndRunnable.run();
1857             mLaunchAnimationEndRunnable = null;
1858         }
1859     }
1860 
1861     @Override
startUnlockHintAnimation()1862     protected void startUnlockHintAnimation() {
1863         super.startUnlockHintAnimation();
1864         startHighlightIconAnimation(getCenterIcon());
1865     }
1866 
1867     /**
1868      * Starts the highlight (making it fully opaque) animation on an icon.
1869      */
startHighlightIconAnimation(final KeyguardAffordanceView icon)1870     private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1871         icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1872                 Interpolators.FAST_OUT_SLOW_IN, new Runnable() {
1873                     @Override
1874                     public void run() {
1875                         icon.setImageAlpha(icon.getRestingAlpha(),
1876                                 true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1877                                 Interpolators.FAST_OUT_SLOW_IN, null);
1878                     }
1879                 });
1880     }
1881 
1882     @Override
getMaxTranslationDistance()1883     public float getMaxTranslationDistance() {
1884         return (float) Math.hypot(getWidth(), getHeight());
1885     }
1886 
1887     @Override
onSwipingStarted(boolean rightIcon)1888     public void onSwipingStarted(boolean rightIcon) {
1889         mFalsingManager.onAffordanceSwipingStarted(rightIcon);
1890         boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
1891                 : rightIcon;
1892         if (camera) {
1893             mKeyguardBottomArea.bindCameraPrewarmService();
1894         }
1895         requestDisallowInterceptTouchEvent(true);
1896         mOnlyAffordanceInThisMotion = true;
1897         mQsTracking = false;
1898     }
1899 
1900     @Override
onSwipingAborted()1901     public void onSwipingAborted() {
1902         mFalsingManager.onAffordanceSwipingAborted();
1903         mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
1904     }
1905 
1906     @Override
onIconClicked(boolean rightIcon)1907     public void onIconClicked(boolean rightIcon) {
1908         if (mHintAnimationRunning) {
1909             return;
1910         }
1911         mHintAnimationRunning = true;
1912         mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
1913             @Override
1914             public void run() {
1915                 mHintAnimationRunning = false;
1916                 mStatusBar.onHintFinished();
1917             }
1918         });
1919         rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
1920         if (rightIcon) {
1921             mStatusBar.onCameraHintStarted();
1922         } else {
1923             if (mKeyguardBottomArea.isLeftVoiceAssist()) {
1924                 mStatusBar.onVoiceAssistHintStarted();
1925             } else {
1926                 mStatusBar.onPhoneHintStarted();
1927             }
1928         }
1929     }
1930 
1931     @Override
getLeftIcon()1932     public KeyguardAffordanceView getLeftIcon() {
1933         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1934                 ? mKeyguardBottomArea.getRightView()
1935                 : mKeyguardBottomArea.getLeftView();
1936     }
1937 
1938     @Override
getCenterIcon()1939     public KeyguardAffordanceView getCenterIcon() {
1940         return mKeyguardBottomArea.getLockIcon();
1941     }
1942 
1943     @Override
getRightIcon()1944     public KeyguardAffordanceView getRightIcon() {
1945         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1946                 ? mKeyguardBottomArea.getLeftView()
1947                 : mKeyguardBottomArea.getRightView();
1948     }
1949 
1950     @Override
getLeftPreview()1951     public View getLeftPreview() {
1952         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1953                 ? mKeyguardBottomArea.getRightPreview()
1954                 : mKeyguardBottomArea.getLeftPreview();
1955     }
1956 
1957     @Override
getRightPreview()1958     public View getRightPreview() {
1959         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1960                 ? mKeyguardBottomArea.getLeftPreview()
1961                 : mKeyguardBottomArea.getRightPreview();
1962     }
1963 
1964     @Override
getAffordanceFalsingFactor()1965     public float getAffordanceFalsingFactor() {
1966         return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
1967     }
1968 
1969     @Override
needsAntiFalsing()1970     public boolean needsAntiFalsing() {
1971         return mStatusBarState == StatusBarState.KEYGUARD;
1972     }
1973 
1974     @Override
getPeekHeight()1975     protected float getPeekHeight() {
1976         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1977             return mNotificationStackScroller.getPeekHeight();
1978         } else {
1979             return mQsMinExpansionHeight;
1980         }
1981     }
1982 
1983     @Override
getCannedFlingDurationFactor()1984     protected float getCannedFlingDurationFactor() {
1985         if (mQsExpanded) {
1986             return 0.7f;
1987         } else {
1988             return 0.6f;
1989         }
1990     }
1991 
1992     @Override
fullyExpandedClearAllVisible()1993     protected boolean fullyExpandedClearAllVisible() {
1994         return mNotificationStackScroller.isDismissViewNotGone()
1995                 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
1996     }
1997 
1998     @Override
isClearAllVisible()1999     protected boolean isClearAllVisible() {
2000         return mNotificationStackScroller.isDismissViewVisible();
2001     }
2002 
2003     @Override
getClearAllHeight()2004     protected int getClearAllHeight() {
2005         return mNotificationStackScroller.getDismissViewHeight();
2006     }
2007 
2008     @Override
isTrackingBlocked()2009     protected boolean isTrackingBlocked() {
2010         return mConflictingQsExpansionGesture && mQsExpanded;
2011     }
2012 
isQsExpanded()2013     public boolean isQsExpanded() {
2014         return mQsExpanded;
2015     }
2016 
isQsDetailShowing()2017     public boolean isQsDetailShowing() {
2018         return mQsContainer.isShowingDetail();
2019     }
2020 
closeQsDetail()2021     public void closeQsDetail() {
2022         mQsContainer.getQsPanel().closeDetail();
2023     }
2024 
2025     @Override
shouldDelayChildPressedState()2026     public boolean shouldDelayChildPressedState() {
2027         return true;
2028     }
2029 
isLaunchTransitionFinished()2030     public boolean isLaunchTransitionFinished() {
2031         return mIsLaunchTransitionFinished;
2032     }
2033 
isLaunchTransitionRunning()2034     public boolean isLaunchTransitionRunning() {
2035         return mIsLaunchTransitionRunning;
2036     }
2037 
setLaunchTransitionEndRunnable(Runnable r)2038     public void setLaunchTransitionEndRunnable(Runnable r) {
2039         mLaunchAnimationEndRunnable = r;
2040     }
2041 
setEmptyDragAmount(float amount)2042     public void setEmptyDragAmount(float amount) {
2043         float factor = 0.8f;
2044         if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
2045             factor = 0.4f;
2046         } else if (!mStatusBar.hasActiveNotifications()) {
2047             factor = 0.4f;
2048         }
2049         mEmptyDragAmount = amount * factor;
2050         positionClockAndNotifications();
2051     }
2052 
interpolate(float t, float start, float end)2053     private static float interpolate(float t, float start, float end) {
2054         return (1 - t) * start + t * end;
2055     }
2056 
setDozing(boolean dozing, boolean animate)2057     public void setDozing(boolean dozing, boolean animate) {
2058         if (dozing == mDozing) return;
2059         mDozing = dozing;
2060         if (mStatusBarState == StatusBarState.KEYGUARD) {
2061             updateDozingVisibilities(animate);
2062         }
2063     }
2064 
updateDozingVisibilities(boolean animate)2065     private void updateDozingVisibilities(boolean animate) {
2066         if (mDozing) {
2067             mKeyguardStatusBar.setVisibility(View.INVISIBLE);
2068             mKeyguardBottomArea.setVisibility(View.INVISIBLE);
2069         } else {
2070             mKeyguardBottomArea.setVisibility(View.VISIBLE);
2071             mKeyguardStatusBar.setVisibility(View.VISIBLE);
2072             if (animate) {
2073                 animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION);
2074                 mKeyguardBottomArea.startFinishDozeAnimation();
2075             }
2076         }
2077     }
2078 
2079     @Override
isDozing()2080     public boolean isDozing() {
2081         return mDozing;
2082     }
2083 
setShadeEmpty(boolean shadeEmpty)2084     public void setShadeEmpty(boolean shadeEmpty) {
2085         mShadeEmpty = shadeEmpty;
2086         updateEmptyShadeView();
2087     }
2088 
updateEmptyShadeView()2089     private void updateEmptyShadeView() {
2090 
2091         // Hide "No notifications" in QS.
2092         mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
2093     }
2094 
setQsScrimEnabled(boolean qsScrimEnabled)2095     public void setQsScrimEnabled(boolean qsScrimEnabled) {
2096         boolean changed = mQsScrimEnabled != qsScrimEnabled;
2097         mQsScrimEnabled = qsScrimEnabled;
2098         if (changed) {
2099             updateQsState();
2100         }
2101     }
2102 
setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher)2103     public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
2104         mKeyguardUserSwitcher = keyguardUserSwitcher;
2105     }
2106 
2107     private final Runnable mUpdateHeader = new Runnable() {
2108         @Override
2109         public void run() {
2110             mQsContainer.getHeader().updateEverything();
2111         }
2112     };
2113 
onScreenTurningOn()2114     public void onScreenTurningOn() {
2115         mKeyguardStatusView.refreshTime();
2116     }
2117 
2118     @Override
onEmptySpaceClicked(float x, float y)2119     public void onEmptySpaceClicked(float x, float y) {
2120         onEmptySpaceClick(x);
2121     }
2122 
2123     @Override
onMiddleClicked()2124     protected boolean onMiddleClicked() {
2125         switch (mStatusBar.getBarState()) {
2126             case StatusBarState.KEYGUARD:
2127                 if (!mDozingOnDown) {
2128                     EventLogTags.writeSysuiLockscreenGesture(
2129                             EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
2130                             0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
2131                     startUnlockHintAnimation();
2132                 }
2133                 return true;
2134             case StatusBarState.SHADE_LOCKED:
2135                 if (!mQsExpanded) {
2136                     mStatusBar.goToKeyguard();
2137                 }
2138                 return true;
2139             case StatusBarState.SHADE:
2140 
2141                 // This gets called in the middle of the touch handling, where the state is still
2142                 // that we are tracking the panel. Collapse the panel after this is done.
2143                 post(mPostCollapseRunnable);
2144                 return false;
2145             default:
2146                 return true;
2147         }
2148     }
2149 
2150     @Override
dispatchDraw(Canvas canvas)2151     protected void dispatchDraw(Canvas canvas) {
2152         super.dispatchDraw(canvas);
2153         if (DEBUG) {
2154             Paint p = new Paint();
2155             p.setColor(Color.RED);
2156             p.setStrokeWidth(2);
2157             p.setStyle(Paint.Style.STROKE);
2158             canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
2159             p.setColor(Color.BLUE);
2160             canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
2161             p.setColor(Color.GREEN);
2162             canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
2163                     calculatePanelHeightQsExpanded(), p);
2164             p.setColor(Color.YELLOW);
2165             canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
2166                     calculatePanelHeightShade(), p);
2167             p.setColor(Color.MAGENTA);
2168             canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
2169                     calculateQsTopPadding(), p);
2170             p.setColor(Color.CYAN);
2171             canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
2172                     mNotificationStackScroller.getTopPadding(), p);
2173         }
2174     }
2175 
2176     @Override
onHeadsUpPinnedModeChanged(final boolean inPinnedMode)2177     public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
2178         if (inPinnedMode) {
2179             mHeadsUpExistenceChangedRunnable.run();
2180             updateNotificationTranslucency();
2181         } else {
2182             mHeadsUpAnimatingAway = true;
2183             mNotificationStackScroller.runAfterAnimationFinished(
2184                     mHeadsUpExistenceChangedRunnable);
2185         }
2186     }
2187 
2188     @Override
onHeadsUpPinned(ExpandableNotificationRow headsUp)2189     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
2190         mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
2191     }
2192 
2193     @Override
onHeadsUpUnPinned(ExpandableNotificationRow headsUp)2194     public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
2195     }
2196 
2197     @Override
onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp)2198     public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
2199         mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
2200     }
2201 
2202     @Override
setHeadsUpManager(HeadsUpManager headsUpManager)2203     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2204         super.setHeadsUpManager(headsUpManager);
2205         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller,
2206                 this);
2207     }
2208 
setTrackingHeadsUp(boolean tracking)2209     public void setTrackingHeadsUp(boolean tracking) {
2210         if (tracking) {
2211             mNotificationStackScroller.setTrackingHeadsUp(true);
2212             mExpandingFromHeadsUp = true;
2213         }
2214         // otherwise we update the state when the expansion is finished
2215     }
2216 
2217     @Override
onClosingFinished()2218     protected void onClosingFinished() {
2219         super.onClosingFinished();
2220         resetVerticalPanelPosition();
2221         setClosingWithAlphaFadeout(false);
2222     }
2223 
setClosingWithAlphaFadeout(boolean closing)2224     private void setClosingWithAlphaFadeout(boolean closing) {
2225         mClosingWithAlphaFadeOut = closing;
2226         mNotificationStackScroller.forceNoOverlappingRendering(closing);
2227     }
2228 
2229     /**
2230      * Updates the vertical position of the panel so it is positioned closer to the touch
2231      * responsible for opening the panel.
2232      *
2233      * @param x the x-coordinate the touch event
2234      */
updateVerticalPanelPosition(float x)2235     protected void updateVerticalPanelPosition(float x) {
2236         if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
2237             resetVerticalPanelPosition();
2238             return;
2239         }
2240         float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
2241         float rightMost = getWidth() - mPositionMinSideMargin
2242                 - mNotificationStackScroller.getWidth() / 2;
2243         if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
2244             x = getWidth() / 2;
2245         }
2246         x = Math.min(rightMost, Math.max(leftMost, x));
2247         setVerticalPanelTranslation(x -
2248                 (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
2249      }
2250 
resetVerticalPanelPosition()2251     private void resetVerticalPanelPosition() {
2252         setVerticalPanelTranslation(0f);
2253     }
2254 
setVerticalPanelTranslation(float translation)2255     protected void setVerticalPanelTranslation(float translation) {
2256         mNotificationStackScroller.setTranslationX(translation);
2257         mQsAutoReinflateContainer.setTranslationX(translation);
2258     }
2259 
updateExpandedHeight(float expandedHeight)2260     protected void updateExpandedHeight(float expandedHeight) {
2261         mNotificationStackScroller.setExpandedHeight(expandedHeight);
2262         updateKeyguardBottomAreaAlpha();
2263     }
2264 
setPanelScrimMinFraction(float minFraction)2265     public void setPanelScrimMinFraction(float minFraction) {
2266         mBar.panelScrimMinFractionChanged(minFraction);
2267     }
2268 
clearNotificationEffects()2269     public void clearNotificationEffects() {
2270         mStatusBar.clearNotificationEffects();
2271     }
2272 
2273     @Override
isPanelVisibleBecauseOfHeadsUp()2274     protected boolean isPanelVisibleBecauseOfHeadsUp() {
2275         return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
2276     }
2277 
2278     @Override
hasOverlappingRendering()2279     public boolean hasOverlappingRendering() {
2280         return !mDozing;
2281     }
2282 
launchCamera(boolean animate, int source)2283     public void launchCamera(boolean animate, int source) {
2284         if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
2285             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
2286         } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
2287             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
2288         } else {
2289 
2290             // Default.
2291             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
2292         }
2293 
2294         // If we are launching it when we are occluded already we don't want it to animate,
2295         // nor setting these flags, since the occluded state doesn't change anymore, hence it's
2296         // never reset.
2297         if (!isFullyCollapsed()) {
2298             mLaunchingAffordance = true;
2299             setLaunchingAffordance(true);
2300         } else {
2301             animate = false;
2302         }
2303         mAfforanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
2304     }
2305 
onAffordanceLaunchEnded()2306     public void onAffordanceLaunchEnded() {
2307         mLaunchingAffordance = false;
2308         setLaunchingAffordance(false);
2309     }
2310 
2311     @Override
setAlpha(float alpha)2312     public void setAlpha(float alpha) {
2313         super.setAlpha(alpha);
2314         mNotificationStackScroller.setParentFadingOut(alpha != 1.0f);
2315     }
2316 
2317     /**
2318      * Set whether we are currently launching an affordance. This is currently only set when
2319      * launched via a camera gesture.
2320      */
setLaunchingAffordance(boolean launchingAffordance)2321     private void setLaunchingAffordance(boolean launchingAffordance) {
2322         getLeftIcon().setLaunchingAffordance(launchingAffordance);
2323         getRightIcon().setLaunchingAffordance(launchingAffordance);
2324         getCenterIcon().setLaunchingAffordance(launchingAffordance);
2325     }
2326 
2327     /**
2328      * Whether the camera application can be launched for the camera launch gesture.
2329      *
2330      * @param keyguardIsShowing whether keyguard is being shown
2331      */
canCameraGestureBeLaunched(boolean keyguardIsShowing)2332     public boolean canCameraGestureBeLaunched(boolean keyguardIsShowing) {
2333         ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
2334         String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null)
2335                 ? null : resolveInfo.activityInfo.packageName;
2336         return packageToLaunch != null &&
2337                (keyguardIsShowing || !isForegroundApp(packageToLaunch)) &&
2338                !mAfforanceHelper.isSwipingInProgress();
2339     }
2340 
2341     /**
2342      * Return true if the applications with the package name is running in foreground.
2343      *
2344      * @param pkgName application package name.
2345      */
isForegroundApp(String pkgName)2346     private boolean isForegroundApp(String pkgName) {
2347         ActivityManager am = getContext().getSystemService(ActivityManager.class);
2348         List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
2349         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
2350     }
2351 
setGroupManager(NotificationGroupManager groupManager)2352     public void setGroupManager(NotificationGroupManager groupManager) {
2353         mGroupManager = groupManager;
2354     }
2355 }
2356